滑动冲突现象及解决方案(笔记二)

一.滑动冲突场景

继续讲述上篇场景一的滑动冲突,定义出模拟场景和解决方案(内部拦截法)。跟上篇一样定义一个可以横向滑动的父容器,子View定义一个RecycleView可以垂直滑动。

二.重点代码
  • 父容器ViewGroup
    只需重写父容器onInterceptTouchEvent方法,如下:
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean isIntercept = false;
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //不能拦截Down事件,后续事件自己处理,导致子View永远处理不了事件
                break;
            case MotionEvent.ACTION_MOVE:
            case MotionEvent.ACTION_UP:
                //设置为true:当子View 调用 requestDisallowInterceptTouchEvent(false)申请父容器不拦截事件为(false)
                //此时此事件交回给父容器,所以父容器需要自己消耗掉返回true。

                /*疑点:这里move事件已经拦截了,那怎么会传递后续事件到子View呢?
                 子View需要在down事件的时候调用requestDisallowInterceptTouchEvent(true),
                 表示父容器down事件之后的系列事件将不要拦截(这里跟事件分发流程有点不同)*/
                isIntercept = true;
                break;
        }
        return isIntercept;
    }
  • 子类View
    重写dispatchTouchEvent方法,如下:
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                //请求父容器不拦截后续系列事件,
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                float moveX = ev.getX();
                float moveY = ev.getY();
                if (Math.abs(moveX- mLastX)>Math.abs(moveY- mLastY)) {
                    //请父类容器不拦截事件为false,即父容器会拦截之后系列事件
                    getParent().requestDisallowInterceptTouchEvent(false);
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
        }

        mLastX = ev.getX();
        mLastY = ev.getY();
        return super.dispatchTouchEvent(ev);
    }
  • 正常处理
    在这里插入图片描述

  • 不使用 requestDisallowInterceptTouchEvent 处理

  if (Math.abs(moveX- mLastX)>Math.abs(moveY- mLastY)) {
                    //请父类容器不拦截事件为false,即父容器会拦截之后系列事件
                    //getParent().requestDisallowInterceptTouchEvent(false);
                }

在这里插入图片描述

三.具体代码
1.定义一个可以横向滑动的容器HorizontalScrollViewGroup02,冲突处理采用内部部拦截法,(跟上篇ViewGroup一样,只是onInterceptTouchEvent方法有差异)代码入下:
public class HorizontalScrollViewGroup02 extends ViewGroup {
    private final static String TAG = "HorizontalScrollview";
    private Scroller mScroller;
    private float mDownX;

    float interceptDownX = 0;
    float interceptDownY = 0;
    private VelocityTracker mVelocityTracker;//速度跟踪

    private int mChildWidth;
    private int mChildeIndex;
    private int mChildCount;

    public HorizontalScrollViewGroup02(Context context) {
        this(context, null);
    }

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

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int childCount = getChildCount();

        //1.先测量子view
        measureChildren(widthMeasureSpec, heightMeasureSpec);

