拦截MotionEvents并阻止父级在ViewPager

时间:2015-08-31 17:18:14

标签: android android-viewpager ontouchevent motionevent

我将自定义子视图放入ViewPager。我没有任何拦截触摸事件的重要经验,所以这对我来说都是新鲜的。

我已经在SO和其他博客上查看过与此相关的大量问题,但到目前为止,没有一条建议的建议对我有太多帮助。

我的自定义视图有两个彼此重叠的面板。我需要允许用户滑动最前面的面板,而不将这些触摸事件传递给父ViewPager。

我很难理解这一点,因为尽管在我的视图onTouchEvent中适当地返回false,我的视图onInterceptTouchEvent似乎始终被调用{ {1}}。我的理解可能在这里有误,但我认为返回false应该意味着不应该调用我的观点onTouchEvent

以下是我的自定义视图的代码:

package com.example.dm78.viewpagertouchexample;

import android.content.Context;
import android.support.v4.view.GestureDetectorCompat;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;

public class MyCustomView extends FrameLayout {

    public static final String TAG = MyCustomView.class.getSimpleName();

    private GestureDetectorCompat mGestureDetector;
    private PanelData mPanelData;
    private LinearLayout mFrontPanel;
    private float xAdditive;
    private float mFrontPanelInitialX = Float.NaN;
    private float mDownX = Float.NaN;
    private float frontPanelXSwipeThreshold = 100;  // arbitrary value

    public MyCustomView(Context context, PanelData holder) {
        super(context);
        mPanelData = holder;
        init();
    }

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

    private void init() {
        mGestureDetector = new GestureDetectorCompat(getContext(), new GestureDetector.OnGestureListener() {
            @Override
            public boolean onDown(MotionEvent e) {
                return false;
            }

            @Override
            public void onShowPress(MotionEvent e) {

            }

            @Override
            public boolean onSingleTapUp(MotionEvent e) {
                return false;
            }

            @Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
                return false;
            }

            @Override
            public void onLongPress(MotionEvent e) {

            }

            @Override
            public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
                return false;
            }
        });

        View view = View.inflate(getContext(), R.layout.my_custom_view, this);
        mFrontPanel = (LinearLayout) view.findViewById(R.id.front_panel);
        FrameLayout mRearPanel = (FrameLayout) findViewById(R.id.rear_panel);
        TextView mFrontTextView = (TextView) findViewById(R.id.front_textView);
        TextView mRearTextView = (TextView) findViewById(R.id.rear_textView);

        mFrontTextView.setText(mPanelData.frontText);
        mRearTextView.setText(mPanelData.rearText);

        if (mPanelData.showFrontPanel) {
            mFrontPanel.setVisibility(View.VISIBLE);
        }
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        mFrontPanelInitialX = mFrontPanel.getX();
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        return super.dispatchTouchEvent(ev) || mGestureDetector.onTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (mPanelData.showFrontPanel) {
            getParent().requestDisallowInterceptTouchEvent(true);
            return true;
        }
        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        float dX;
        long duration;
        boolean result = false;
        int xDir = 0;

        if (mDownX != Float.NaN) {
            xDir = (int) Math.abs(event.getRawX() - mDownX);
        }

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // for later ACTION_MOVE events, we'll add xAdditive to event.getRawX() to get a dX for animations
                xAdditive = mFrontPanel.getX() - event.getRawX();
                mDownX = event.getRawX();
                result = true;
                break;
            case MotionEvent.ACTION_MOVE:
                // left movement detected
                if (xDir < 0) {
                    // animate panel interactively with new events coming in

                    // assume we haven't passed the point of no return
                    dX = event.getRawX() + xAdditive;
                    duration = 0;

                    if ((mFrontPanel.getX() + dX) < frontPanelXSwipeThreshold) {
                        // go ahead and animate the panel away
                        dX = -mFrontPanel.getWidth();
                        duration = 200;
                    }

                    mFrontPanel.animate().x(dX).setDuration(duration).start();
                    result = true;
                }
                break;
            case MotionEvent.ACTION_UP:
                // test value here is arbitrary
                if (xDir < -10) {
                    int newX;
                    // if panel has been moved left enough, just animate it away
                    if (mFrontPanel.getX() < frontPanelXSwipeThreshold) {
                        newX = -mFrontPanel.getWidth();
                    }
                    // otherwise, animate return to initial position
                    else {
                        newX = -getContext().getResources().getDisplayMetrics().widthPixels;
                    }

                    mFrontPanel.animate().x(newX).setDuration(200).start();
                    result = true;
                    getParent().requestDisallowInterceptTouchEvent(false);
                }
                break;
            default:
                result = super.onTouchEvent(event) || mGestureDetector.onTouchEvent(event);
        }

        return result;
    }

    public static class PanelData {
        public String rearText;
        public String frontText;
        public boolean showFrontPanel;
    }
}

