web-dev-qa-db-ja.com

タッチリスナーとonclickListenerを使用してボタンを強調表示したままにします

次の操作を行った後、ボタンが強調表示された状態のままになる問題があります。

public class MainActivity extends AppCompatActivity {

    @SuppressLint("ClickableViewAccessibility")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        AppCompatButton button = (AppCompatButton) findViewById(R.id.mybutton);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d("Test", "calling onClick");
            }
        });
        button.setOnTouchListener(new View.OnTouchListener() {

            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN: {
                        v.getBackground().setColorFilter(0xe0f47521,PorterDuff.Mode.SRC_ATOP);
                        v.invalidate();
                        break;
                    }
                    case MotionEvent.ACTION_UP: {
                        v.getBackground().clearColorFilter();
                        v.invalidate();
                        v.performClick();
                        Log.d("Test", "Performing click");
                        return true;
                    }
                }
                return false;
            }
        });

    }
}

上記のコードに関しては、それを使用する場合、ボタンのクリックがタッチで処理されることを期待しています。「true」を返すことで、処理はtouchListenerで停止します。

しかし、これはそうではありません。クリックが呼び出されても、ボタンは強調表示された状態のままです。

私が得るものは:

Test - calling onClick
Test - Performing click

一方、次のコードを使用している場合、ボタンはクリックされ、同じように印刷されますが、ボタンは強調表示された状態で止まりません。

public class MainActivity extends AppCompatActivity {

    @SuppressLint("ClickableViewAccessibility")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        AppCompatButton button = (AppCompatButton) findViewById(R.id.mybutton);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d("Test", "calling onClick");
            }
        });
        button.setOnTouchListener(new View.OnTouchListener() {

            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN: {
                        v.getBackground().setColorFilter(0xe0f47521,PorterDuff.Mode.SRC_ATOP);
                        v.invalidate();
                        break;
                    }
                    case MotionEvent.ACTION_UP: {
                        v.getBackground().clearColorFilter();
                        v.invalidate();
                        // v.performClick();
                        Log.d("Test", "Performing click");
                        return false;
                    }
                }
                return false;
            }
        });

    }
}

タッチイベントへのレスポンダーチェーンとは何かについて少し混乱しています。私の推測では、

1)TouchListener

2)ClickListener

3)ParentViews

誰かもこれを確認できますか?

10
Whitebear

このようなカスタマイズには、プログラムによる変更は必要ありません。 xmlファイルで簡単に行うことができます。まず、setOnTouchListenerで指定したonCreateメソッドを完全に削除します。次に、次のように_res/color_ディレクトリでセレクターの色を定義します。 (ディレクトリが存在しない場合は作成してください)

res/color/button_tint_color.xml

_<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:Android="http://schemas.Android.com/apk/res/Android">
    <item Android:color="#e0f47521" Android:state_pressed="true" />
    <item Android:color="?attr/colorButtonNormal" Android:state_pressed="false" />
</selector>
_

次に、ボタンの_app:backgroundTint_属性に設定します。

_<androidx.appcompat.widget.AppCompatButton
    Android:id="@+id/mybutton"
    Android:layout_width="wrap_content"
    Android:layout_height="wrap_content"
    Android:text="Button"
    app:backgroundTint="@color/button_tint_color" />
_


視覚的な結果:

enter image description here



編集:(タッチイベントの問題に対処するため)

全体的な観点から見ると、タッチイベントのフローはActivityから始まり、レイアウト(親レイアウトから子レイアウト)、そしてビューへと流れていきます。 (次の図のLTRフロー)

enter image description here

Touchイベントがターゲットビューに到達すると、ビューはイベントを処理し、それを前のレイアウト/アクティビティに渡すかどうかを決定できます(falseメソッドでtrueonTouchで返す)。 (上の画像のRTLフロー)

View のソースコードを見て、タッチイベントフローをより深く理解しましょう。 dispatchTouchEvent の実装を見ると、OnTouchListenerをビューに設定して、trueonTouchメソッドで返すと、 onTouchEvent ofビューは呼び出されません。

_public boolean dispatchTouchEvent(MotionEvent event) {
    // removed lines for conciseness...
    boolean result = false;    
    // removed lines for conciseness...
    if (onFilterTouchEventForSecurity(event)) {
        // removed lines for conciseness...
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) { // <== right here!
            result = true;
        }
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }
    // removed lines for conciseness...
    return result;
}
_

次に、イベントアクションが_MotionEvent.ACTION_UP_である onTouchEvent メソッドを見てください。ここでは、クリック実行アクションが発生していることがわかります。したがって、trueOnTouchListeneronTouchを返し、その結果、onTouchEventを呼び出さないと、OnClickListeneronClickが呼び出されなくなります。

