滑动冲突外部拦截法demo分析

        最近在学习ViewGroup事件分发,针对滑动冲突掌握的不好。所以通过分析一个demo来加深自己的印象。

        先贴代码,主要包括Activity左右滑动容器ViewGroup上下滑动ViewGroup

public class DemoActivity_1 extends Activity {
    private static final String TAG = "DemoActivity_1";

    private HorizontalScrollViewEx mListContainer;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.demo_1);
        Log.d(TAG, "onCreate");
        initView();
    }

    private void initView() {
        LayoutInflater inflater = getLayoutInflater();
        mListContainer = (HorizontalScrollViewEx) findViewById(R.id.container);
        final int screenWidth = MyUtils.getScreenMetrics(this).widthPixels;
        final int screenHeight = MyUtils.getScreenMetrics(this).heightPixels;
        for (int i = 0; i < 3; i++) {
            ViewGroup layout = (ViewGroup) inflater.inflate(
                    R.layout.content_layout, mListContainer, false);
            layout.getLayoutParams().width = screenWidth;
            TextView textView = (TextView) layout.findViewById(R.id.title);
            textView.setText("page " + (i + 1));
            layout.setBackgroundColor(Color.rgb(255 / (i + 1), 255 / (i + 1), 0));
            createList(layout);
            mListContainer.addView(layout);
        }
    }

    private void createList(ViewGroup layout) {
        ListView listView = (ListView) layout.findViewById(R.id.list);
        ArrayList<String> datas = new ArrayList<String>();
        for (int i = 0; i < 50; i++) {
            datas.add("name " + i);
        }

        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
                R.layout.content_list_item, R.id.name, datas);
        listView.setAdapter(adapter);
        listView.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view,
                                    int position, long id) {
                Toast.makeText(DemoActivity_1.this, "click item",
                        Toast.LENGTH_SHORT).show();

            }
        });
    }
}
public class HorizontalScrollViewEx extends ViewGroup {
    private static final String TAG = "HorizontalScrollViewEx";

    private int mChildrenSize;
    private int mChildWidth;
    private int mChildIndex;

    // 分别记录上次滑动的坐标
    private int mLastX = 0;
    private int mLastY = 0;
    // 分别记录上次滑动的坐标(onInterceptTouchEvent)
    private int mLastXIntercept = 0;
    private int mLastYIntercept = 0;

    private Scroller mScroller;
    private VelocityTracker mVelocityTracker;

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

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

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

