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

时间:2014-04-01 21:49:23

标签: android gesture

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

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

simplified example layout

最接近的我会在顶部视图中使用GestureDetectorCompat在onDown中返回true(否则我不会再获取任何其他事件)在onScroll中返回true,在onSingleTapUp中返回false。我已经在视图中尝试了几个具有相同结果的东西:我在未遮挡的部分上轻拍,但顶视图会遮挡模糊部分的所有运动事件。

1 个答案:

答案 0 :(得分: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;
        }
    };
}

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

当发生up事件时,我们使用ViewGroup的拦截功能来指示该事件远离原始&#34;目标&#34;查看,并将其发送到其边界适合事件的下一个可用子视图。你可以很容易地硬编码第二个&#34;模糊&#34;我也在这里查看,但是我已经把它写到了下面的任何和所有可能的孩子身上......类似于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>

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

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;
    }
}

目标视图将触发所有手势回调,并且模糊视图将接收up事件。活动中的OnTouchListener只是为了验证事件的传递。

如果您想了解有关Android中自定义触控处理的更多详细信息,here is a video link我最近就该主题所做的演示文稿。