Понимание того, почему onClick () вызывается даже после того, как onDispatchTouchEvent () возвращает True

Допустим, в приложении для Android мы хотим иметь возможность временно и надежно игнорировать все прикосновения пользователя в любой момент.

Из проведенного мной исследования переполнения стека, а также здесь, здесь и здесь, согласованное решение выглядит примерно так:

(Код MainActivity.java):

// returning true should mean touches are ignored/blocked
@Override
public boolean dispatchTouchEvent(MotionEvent pEvent) {

    if (disableTouches) {
        return true;
    } else {
        return super.dispatchTouchEvent(pEvent);
    }

}

Однако, когда мы вводим Инструмент для упражнений Android Monkey и отправляем события касания в приложение с большой скоростью, становится очевидным, что свиньи начинают летать на квантовом уровне - мы можем получать звонки на onClick() даже после / во время, когда «blockTouches» был установлено значение true.

МОЙ ВОПРОС: Почему? - Это нормальное поведение Android или я допустил ошибку в коде? :)

Примечание: Я уже исключил возможность того, что onClick() будет вызван пользовательским вводом, отличным от прикосновений (и, следовательно, не будет контролироваться методом onDispatchTouchEvent()) ... путем добавления «—-pct-touch 100» к команде обезьяны.

Вот код, который я использую для этого теста:

Основная деятельность:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    View rootView; // turns black when "touch rejection" is in progress

    View allowedButton;
    View notAllowedButton;

    // Used to decide whether to process touch events.
    // Set true temporarily when notAllowedButton is clicked.
    boolean touchRejectionAnimationInProgress = false;

    int errorCount = 0; // counting "unexpected/impossible" click calls

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        rootView = findViewById(R.id.rootView);

        allowedButton = findViewById(R.id.allowedButton);
        notAllowedButton = findViewById(R.id.notAllowedButton);

        allowedButton.setOnClickListener(this);
        notAllowedButton.setOnClickListener(this);

        allowedButton.setBackgroundColor(Color.GREEN);
        notAllowedButton.setBackgroundColor(Color.RED);

    }

    // returning true should mean touches are ignored/blocked
    @Override
    public boolean dispatchTouchEvent(MotionEvent pEvent) {

        if (touchRejectionAnimationInProgress) {
            Log.i("XXX", "touch rejected in dispatchTouchevent()");
            return true;
        } else {
            return super.dispatchTouchEvent(pEvent);
        }

    }

    @Override
    public void onClick(View viewThatWasClicked){

        Log.i("XXX", "onClick() called.  View clicked: " + viewThatWasClicked.getTag());

        //checking for unexpected/"impossible"(?) calls to this method
        if (touchRejectionAnimationInProgress) {
            Log.i("XXX!", "IMPOSSIBLE(?) call to onClick() detected.");
            errorCount ++;
            Log.i("XXX!", "Number of unexpected clicks: " + errorCount);
            return;
        } // else proceed...

        if (viewThatWasClicked == allowedButton) {
            // Irrelevant
        } else if (viewThatWasClicked == notAllowedButton) {
            // user did something that is not allowed.
            touchRejectionAnimation();
        }

    }

    // When the user clicks on something "illegal,"
    // all user input is ignored temporarily for 200 ms.
    // (arbitrary choice of duration, but smaller is better for testing)
    private void touchRejectionAnimation() {

        Log.i("XXX", "touchRejectionAnimation() called.");

        touchRejectionAnimationInProgress = true;
        rootView.setBackgroundColor(Color.BLACK);

        // for logging/debugging purposes...
        final String rejectionID = (new Random().nextInt() % 9999999) + "";
        Log.i("XXX", "rejection : " + rejectionID + " started.");

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                try { Thread.sleep(200); } catch (Exception e) {
                    Log.e("XXX", "exception in touchRejection() BG thread!");
                }
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Log.i("XXX", "rejection " + rejectionID + " ending");
                        rootView.setBackgroundColor(Color.WHITE);
                        touchRejectionAnimationInProgress = false;
                    }
                });
            }
        });

        thread.start();

    }

}

layout.xml:

<?xml version = "1.0" encoding = "utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android = "http://schemas.android.com/apk/res/android"
    xmlns:app = "http://schemas.android.com/apk/res-auto"
    xmlns:tools = "http://schemas.android.com/tools"
    android:id = "@+id/rootView"
    android:layout_width = "match_parent"
    android:layout_height = "match_parent"
    tools:context = ".MainActivity">

    <View
        android:id = "@+id/allowedButton"
        android:layout_width = "0dp"
        android:layout_height = "0dp"
        android:layout_marginTop = "32dp"
        android:layout_marginBottom = "32dp"
        android:tag = "allowedButton"
        app:layout_constraintBottom_toBottomOf = "parent"
        app:layout_constraintEnd_toStartOf = "@+id/notAllowedButton"
        app:layout_constraintStart_toStartOf = "parent"
        app:layout_constraintTop_toTopOf = "parent" />

    <View
        android:id = "@+id/notAllowedButton"
        android:layout_width = "0dp"
        android:layout_height = "0dp"
        android:layout_marginTop = "32dp"
        android:layout_marginBottom = "32dp"
        android:tag = "view2"
        app:layout_constraintBottom_toBottomOf = "parent"
        app:layout_constraintEnd_toEndOf = "parent"
        app:layout_constraintStart_toEndOf = "@+id/allowedButton"
        app:layout_constraintTop_toTopOf = "parent" />

</android.support.constraint.ConstraintLayout>

Вы уверены, что ваш сценарий обезьяны не нажимает на allowedButton и notAllowedButton? Можете ли вы полностью отключить их и попробовать еще раз?

BLuFeNiX 03.11.2018 15:25

Он может щелкнуть и действительно щелкает по обоим из них по дизайну.

Nerdy Bunz 03.11.2018 23:22
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
4
2
255
1

Ответы 1

Если вы не хотите, чтобы ваш onClick () запускался при любом просмотре, щелкните.

Ниже приведены шаги, о которых необходимо позаботиться.

Create custom viewGroup eg: MyConstraintLayout and add all child inside it.

Override onInterceptTouchEvent(MotionEvent ev) and return it has true.

public class MyConstraintLayout extends ConstraintLayout {

    private boolean mIsViewsTouchable;

    public ParentView(Context context) {
        super(context);
    }

    public ParentView(Context context, AttributeSet attrs) {
        super(context, attrs);
        inflate(context, R.layout.custom_view, this);
    }

    public ParentView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public void setViewsTouchable(boolean isViewTouchable) {
        mIsViewsTouchable = isViewTouchable;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return mIsViewsTouchable;
    }
 }

Примечание: используйте метод setViewsTouchable () в соответствии с вашими требованиями. Если вы передадите параметр как true, все представления не будут кликабельными, если false, ваши представления будут кликабельными.

Я не понимаю, почему это сработает, поскольку MainActivity.onDispatchTouchEvent () уже возвращает true в моем примере и существует вверх по течению (происходит раньше, чем) ViewGroup.onInterceptTouchEvent ... но я все равно попытался протестировать этот подход, который вы описываете ... но столкнулся с проблемой: я не могу понять, как адаптировать "inflate (context, R.layout.custom_view, this)" к моему варианту использования, где настраиваемый ConstraintLayout (MyConstraintLayout) будет корневым представлением activity_main .xml.

Nerdy Bunz 29.10.2018 11:47

Другие вопросы по теме