    private void init() {
        mScroller = new Scroller(getContext());
        mVelocityTracker = VelocityTracker.obtain();
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        boolean intercepted = false;
        int x = (int) event.getX();
        int y = (int) event.getY();

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                Log.e(TAG, "onInterceptTouchEvent: DOWN");
                //默认viewgroup不拦截down事件
                intercepted = false;
                //当左右滑动动画还没结束时触发down事件,
                // 则左右移动动画被取消,viewgroup拦截down事件
                if (!mScroller.isFinished()) {
                    mScroller.abortAnimation();
                    intercepted = true;
                }
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                int deltaX = x - mLastXIntercept;
                int deltaY = y - mLastYIntercept;
                //x方向偏移量大于y方向偏移量,则viewgroup拦截move事件自己处理
                //否则交给子view处理
                Log.e(TAG, "onInterceptTouchEvent: MOVE " + " ---- Math.abs(deltaX) = " + Math.abs(deltaX)
                + " Math.abs(deltaY)= " + Math.abs(deltaY));
                if (Math.abs(deltaX) > Math.abs(deltaY)) {
                    intercepted = true;
                } else {
                    intercepted = false;
                }
                break;
            }
            case MotionEvent.ACTION_UP: {
                Log.e(TAG, "onInterceptTouchEvent: UP");
                //up事件默认不拦截
                intercepted = false;
                break;
            }
            case MotionEvent.ACTION_CANCEL:
                Log.e(TAG, "onInterceptTouchEvent: CANCLE");
                break;
            default:
                break;
        }

        Log.d(TAG, "intercepted=" + intercepted);
        mLastX = x;
        mLastY = y;
        mLastXIntercept = x;
        mLastYIntercept = y;

        return intercepted;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mVelocityTracker.addMovement(event);
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                Log.e(TAG, "onTouchEvent: DOWN");
                //当左右滑动动画还没结束时触发down事件,
                // viewgroup拦截down事件,并取消左右滑动动画
                if (!mScroller.isFinished()) {
                    mScroller.abortAnimation();
                }
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                int deltaX = x - mLastX;
                int deltaY = y - mLastY;
                Log.e(TAG, "onTouchEvent: MOVE"  + " --- Math.abs(deltaX) = " + Math.abs(deltaX)
                        + " Math.abs(deltaY)= " + Math.abs(deltaY));
                //-deltaX表示控件内容向右移动deltaX距离
                scrollBy(-deltaX, 0);
                break;
            }
            case MotionEvent.ACTION_UP: {
                Log.e(TAG, "onTouchEvent: UP");
                int scrollX = getScrollX();
                int scrollToChildIndex = scrollX / mChildWidth;
                mVelocityTracker.computeCurrentVelocity(1000);
                float xVelocity = mVelocityTracker.getXVelocity();
                if (Math.abs(xVelocity) >= 50) {
                    mChildIndex = xVelocity > 0 ? mChildIndex - 1 : mChildIndex + 1;
                } else {
                    mChildIndex = (scrollX + mChildWidth / 2) / mChildWidth;
                }
                mChildIndex = Math.max(0, Math.min(mChildIndex, mChildrenSize - 1));
                int dx = mChildIndex * mChildWidth - scrollX;
                smoothScrollBy(dx, 0);
                mVelocityTracker.clear();
                break;
            }
            case MotionEvent.ACTION_CANCEL: {
                Log.e(TAG, "onTouchEvent: CANCLE");
                break;
            }
            default:
                break;
        }

        mLastX = x;
        mLastY = y;
        return true;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int measuredWidth = 0;
        int measuredHeight = 0;
        final int childCount = getChildCount();
        measureChildren(widthMeasureSpec, heightMeasureSpec);

        int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec);
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        if (childCount == 0) {
            setMeasuredDimension(0, 0);
        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
            final View childView = getChildAt(0);
            measuredHeight = childView.getMeasuredHeight();
            setMeasuredDimension(widthSpaceSize, childView.getMeasuredHeight());
        } else if (widthSpecMode == MeasureSpec.AT_MOST) {
            final View childView = getChildAt(0);
            measuredWidth = childView.getMeasuredWidth() * childCount;
            setMeasuredDimension(measuredWidth, heightSpaceSize);
        } else {
            final View childView = getChildAt(0);
            measuredWidth = childView.getMeasuredWidth() * childCount;
            measuredHeight = childView.getMeasuredHeight();
            setMeasuredDimension(measuredWidth, measuredHeight);
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int childLeft = 0;
        final int childCount = getChildCount();
        mChildrenSize = childCount;

        for (int i = 0; i < childCount; i++) {
            final View childView = getChildAt(i);
            if (childView.getVisibility() != View.GONE) {
                final int childWidth = childView.getMeasuredWidth();
                mChildWidth = childWidth;
                childView.layout(childLeft, 0, childLeft + childWidth,
                        childView.getMeasuredHeight());
                childLeft += childWidth;
            }
        }
    }

    private void smoothScrollBy(int dx, int dy) {
        mScroller.startScroll(getScrollX(), 0, dx, 0, 500);
        invalidate();
    }

    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            postInvalidate();
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        mVelocityTracker.recycle();
        super.onDetachedFromWindow();
    }
}
public class MyListView extends ListView {
    private static final String TAG = MyListView.class.getSimpleName();
    private int mLastX = 0;
    private int mLastY = 0;

    public MyListView(Context context) {
        super(context);
    }

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

    public MyListView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public MyListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                Log.e(TAG, "dispatchTouchEvent: ACTION_DOWN");
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                Log.e(TAG, "dispatchTouchEvent: ACTION_MOVE");
                break;
            }
            case MotionEvent.ACTION_UP: {
                Log.e(TAG, "dispatchTouchEvent: ACTION_UP");
                break;
            }
            case MotionEvent.ACTION_CANCEL: {
                Log.e(TAG, "dispatchTouchEvent: ACTION_CANCEL");
                break;
            }
            default:break;
        }
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean res = super.onInterceptTouchEvent(ev);
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                Log.e(TAG, "onInterceptTouchEvent: ACTION_DOWN res=" +  res);
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                Log.e(TAG, "onInterceptTouchEvent:  ACTION_MOVE res=" +  res);
                break;
            }
            case MotionEvent.ACTION_UP: {
                Log.e(TAG, "onInterceptTouchEvent:  ACTION_UP res=" +  res);
                break;
            }
            case MotionEvent.ACTION_CANCEL: {
                Log.e(TAG, "onInterceptTouchEvent:  ACTION_CANCEL res=" +  res);
                break;
            }
            default:break;
        }
        return res;
    }

}

        上面代码中HorizontalScrollViewEx负责左右滑动切换页面,MyListView负责上下滑动元素。在HorizontalScrollViewEx的onInterceptTouchEvent和onTouchEvent两个方法中对每个事件都打了log;MyListView的dispatchTouchEvent中也打了log。

        上面的代码是已经解决了滑动冲突的。我们可以通过log的方式来查看事件分发和处理流程。

        分别尝试左右滑动和上下滑动,看看两者的区别。

