拦截 MotionEvents 并阻止父级在 ViewPager 中处理它们

dm7*_*m78 2 android touch-event android-viewpager motionevent

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

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

我的自定义视图有两个相互重叠的面板。我需要允许用户在不将这些触摸事件传递给父 ViewPager 的情况下滑动最前面的面板。

我很难理解这一点,因为似乎我的观点onTouchEvent总是被调用,尽管false在我观点的onInterceptTouchEvent. 我的理解在这里可能是错误的,但我的印象是返回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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

I've tossed a GestureDetector.OnGestureListener in on the suggestion of some blog, but I haven't found much use for it and don't know how it might help me to begin with.

Can you find something obviously wrong with my code? Am I even on the right track?

Update 2015-09-01: I've discovered that ViewParent#requestDisallowInterceptTouchEvent(boolean) method should probably be called on my view's parent at a couple of points. It seems like perhaps onInterceptTouchEvent might be a good place to set the flag and in onTouchEvent in the case for ACTION_UP might be a good place to cancel it. This is obviously not correct, since when I scroll to a page that has a front panel that the user needs to swipe away, the ViewPager stops scrolling entirely and there is no user-visible response from the app. How can I make this work?

Working example app repo: https://gitlab.com/dm78/ViewPagerTouchExample/tree/master

dm7*_*m78 5

我的代码有很多问题。

  • Math.abs()在确定事件的运动方向时,我忘记删除调用。
  • 在决定如何为面板设置动画时,我需要调整一些我正在检查的值。
  • 我需要requestDisallowInterceptTouchEvent(boolean)在几个地方调用我的视图的父级来启用/禁用父级吞噬我的视图上生成的所有事件。

requestDisallowInterceptTouchEvent(boolean)在我看来,通过使用,这允许孩子处理事件,而不是将视图与ViewPager显示它的视图紧密耦合,我觉得这是更可取的,因为这种技术可以应用于(据我想象)大约任何 UI 上下文中任何位置的任何视图以获得所需的行为。它不依赖于视图的其他元素了解正在发生的事情的亲密细节,以便解决方案正常运行。

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

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