Кнопка слева в выделенном состоянии с 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

Может ли кто-нибудь это подтвердить?

Белый медведь
источник
Что вы на самом деле хотите сделать, это обработка прикосновением или изменение цвета при нажатии?
Хайдер Салим
Я хочу запустить некоторую логику и затем вызвать executeClick, чтобы цвет кнопки не менялся.
WhiteBear
@Whitebear Пожалуйста, проверьте ответ ниже. Может быть, я могу добавить больше информации.
GensaGames
Это может помочь вам понять поток сенсорных событий. Непонятно, что вы хотите, чтобы произошло. Вы хотите обработчик кликов и выполнить клик? Вы хотите, чтобы кнопка перешла из своего первоначального цвета в состояние, установленное цветным фильтром, а затем вернулась к своему первоначальному цвету?
Cheticamp
Позвольте мне объяснить, что я имею в виду. У меня есть TouchListener и ClickListener на кнопке. Касание предшествует щелчку по приоритету и возвращает true, если оно обработало событие, что означает, что никто другой не должен обрабатывать его. Это именно то, что я делаю с помощью касания, обработки нажатия и возврата true, но кнопка все еще остается подсвеченной, даже если вызывается прослушиватель onclick, и процесс выполняется правильно.
WhiteBear

Ответы:

10

Такие настройки не требуют программных изменений. Вы можете сделать это просто в xmlфайлах. Прежде всего, удалите setOnTouchListenerметод, который вы предоставляете в onCreateполностью. Затем определите цвет селектора в 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 на следующей картинке)

введите описание изображения здесь

Когда сенсорное событие достигает целевой вид, вид может обрабатывать то событие решило передать его предварительные раскладки / деятельности или нет (возвращающейся falseиз trueв onTouchметоде). (RTL поток на картинке выше)

Теперь давайте взглянем на исходный код View, чтобы глубже понять потоки сенсорных событий. Взглянув на реализацию dispatchTouchEvent, мы увидим, что если вы установите a 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. Мы видим, что там происходит действие «выполнить щелчок». Таким образом, возвращаясь trueв OnTouchListener" onTouchи " и, следовательно, не вызывая " onTouchEvent, вызывает не вызов OnClickListener" 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 проверяет, имеет ли оно значение null, перед установкой в ​​false, а также, что runnable не запускается, если мы предварительно сжаты. Я не совсем понимаю , как вы вычитать , что он должен быть установлен в ложь
WhiteBear
onClickНазывается потому , что вы звоните v.performClick();. Пожалуйста, проверьте приведенный выше код в MotionEvent.ACTION_UPразделе еще раз, setPressed(false)в любом случае mUnsetPressedStateвызывается , является ли он пустым или нет, prepressedявляется ли он истинным или нет. Разница заключается в способе вызова, setPressed(false)который может быть через post/ postDelayedили напрямую.
аминография
2

Вы бездельничаете 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 из обратного вызова). Изменение цвета не может продолжаться дальше и замерзает тем же цветом. И вот почему второй вариант (без перехвата) работает нормально, и это ваше замешательство.

ОБНОВИТЬ

Все, что вам нужно сделать, это изменить поведение и цвет для 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>
GensaGames
источник
Как бы вы изменили первый пример, чтобы он работал хорошо тогда?
WhiteBear
Я не пытаюсь удалить сенсорный слушатель из моей реализации. поскольку я хочу перехватывать щелчки в определенном представлении с помощью сенсорного обработчика, а затем обрабатывать их самостоятельно (с помощью executeClick) и возвращать истину из прослушивателя касания, чтобы сообщить другим обработчикам, что дальнейшая обработка не выполняется. В моем примере журналы печатаются нормально, но кнопка все еще остается выделенной.
WhiteBear
@Whitebear Вы не упомянули это в своих вопросах. В любом случае, вы можете использовать столько onTouchListenerсимволов, сколько захотите. Вам просто не нужно использовать событие, по return true.
GensaGames
@Whitebear ИЛИ Снимите селектор и установите необработанный цвет на кнопку с помощью backgroundColor.
GensaGames
Ребята, вы все равно не обращаясь то , что я написал в исходном сообщении ..
WhiteBear
0

если вы назначите фон кнопке, он не изменит цвет при нажатии.

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

и установите его в качестве фона для вашей кнопки

android:background="@color/myColor"
Хайдер Салим
источник
0

Вы можете просто использовать материал Chips вместо Button view. обратитесь: https://material.io/develop/android/components/chip, где они обрабатывают эти hililghted события, и вы можете настроить с применением тем.

Прабудда Фернандо
источник