android - 如何防止 TouchEvent 滚动

标签 android

我有一个自定义 View ,其中我具有触摸事件功能(滑动等)。现在可能会发生这种情况,即在 ScrollableLayout 中使用此自定义 View 。那么问题是,当用户在我的自定义 View 内滑动时,父级 (ScrollableLayout) 也会处理滑动手势,因此它会滚动,但它不应该滚动。

我需要来自 JavaScript 的类似 event.preventDefaults() 的东西。

我覆盖 View#onTouchEvent 并始终返回 true。 我想,当我从 onTouchEvent 返回 true 时,这意味着该事件已被消耗并且没有其他 View 将获得 onTouchEvent,但这是错误的。

谁能帮帮我?

我的 View 很容易测试:

public class PreventingTouchEventView extends View {

    public PreventingTouchEventView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawColor(Color.RED);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(200, 200);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return true;
    }
}

我将一个示例android项目推送到了github: https://github.com/jjoe64/android-preventing-touch-test

如果您在红色 Canvas 内触摸和滚动,ScrollableLayout 应该滚动。

最佳答案

要防止触摸该布局,您有多种选择:

1) 设置 OnTouchListener,它总是返回 true。

2) 覆盖 dispatchTouchEvent(MotionEvent event) 总是返回 true

UPD: 好吧,通常你必须设置 OnTouchListener,它总是返回 true,正如我之前所说,以防止其他 View 被调用 onTouch,但是ScrollView 完全是另一个故事。

首先,我将介绍 dispatchTouchEvent 的工作原理。它开始从 Root View 向其 subview 分派(dispatch)事件,这意味着父 View 的 dispatchTouchEvent 被其 subview 的 BEFORE dispatchTouchEvent 调用。

然后在ViewGroup.dispatchTouchEvent()里面有一段代码

final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
    intercepted = onInterceptTouchEvent(ev);
    ev.setAction(action); // restore action in case it was changed
} else {
    intercepted = false;
}

决定是否应该拦截此触摸事件,这意味着如果 intercepted 标志为真,则此 View 将拦截所有后续事件。并在 ScrollView onInterceptTouchEvent 中实现拦截正在执行垂直滚动的操作。

所以我的第一个想法是将 FLAG_DISALLOW_INTERCEPT 设置为自定义 View 的父级,以禁止拦截事件的机会,但由于 ViewGroup< 中的下一行代码,这个想法失败了:

if (actionMasked == MotionEvent.ACTION_DOWN) {
     // Throw away all previous state when starting a new touch gesture.
     // The framework may have dropped the up or cancel event for the previous gesture
    // due to an app switch, ANR, or some other state change.
    cancelAndClearTouchTargets(ev);
    resetTouchState();
}

这意味着在 MotionEvent.ACTION_DOWN 之后所有状态都被清除(FLAG_DISALLOW_INTERCEPT 也被清除)。

所以最终的解决方案很简单

public class PreventingTouchEventView extends View {

    public PreventingTouchEventView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawColor(Color.RED);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        if (getParent() != null && event.getAction() == MotionEvent.ACTION_DOWN) {
            getParent().requestDisallowInterceptTouchEvent(true);
        }
        return super.dispatchTouchEvent(event);
    }
}

这会在第一个 MotionEvent.ACTION_DOWN 之后立即引发 FLAG_DISALLOW_INTERCEPT,这将禁止父级进一步拦截 future 的操作。

但请注意,如果您的 parent 认为在 MotionEvent.ACTION_DOWN 上做了一些疯狂的事情并拦截了此事件,那么您对此无能为力,因为您 parent 的 dispatchTouchEvent 在您的 dispatchTouchEvent 之前被调用。

关于android - 如何防止 TouchEvent 滚动,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17837711/

相关文章:

android - listView 下方的按钮

android - bindService 在单击按钮时不起作用,但在 onCreate 中起作用 - android

java - 在 MVVM 中显示对话框、布局和数据过滤

android - 带 fragment 的垂直 ViewPager

android - 嵌套的 Viewpager fragment 未初始化

java - 如何从 android 中的 arraylist 获取 null

android - 在获得两个或更多修饰符的 Jetpack compose 组件中,应该如何命名修饰符参数?

android - Xtype 文本字段上的 Sencha 触摸键盘下一个按钮

Android TelephonyManager 不适用于 android studio 模拟器?

java - 将应用程序范围的 boolean 值保存在 SharedPreference 中