如何防止TouchEvent滚动

时间:2013-07-24 14:57:32

标签: android

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

我需要来自JavaScript的event.preventDefaults()

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

有人能帮助我吗?

My 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

如果触摸并滚动红色画布,ScrollableLayout应该滚动。

2 个答案:

答案 0 :(得分:12)

为了防止来自该布局的触摸,您有以下几种选择:

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

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

UPD: Okey,通常你必须设置OnTouchListener,它总是会返回true,正如我之前所说的那样,为了防止其他视图被调用onTouch,但是使用ScrollView它完全是另一个故事。

首先,我将告诉dispatchTouchEvent如何运作。它开始从根视图调度事件到它的子视图,这意味着父视图的dispatchTouchEvent被称为 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标志为真,则此视图将拦截所有后续事件。并在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之后立即引发MotionEvent.ACTION_DOWN,这将阻止父母进一步拦截未来的行动。

但是请注意,如果您的父视图在MotionEvent.ACTION_DOWN上做了一些疯狂的事情并拦截了这个事件,那么您就无法对此做任何事情,因为您父母的dispatchTouchEvent会在dispatchTouchEvent之前被调用1}}。

答案 1 :(得分:1)

你必须覆盖父的onInterceptTouchEvent方法