onTouchEventを呼び出さないことには、別の問題があります。これは、押された状態に関連していて、質問で述べました。以下のコードブロックでわかるように、実行時に UnsetPressedState _(false)_を呼び出す setPressed のインスタンスがあります。 setPressed(false)を呼び出さない結果は、ビューが押された状態で動かなくなり、その描画可能な状態が変化しないことです。

_public boolean onTouchEvent(MotionEvent event) {
    // removed lines for conciseness...
    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                // removed lines for conciseness...
                if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                    // removed lines for conciseness...
                    if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                        // removed lines for conciseness...
                        if (!focusTaken) {
                            // Use a Runnable and post this rather than calling
                            // performClick directly. This lets other visual state
                            // of the view update before click actions start.
                            if (mPerformClick == null) {
                                mPerformClick = new PerformClick();
                            }
                            if (!post(mPerformClick)) {
                                performClickInternal();
                            }
                        }
                    }
                    if (mUnsetPressedState == null) {
                        mUnsetPressedState = new UnsetPressedState();
                    }
                    if (prepressed) {
                        postDelayed(mUnsetPressedState,
                                ViewConfiguration.getPressedStateDuration());
                    } else if (!post(mUnsetPressedState)) {
                        // If the post failed, unpress right now
                        mUnsetPressedState.run();
                    }
                    // removed lines for conciseness...
                }
                // removed lines for conciseness...
                break;
            // removed lines for conciseness...
        }
        return true;
    }
    return false;
}
_

nsetPressedState

_private final class UnsetPressedState implements Runnable {
    @Override
    public void run() {
        setPressed(false);
    }
}
_


上記の説明については、setPressed(false)を自分で呼び出して、イベントアクションが_MotionEvent.ACTION_UP_である描画可能状態を変更することにより、コードを変更できます。

_button.setOnTouchListener(new View.OnTouchListener() {

    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                v.getBackground().setColorFilter(0xe0f47521,PorterDuff.Mode.SRC_ATOP);
                v.invalidate();
                break;
            }
            case MotionEvent.ACTION_UP: {
                v.getBackground().clearColorFilter();
                // v.invalidate();
                v.setPressed(false);
                v.performClick();
                Log.d("Test", "Performing click");
                return true;
            }
        }
        return false;
    }
});
_
10
aminography

touchおよびfocusイベントをめちゃくちゃにしています。同じ色で行動を理解することから始めましょう。デフォルトでは、AndroidではSelectorの背景としてButtonが割り当てられています。したがって、背景色を変更するだけで、makeは静的になります(色は変更されません)。しかし、それはネイティブの動作ではありません。

Selectorは次のようになります。

<?xml version="1.0" encoding="utf-8"?> 
  <selector xmlns:Android="http://schemas.Android.com/apk/res/Android">
    <item
        Android:state_focused="true"
        Android:state_pressed="true"
        Android:drawable="@drawable/bgalt" />

    <item
        Android:state_focused="false"
        Android:state_pressed="true"
        Android:drawable="@drawable/bgalt" />

    <item Android:drawable="@drawable/bgnorm" />
</selector>

上記のように、状態focusedと状態pressedがあります。 onTouchListenerを設定すると、focusとは関係のないタッチイベントを処理できます。

ボタンのSelectorは、ボタンのクリックイベント中にfocusイベントをtouchに置き換える必要があります。しかし、コードの最初の部分で、touchのイベントをインターセプトしました(コールバックからtrueを返します)。色の変更を続行できず、同じ色でフリーズしています。そして、それが2番目のバリアント(インターセプトなし)が正常に機能している理由です。

[〜#〜]更新[〜#〜]

Selectorの動作と色を変更するだけです。例のために。 Buttonの次の背景を使用する。 [〜#〜] and [〜#〜]実装からonTouchListenerを削除します。

<?xml version="1.0" encoding="utf-8"?> 
  <selector xmlns:Android="http://schemas.Android.com/apk/res/Android">
    <item
        Android:state_pressed="true"
        Android:drawable="@color/color_pressed" />

    <item Android:drawable="@color/color_normal" />
</selector>
2
GensaGames

ボタンビューの代わりにマテリアルチップを使用できます。参照: https://material.io/develop/Android/components/chip そこで、これらの強化されたイベントを処理し、テーマを適用してカスタマイズできます。

0

ボタンに背景を割り当てても、クリックしても色は変わりません。

 <color name="myColor">#000000</color>

ボタンの背景に設定します

Android:background="@color/myColor"
0
Haider Saleem