目前,在ViewPager中通常不会发生任何事情。我的孩子视图完全忽略了触摸输入。我确定我在这里做了一些愚蠢的事情,但是我对Android SDK的这一部分没有足够的经验来了解它是什么。

我已经在某个博客的建议中抛出GestureDetector.OnGestureListener,但我还没有找到它的用处,也不知道它对我的启动有何帮助

你能找到我的代码明显错误的东西吗?我甚至走在正确的轨道上吗?

更新2015-09-01: 我发现ViewParent#requestDisallowInterceptTouchEvent(boolean)方法可能应该在我看来的父母的几个点上调用。似乎onInterceptTouchEvent可能是设置标志的好地方,onTouchEventACTION_UP的情况下可能是取消它的好地方。这显然是不正确的,因为当我滚动到具有用户需要轻扫的前面板的页面时,ViewPager完全停止滚动,并且应用程序没有用户可见的响应。我怎样才能做到这一点?

工作示例app repo:https://gitlab.com/dm78/ViewPagerTouchExample/tree/master

3 个答案:

答案 0 :(得分:2)

我的代码存在许多问题。

  • 在确定某个活动的动作方向时,我忘了删除对Math.abs()的来电。
  • 在决定如何设置面板动画时,我需要调整一些我要检查的值。
  • 我需要在一些地方为我的观看者的父母致电requestDisallowInterceptTouchEvent(boolean),以启用/禁用家长吞下我视图中生成的所有事件。

在我的视图中使用requestDisallowInterceptTouchEvent(boolean),这允许孩子处理事件而不是将视图紧密耦合到显示它的ViewPager,我认为这是更好的选择,因为这种技术可以应用于(据我所知)任何UI上下文中的任何视图,以获得所需的行为。它不依赖视图的其他元素,了解正在发生的事情的细节,以使解决方案正常运行。

解决上述问题后,只需调整动画如何触发即可实现所需的互动。

我的解决方案仍有一个小问题,但这是另一个问题。

答案 1 :(得分:0)

来自onInterceptTouchEvent(MotionEvent ev)

的文档
  

返回true以从子节点窃取运动事件,并通过onTouchEvent()将它们分派到此ViewGroup。当前目标将收到ACTION_CANCEL事件,此处不会再传递任何消息。

因此,当子视图不应该获取触摸事件时,您希望返回true。默认情况下,MotionEvents从布局堆栈的根目录“传递”到其子视图,直到其中一个没有任何子节点或拦截此事件。

据我了解,您希望在该寻呼机内的某些子视图上执行滑动手势时暂时禁用ViewPager的分页,因此寻呼机不得截取该事件,也不能消耗触摸事件(必须返回false)来自onTouchEvent(MotionEvent ev))。我遇到了类似的问题并this answer帮助了,这可能是解决问题的方法。

答案 2 :(得分:0)

默认情况下,父级在android中的子级之前接收触摸事件。因此,当检测到触摸时,将调用视图寻呼机的onInterceptTouchEvent。

因此,要禁用ViewPager上的触摸,请继承ViewPager,并从onInterceptTouchEvent返回false。例如

public class MyViewPager extends ViewPager {
 //constructors n rest of the code here...

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
       //custom logic here...
       return false;
    }
}

是的,你是对的,返回false将阻止为该特定视图组调用onTouchEvent。

我希望这很有帮助。干杯:)