Кнопка залишена у виділеному стані з 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;
            }
        });

    }
}

Що стосується коду, наведеного вище, то під час його використання я очікую, що натискання кнопки буде оброблене дотиком, а поверненням "справжнього" обробка повинна зупинитися на 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) ParentView

Чи може хтось це також підтвердити?


Що ви насправді хочете зробити, це обробка дотиком чи зміна кольору лише натисканням?
Хайдер

Я хочу запустити певну логіку на дотик, а потім зателефонувати performClick і не змінити колір кнопки.
Whitebear

@Whitebear Перевірте відповідь нижче. Можливо, я можу додати більше інформації.
GensaGames

Це може допомогти вам зрозуміти потік сенсорних подій. Не ясно, що ви хочете статися. Ви хочете обробляти кліки та виконувати клік? Ви хочете, щоб кнопка перейшла від початкового кольору до стану, встановленого кольоровим фільтром, а потім повернеться до початкового кольору?
Cheticamp

Дозвольте мені пояснити, що я маю на увазі. У мене на кнопці є touchListener і clickListener. Дотик передує клацанню за пріоритетом і повертає істину, якщо він обробляв подію, а це означає, що ніхто більше не повинен впоратися з цим. Це саме те, що я роблю за допомогою дотику, обробляючи клацання і повертаючи істину, але кнопка все ще залишається виділеною, навіть незважаючи на те, що викликається слухач 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 на наступному малюнку)

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

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

Тепер давайте подивимося на View «s вихідний код , щоб отримати більш глибоке розуміння дотиком потоків подій. Переглянувши реалізацію програми 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. Ми бачимо, що там виконується дія виконувати-клацати. Таким чином, повернення trueдо OnTouchListener's onTouchі, отже, не заклик onTouchEvent, викликає не дзвінок OnClickListener' s 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;
}

UnsetPressState :

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. mUnsetPressState перевіряє, чи він недійсний, перш ніж встановити значення false, а також запущений не впевнений, що він працює, якщо ми натиснуті. Я не зовсім розумію, як ви вважаєте, що це повинно бути встановлено на помилку
Whitebear

onClickНазивається тому , що ви дзвоните v.performClick();. Перевірте вищевказаний код у MotionEvent.ACTION_UPрозділі ще раз, setPressed(false)називається він все одно, чи mUnsetPressedStateнедійсним, чи prepressedправдивим чи ні. Різниця полягає в способі виклику, setPressed(false)який може бути через post/ postDelayedабо безпосередньо.
амінографія

2

Ви возитесь touchі focusподії. Почнемо з розуміння поведінки одного кольору. За замовчуванням в Android Selectorпризначений фоновий режим Button. Тому просто змінюючи колір фону, зробіть статичним (колір не зміниться). Але це не рідна поведінка.

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(повернення істинного з зворотного дзвінка). Зміна кольору не може продовжуватися і заморожується тим самим кольором. І тому другий варіант (без перехоплення) працює нормально, і це ваша плутанина.

ОНОВЛЕННЯ

Все, що вам потрібно зробити - це змінити поведінку та колір кольорів 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) і повернути істину від слухача дотику, щоб сказати іншим обробникам, що більше ніяких операцій не проводиться. На моєму прикладі журнали друкуються чудово, але кнопка все ще залишається виділеною.
Whitebear

@Whitebear Ви цього не згадували у своїх питаннях. У будь-якому випадку ви можете використовувати скільки onTouchListenerзавгодно s. Вам просто не потрібно споживати події, автор return true.
GensaGames

@Whitebear АБО Видаліть перемикач та встановіть неперероблений колір кнопкою за допомогою backgroundColor.
GensaGames

хлопці, ви все ще не звертаєтесь до того, про що я писав у оригінальній публікації ..
Whitebear

0

якщо призначити фон кнопці, вона не змінить колір при натисканні.

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

і встановіть його як backgrount на вашу кнопку

android:background="@color/myColor"

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.