5

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

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

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

有谁能够帮助我?

我的视图很容易测试它:

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

如果您在红色画布内触摸并滚动,则 ScrollableLayout 不应滚动

4

2 回答 2

14

为了防止该布局的触摸,您有几个选项:

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

2) 覆盖 dispatchTouchEvent(MotionEvent event) 以始终返回 true

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

首先我会告诉你是如何dispatchTouchEvent工作的。它开始将事件从根视图分派到它的子视图,这意味着dispatchTouchEvent父视图在其子视图之前 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标志为真,则此视图将拦截所有后续事件。并在ScrollView onInterceptTouchEvent执行垂直滚动时拦截动作。

所以我的第一个想法是设置FLAG_DISALLOW_INTERCEPT为您的自定义视图的父级,以禁止这种拦截事件的机会,但是由于以下代码行,这个想法失败了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);
    }
}

这将FLAG_DISALLOW_INTERCEPT在 first 之后立即引发MotionEvent.ACTION_DOWN,这将不允许父母进一步拦截未来的行动。

但请注意,如果您的父母认为在做一些疯狂的事情MotionEvent.ACTION_DOWN并拦截此事件,那么您将无能为力,因为您的父母dispatchTouchEvent在您的dispatchTouchEvent.

于 2013-07-24T15:03:57.250 回答
1

你必须覆盖父母的 onInterceptTouchEvent 方法

于 2013-07-24T15:02:00.013 回答