Android视图处理滚动手势但忽略了触摸

dan*_*mal 2 android gesture

我有一个用例,屏幕上有两个视图,其中一个部分覆盖另一个.上面的那个需要处理滚动事件并忽略修饰.部分模糊的视图应该处理触摸事件,包括在模糊视图忽略的重叠区域中发生的事件.

下面是一个简化的示例布局.

简化示例布局

我最接近的是在顶视图上使用GestureDetectorCompat在onDown中返回true(否则我没有得到任何进一步的事件),在onScroll中返回true,在onSingleTapUp中返回false.我在后面的视图中尝试了几个具有相同结果的东西:我在未模糊的部分上轻拍,但顶视图吃掉了模糊部分的所有运动事件.

Dev*_*red 6

由于Android处理触摸事件流的方式,您想要做的事情并不像您希望的那样简单.所以让我先介绍一下上下文:

这是一个棘手的命题是因为Android将手势定义为ACTION_DOWN和相应的ACTION_UP之间的所有事件.ACTION_DOWN是框架搜索触摸目标的唯一点(这就是为什么你必须为该事件返回true才能看到任何其他事件).找到合适的目标后,该手势中的所有剩余事件将直接传递给该视图,而不是其他任何人.

这意味着如果您希望单个事件转到其他目的地,则必须自己捕获并重定向.所有触摸事件都从父视图流向一个长链中的子视图.父视图控制触摸事件从一个子节点移动到下一个子节点的时间和方式,包括修改坐标MotionEvent以匹配每个子视图的本地边界.因此,操作触摸事件的最有效位置是自定义ViewGroup父实现.

以下示例附带一大堆假设. 基本上,我假设两个观点都只是一个愚蠢的View,没有内部愿望处理触摸(这可能是错误的).将此代码应用于其他更复杂的子视图可能需要一些返工......但这应该可以帮助您入门.

强制触摸重定向的最佳位置是两个视图的共同父级,因为它是两者的触摸起源(如上所述).

public class TouchUpRedirectLayout extends FrameLayout implements View.OnTouchListener {

    private int mTargetViewId;
    private View mTargetView;
    private boolean mTargetTouchActive;

    private GestureDetector mGestureDetector;

    public TouchUpRedirectLayout(Context context) {
        super(context);
        init(context);
    }

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

    public TouchUpRedirectLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context);
    }

    private void init(Context context) {
        mGestureDetector = new GestureDetector(context, mGestureListener);
    }

    public void setTargetViewId(int resId) {
        mTargetViewId = resId;
        updateTargetView();
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        //Find the target view, if set, once inflated
        updateTargetView();
    }

    //Set the target view to handle gestures
    private void updateTargetView() {
        if (mTargetViewId > 0) {
            mTargetView = findViewById(mTargetViewId);
            if (mTargetView != null) {
                mTargetView.setOnTouchListener(this);
            }
        }
    }

    private Rect mHitRect = new Rect();
    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_UP:
                if (mTargetTouchActive) {
                    mTargetTouchActive = false;

                    //Validate the up
                    int index = indexOfChild(mTargetView) - 1;
                    if (index < 0) {
                        return false;
                    }

                    for (int i=index; i >= 0; i--) {
                        final View child = getChildAt(i);
                        child.getHitRect(mHitRect);
                        if (mHitRect.contains((int) event.getX(), (int) event.getY())) {
                            //Dispatch and mark handled
                            return child.dispatchTouchEvent(event);
                        }
                    }

                    //Steal this event
                    return true;
                }
                //Allow default processing
                return false;
            default:
                //Allow default processing
                return false;
        }
    }

    //Receive touch events from the target (scroll handling) view
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        mTargetTouchActive = true;
        return mGestureDetector.onTouchEvent(event);
    }

    //Handle gesture events in target view
    private GestureDetector.SimpleOnGestureListener mGestureListener = new GestureDetector.SimpleOnGestureListener() {
        @Override
        public boolean onDown(MotionEvent e) {
            Log.d("TAG", "onDown");
            return true;
        }

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            Log.d("TAG", "Scrolling...");
            return true;
        }
    };
}
Run Code Online (Sandbox Code Playgroud)

此示例布局(我已分类FrameLayout,但您可以选择当前使用的任何布局作为两个视图的父级)跟踪单个"目标"视图,以通知"向下"和"滚动"手势.当手势处于播放状态时,它还通知我们将包含我们需要捕获并转发到另一个模糊视图的ACTION_UP事件.

当up事件发生时,我们使用拦截功能ViewGroup将该事件从原始"目标"视图中引出,并将其分派给下一个可用于该事件的可用子视图.你也可以在这里轻松地对第二个"模糊"视图进行硬编码,但是我已经把它编写给了下面的任何和所有可能的孩子......类似于首先ViewGroup处理对孩子的委托方式.

这是一个示例布局:

<com.example.touchoverlaptest.app.TouchUpRedirectLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/view_root"
    android:layout_width="match_parent"
    android:layout_height="400dp"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context="com.example.touchoverlaptest.app.MainActivity">

    <View
        android:id="@+id/view_obscured"
        android:layout_width="match_parent"
        android:layout_height="250dp"
        android:background="#7A00" />
    <View
        android:id="@+id/view_overlap"
        android:layout_width="match_parent"
        android:layout_height="250dp"
        android:layout_gravity="bottom"
        android:background="#70A0" />

</com.example.touchoverlaptest.app.TouchUpRedirectLayout>
Run Code Online (Sandbox Code Playgroud)

...和视图在行动中的活动:

public class MainActivity extends Activity implements View.OnTouchListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        TouchUpRedirectLayout layout = (TouchUpRedirectLayout) findViewById(R.id.view_root);
        layout.setTargetViewId(R.id.view_overlap);

        layout.findViewById(R.id.view_obscured).setOnTouchListener(this);
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        Log.i("TAG", "Obscured touch "+event.getActionMasked());
        return true;
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {

        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
}
Run Code Online (Sandbox Code Playgroud)

目标视图将触发所有手势回调,并且模糊视图将接收up事件.将OnTouchListener在活动仅仅是为了验证该事件被传递.

如果您想了解有关Android中自定义触摸处理的更多详细信息,请参阅我最近就该主题所做的演示文稿的视频链接.