touchListenerとclickListenerを使用してボタンを強調表示したままにします


10

次の操作を行った後、ボタンが強調表示されたままの状態で問題が発生しました。

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

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


あなたが実際にしたいことは、それをタッチで処理するか、押すだけで色を変えることですか?
ハイダーサリーム

タッチでいくつかのロジックを実行してから、performClickを呼び出し、ボタンの色を変更しないようにしたいと思います。
Whitebear

@Whitebear以下の答えを確認してください。多分私はもっと情報を追加することができます。
GensaGames

これは、タッチイベントの流れを理解するのに役立ちます。あなたが何をしたいのか明確ではありません。クリックハンドラーが必要ですか、実行クリックを実行しますか?ボタンを最初の色からカラーフィルターによって設定された状態にしてから、最初の色に戻しますか?
Cheticamp

どういう意味か説明させてください。ボタンにtouchListenerとclickListenerがあります。タッチは優先度でクリックに先行し、イベントを処理した場合はtrueを返します。つまり、他の誰もそれを処理してはなりません。これは、私がタッチを使って行っていることであり、クリックを処理してtrueを返しますが、onclickリスナーが呼び出されてフローが正しく実行されても、ボタンは引き続き強調表示されたままです。
Whitebear

回答:


10

このようなカスタマイズには、プログラムによる変更は必要ありません。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" />


視覚的な結果:

ここに画像の説明を入力してください



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

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

ここに画像の説明を入力してください

タッチイベントは、ターゲットビューに到達すると、ビューは、従来のレイアウト/活性またはそうでないと(復帰に渡すことを決定そのイベントを扱うことができるfalsetrueonTouch方法)。(上の画像のRTLフロー)

ここで、Viewのソースコードを見て、タッチイベントフローについてより深い洞察を得ましょう。の実装をdispatchTouchEvent確認するOnTouchListenerと、ビューにを設定してからtrueそのonTouchメソッドに戻るonTouchEventと、ビューのが呼び出されないことがわかります。

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;
}

次に、onTouchEventイベントアクションがであるメソッドを見てくださいMotionEvent.ACTION_UP。ここで、クリック実行アクションが発生していることがわかります。だから、帰国trueOnTouchListeneronTouch、結果として呼び出していないonTouchEvent、呼び出していない原因OnClickListeners 'をonClick

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

UnsetPressedState

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;
    }
});

これは私が友達を探しているものではありません。上記の両方の状況での行動の変化を理解したいと思っています。詳しく説明できることがあれば、お知らせください。タッチハンドラーやクリックハンドラーを削除したくありません。上記の答えに対する私のコメントをチェックしてください。
Whitebear

@Whitebear:答えを更新しました。是非チェックしてください。
アミノグラフィー

それは良い詳細な答えであり、私はそれを受け入れます。変更のポイント: Now, look at the onTouchEvent method where the event action is MotionEvent.ACTION_UP. We see that perform-click action happens there. So, returning true in the OnTouchListener's onTouch and consequently not calling the onTouchEvent, causes not calling the OnClickListener's onClick.私の場合、onClickが呼び出されます。mUnsetPressedStateは、falseに設定する前にnullかどうかをチェックします。また、プリプレスされている場合、ランナブルは確実に実行されません。私はかなりあなたがそれをfalseに設定する必要があることを控除する方法を理解していない
Whitebear

onClickあなたが呼び出しているので、と呼ばれていますv.performClick();。上記のコードをMotionEvent.ACTION_UPセクションでもう一度確認してくださいsetPressed(false)。nullかどうかにかかわらmUnsetPressedStateず、prepressedtrue かどうかにかかわらず呼び出されます。違いは、呼び出しの方法であるsetPressed(false)ことが通過することができpost/ postDelayedまたは直接。
アミノグラフィー

2

あなたはめちゃくちゃtouchfocusイベントです。同じ色の行動を理解することから始めましょう。デフォルトでSelectorButton、Androidの背景としてが割り当てられています。したがって、背景色を変更するだけで、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ます。

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

更新

必要なのは、の動作と色を変更することだけですSelector。例のために。の次の背景を使用するButtonまた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>

では、最初の例をどのように変更して、うまく機能するでしょうか?
Whitebear

実装からタッチリスナーを削除するつもりはありません。タッチハンドラーを使用して特定のビューでクリックをキャッチし、それを(performClickを使用して)自分で処理し、タッチリスナーからtrueを返し、他のハンドラーにそれ以上の処理が行われないことを通知するためです。私の例ではログは正常に印刷されていますが、ボタンはまだ強調表示されたままです。
Whitebear

@Whitebearあなたは質問の中でそれについて言及しませんでした。とにかく、必要なだけを使用できますonTouchListener。あなただけで、イベントを消費する必要はありませんreturn true
GensaGames

@Whitebear またはセレクターを削除し、を介してボタンに生の色を設定しますbackgroundColor
GensaGames

人は、あなたはまだ、私は元の記事に書いたもののアドレッシングされていません...
Whitebear

0

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

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

ボタンの背景として設定します

android:background="@color/myColor"

0

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.