        //2.自身测量
        int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
        int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
        int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
        int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);

        if (childCount == 0) {
            //子view 数量为0 宽高为父容器剩余空间
            setMeasuredDimension(sizeWidth, sizeWidth);
        } else if (modeHeight == MeasureSpec.AT_MOST && modeWidth == MeasureSpec.AT_MOST) {
            View childAt = getChildAt(0);
            //横向排布效果 默认取第一个view的高度
            setMeasuredDimension(childCount * childAt.getMeasuredWidth(), childAt.getMeasuredHeight());
        } else if (modeHeight == MeasureSpec.AT_MOST) {
            setMeasuredDimension(sizeWidth, getChildAt(0).getHeight());
        } else if (modeWidth == MeasureSpec.AT_MOST) {
            setMeasuredDimension(childCount * getChildAt(0).getMeasuredWidth(), sizeHeight);
        } else {
            setMeasuredDimension(sizeWidth, sizeWidth);
        }
    }

    /**
     * 确定子view位置
     *
     * @param changed
     * @param l
     * @param t
     * @param r
     * @param b
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int childLeft = 0;
        mChildCount = getChildCount();

        for (int i = 0; i < mChildCount; i++) {
            View childAt = getChildAt(i);
            int measuredWidth = childAt.getMeasuredWidth();
            mChildWidth = measuredWidth;
            //宽:累计子view的宽度  高:取子view高度
            childAt.layout(childLeft, 0, childLeft + childAt.getMeasuredWidth(), childAt.getMeasuredHeight());
            childLeft += childAt.getMeasuredWidth();
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_MOVE:
                float moveX = ev.getX();
                Log.d(TAG, "onTouchEvent: mDownX:" + mDownX + ",moveX:" + moveX);
                //为负数:scrollBy移动的是控件里的内容,是相对滑动方向的反方向
                scrollBy((int) -(moveX - mDownX), 0);
                break;
            case MotionEvent.ACTION_UP:
                //优化:为了滑动到view边界
                measureVelocity(ev);
                break;
        }
        mDownX = ev.getX();
        return true;
    }


    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean isIntercept = false;
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //不能拦截Down事件,后续事件自己处理,导致子View永远处理不了事件
                break;
            case MotionEvent.ACTION_MOVE:
            case MotionEvent.ACTION_UP:
                //设置为true:当子View 调用 requestDisallowInterceptTouchEvent(false)申请父容器不拦截事件为(false)
                //此时此事件交回给父容器,所以父容器需要自己消耗掉返回true。

                /*疑点:这里move事件已经拦截了,那怎么会传递后续事件到子View呢?
                 子View需要在down事件的时候调用requestDisallowInterceptTouchEvent(true),
                 表示父容器down事件之后的系列事件将不要拦截(这里跟事件分发流程有点不同)*/
                isIntercept = true;
                break;
        }
        return isIntercept;
    }

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

    /**
     * 模板写法
     */
    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            postInvalidate();
        }
    }

    private void measureVelocity(MotionEvent ev) {
        mVelocityTracker.addMovement(ev);
        int scroll = getScrollX();
        //1秒内滑动
        mVelocityTracker.computeCurrentVelocity(1000);
        float xVelocity = mVelocityTracker.getXVelocity();
        if (Math.abs(xVelocity) > 50) {
            mChildeIndex = xVelocity > 0 ? mChildeIndex - 1 : mChildeIndex + 1;
        } else {
            mChildeIndex = (scroll + mChildWidth / 2) / mChildWidth;
        }
        mChildeIndex = Math.max(0, Math.min(mChildeIndex, mChildCount - 1));
        int dx = mChildeIndex * mChildWidth - scroll;
        smoothScrollBy(dx);
        mVelocityTracker.clear();
    }

}
2.定义子View,继承RecycleView,重写dispatchTouchEvent方法:

public class InternalInteceptRecycleView extends RecyclerView {

    private float mLastX;
    private float mLastY;

    public InternalInteceptRecycleView(Context context) {
        this(context, null);
    }

    public InternalInteceptRecycleView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                //请求父容器不拦截后续系列事件,
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                float moveX = ev.getX();
                float moveY = ev.getY();
                if (Math.abs(moveX- mLastX)>Math.abs(moveY- mLastY)) {
                    //请父类容器不拦截事件为false,即父容器会拦截之后系列事件
                    getParent().requestDisallowInterceptTouchEvent(false);
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
        }

        mLastX = ev.getX();
        mLastY = ev.getY();
        return super.dispatchTouchEvent(ev);
    }
}
3.定义测试 HorizontalAndVerticalScrollActivity02
  • HorizontalAndVerticalScrollActivity02 界面
/**
 * 内部拦截法
 */

public class HorizontalAndVerticalScrollActivity02 extends AppCompatActivity{

    @Bind(R.id.rv)
    RecyclerView mRv;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_horizontal_scroll02);
        ButterKnife.bind(this, this);
        initData();
    }

    private void initData() {
        ArrayList<String> strings = new ArrayList<>();
        for (int i = 1; i < 81; i++) {
            strings.add("第"+i+"个");
        }
        mRv.setLayoutManager(new LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false));
        NormalTextAdapter normalTextAdapter = new NormalTextAdapter(strings);
        mRv.setAdapter(normalTextAdapter);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        ButterKnife.unbind(this);
    }
}
  • HorizontalAndVerticalScrollActivity02 布局
<?xml version="1.0" encoding="utf-8"?>
<com.example.robertluozizhao.framecollectdemo.view.custom.HorizontalScrollViewGroup02
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/green"
        android:gravity="center"
        android:text="222222222222"
        android:textSize="30px"/>


    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/green"
        android:gravity="center"
        android:text="33333333333"
        android:textSize="30px"/>

    <com.example.robertluozizhao.framecollectdemo.view.custom.InternalInteceptRecycleView
        android:id="@+id/rv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/gray"/>


</com.example.robertluozizhao.framecollectdemo.view.custom.HorizontalScrollViewGroup02>

笔记

内部拦截法
1.作为ViewGroup重写onInterceptTouchEvent方法,拦截除down以外事件;
2.作为View重写dispatchTouchEvent方法,需要配合parent.requestDisallowInterceptTouchEvent的方法:down事件时,申请父容器不拦截事件parent.requestDisallowInterceptTouchEvent(true),事件由子View处理;后续根据需求设置parent.requestDisallowInterceptTouchEvent(false),后续事件将由父容器处理

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值