我有一个自定义 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/