1、左右滑动

HorizontalScrollViewEx: onInterceptTouchEvent: DOWN
MyListView: dispatchTouchEvent: ACTION_DOWN
MyListView: onInterceptTouchEvent: ACTION_DOWN res=false
HorizontalScrollViewEx: onInterceptTouchEvent: MOVE  ---- Math.abs(deltaX) = 3 Math.abs(deltaY)= 1
MyListView: dispatchTouchEvent: ACTION_CANCEL
HorizontalScrollViewEx: onTouchEvent: MOVE --- Math.abs(deltaX) = 22 Math.abs(deltaY)= 1
HorizontalScrollViewEx: onTouchEvent: MOVE --- Math.abs(deltaX) = 36 Math.abs(deltaY)= 6
...
HorizontalScrollViewEx: onTouchEvent: MOVE --- Math.abs(deltaX) = 49 Math.abs(deltaY)= 2
HorizontalScrollViewEx: onTouchEvent: MOVE --- Math.abs(deltaX) = 20 Math.abs(deltaY)= 0
HorizontalScrollViewEx: onTouchEvent: UP

down事件:

1、首先,down事件进入到HorizontalScrollViewEx的dispatchTouchEvent()方法,进入第一步逻辑,一定会走onInterceptTouchEvent(ev),所以会打印log

HorizontalScrollViewEx: onInterceptTouchEvent: DOWN

然后HorizontalScrollViewEx的onInterceptTouchEvent的down事件返回false,表示不拦截down事件。所以down事件会走第二步事件分发的逻辑。 down事件就进入到MyListView的dispatchTouchEvent(),同样会走到MyListView的onInterceptTouchEvent()中,ListView默认不拦截down事件。down事件继续分发给listview的item项,其本质是一个view,默认会消费down事件。所以最后down事件被listview的item消费了。打印log如下

MyListView: dispatchTouchEvent: ACTION_DOWN
MyListView: onInterceptTouchEvent: ACTION_DOWN res=false

2、接下来是move事件。move进入到HorizontalScrollViewEx的dispatchTouchEvent()方法,由于down事件被子view消费了,所以mFirstTouchTarget != null成立,所以会调用onInterceptTouchEvent(ev)。并且左右滑动delta_X > delta_Y, 所以intercept被设置为true。move事件是不会走第二步事件分发的。直接走第三步,此时mFirstTouchTarget != null,并且intercept=true,所以HorizontalScrollViewEx会调用dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits),其中cancelChild为真,所以子view会接受到一个ACTION_CANCLE事件,并把子view回收掉(如果是单指操作,则mFirstTouchTarget变成null

。所以同一实际序列的后续所有事件,子view都接受不到了。

HorizontalScrollViewEx: onInterceptTouchEvent: MOVE  ---- Math.abs(deltaX) = 3 Math.abs(deltaY)= 1
MyListView: dispatchTouchEvent: ACTION_CANCEL

这里有点疑问,为什么MyListView中cancle事件没走onInterceptTouchEvent??

3、接下来还是move事件。在上面的时候mFirstTouchTarget == null成立,所以viewgroup直接自己处理move事件。

4、不断地move事件,处理逻辑同上;

5、up事件,viewgroup自己处理。

2、上下滑动

HorizontalScrollViewEx: onInterceptTouchEvent: DOWN
MyListView: dispatchTouchEvent: ACTION_DOWN
MyListView: onInterceptTouchEvent: ACTION_DOWN res=false
HorizontalScrollViewEx: onInterceptTouchEvent: MOVE  ---- Math.abs(deltaX) = 1 Math.abs(deltaY)= 16
MyListView: dispatchTouchEvent: ACTION_MOVE
MyListView: dispatchTouchEvent: ACTION_MOVE
MyListView: dispatchTouchEvent: ACTION_MOVE
MyListView: dispatchTouchEvent: ACTION_MOVE
MyListView: dispatchTouchEvent: ACTION_MOVE
MyListView: dispatchTouchEvent: ACTION_MOVE
MyListView: dispatchTouchEvent: ACTION_UP

上面可以看到,第一个move事件时,进入了HorizontalScrollViewEx的onInterceptTouchEvent()函数,后面第二、第三等move事件就没有再走onInterceptTouchEvent()。 这是因为在MyListView的onInterceptTouchEvent()方法的MOVE事件处理中调用了parent.requestDisallowInterceptTouchEvent(true);所以后续move事件不再会执行onInterceptTouchEvent(),而是直接交给子view处理。

这里注意下,当事件为move时,MyListView的onInterceptTouchEvent()从来没有被执行过,这是为什么??

猜测是由于listview的item,即TextView无法响应move事件,它应该是在哪里设置了parent.requestDisallowInterceptTouchEvent(true),所以不会执行onInterceptTouchEvent()。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值