java - 了解为什么即使在 onDispatchTouchEvent() 返回 True 后仍会调用 onClick()

标签 java android user-interface onclick ontouch

比方说,在 Android 应用中,我们希望能够在任何时候暂时可靠地忽略所有用户触摸。

根据我对堆栈溢出所做的研究以及 here , here , 和 here ,商定的解决方案似乎是这样的:

(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 Exerciser Tool并快速将触摸事件发送到应用程序,很明显 pig 开始在量子级别飞翔——我们可以调用 onClick() 甚至在“blockTouches”之后/期间已设置为真。

我的问题是:这是为什么? -- 这是正常的 Android 行为,还是我的代码有误? :)

注意:我已经排除了 onClick() 被用户输入而不是触摸调用的可能性(因此不受 onDispatchTouchEvent() 控制) > 方法)...通过在 monkey 命令中添加“—-pct-touch 100”。

这是我用于此测试的代码:

主要 Activity :

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

    }

}

布局.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>

最佳答案

如果您不希望在任何 View 点击时触发您的 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,则所有 View 都不可点击,如果为 false,您的 View 将可点击。

关于java - 了解为什么即使在 onDispatchTouchEvent() 返回 True 后仍会调用 onClick(),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52983232/

相关文章:

Java:让其他线程知道 GUI 是否可以使用

java - 为什么 contains() 方法在 Java 中的非空字符串中找到空字符串

java - 动态更改 BLE 广告数据 Android

javascript - 哪个 Javascript 库鼓励漂亮的 UI 模式并且适合 Silverlight/WPF 开发人员?

java - 表格布局 : Text in the last column is not getting displayed completly

java - 遗传算法的线程池内存不足 - 为什么?

Android - 更新支持库 com.android.support :cardview-v7 to version 27. 0.2 未知属性问题

java - 在 Android 中加载/保存属性文件最方便的方法是什么?

java - 是否有标准方法可以在 Android 上完成本地化日期组件,例如 1s、1m、1h、1d、1w、1week 等

flutter 。如何在列内创建树形图结构