如何成为自定义高手(七)滑动冲突

View的滑动冲突场景

常见的滑动冲突可以简单分为如下三种

  • 场景1:外部滑动方向和内部滑动方向不一致
  • 场景2:外部滑动方向和内部滑动方向一致
  • 场景3:上面两种情况的嵌套


    如何成为自定义高手(七)滑动冲突_第1张图片
    滑动冲突.png

View的滑动冲突解决方式

  1. 外部拦截法:在onInterceptTouchEvvent方法中,首先在ACTION_DOWN这个事件,父容器必须放回false,即不拦截ACTION_DOWN事件,这是因为一旦父容器拦截了ACTION_DOWN,那么后续的ACTION_MOVE和ACTION_UP事件直接传递给父容器处理,没法传递给子元素。
  2. 内部拦截法:父容器不拦截任何事件,所有的事情都传递给子元素。如果子元素需要此事件就直接消耗掉,否则就交由父容器进行处理。如下代码是内部拦截法的典型代码,除了子元素需要做处理以外,父元素也要默认拦截除了ACTION_DOWN以外的其他事件。

场景1的冲突,利用外部拦截法解决

1. 外部拦截法常规解决思路,重写onInterceptTouchEvent方法。
  @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        intercept = false;
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                intercept = false;
                break;
            case MotionEvent.ACTION_MOVE:
                if(父容器需要当前点击事件){
                    intercept = true;
                }else{
                    intercept = false;
                }
               break;
            case MotionEvent.ACTION_UP:
                intercept = false;
                Log.i(TAG, "onInterceptTouchEvent: ACTION_UP");
                break;
        }
        return intercept;
    }
2. 场景1冲突实例

一个可以水平滑动的HorizontalScrollView和一个垂直滑动的ListView就会产生滑动冲突。其实在ViewPager+ListView是不会有滑动冲突的,因为ViewPager内部已经解决。
思路:判断水平滑动还是垂直滑动,可以通过在水平方向上deltaX和垂直方向上deltaY的绝对比较就可以明白用户想要水平还是垂直滑动。
自定义MyViewPager代码

public class MyViewPager extends HorizontalScrollView {

    private boolean intercept;
    private float lastX,lastY,x,y;
    private float deltaX,deltaY;
    private float totalX = 0;
    private static final String TAG = "MyViewPager";
    private LinearLayout mContentLL;
    public MyViewPager(Context context) {
        this(context,null);
    }

    public MyViewPager(Context context, AttributeSet attrs) {

        this(context, attrs,0);
    }

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

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mContentLL = findViewById(R.id.contentLayout);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.i(TAG, "dispatchTouchEvent: ");
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        intercept = false;
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                intercept = false;
                lastX = ev.getX();
                lastY = ev.getY();
                Log.i(TAG, "onInterceptTouchEvent: ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                x = ev.getX();
                y = ev.getY();
                deltaX = x - lastX;
                deltaY = y - lastY;
                Log.i(TAG, "onInterceptTouchEvent: ACTION_MOVE: deltX =  " + deltaX + " , deltY = " + deltaY);
                if(Math.abs(deltaX) > Math.abs(deltaY)){//水平滑动 , 当前父ViewGroup拦截后交给onTouchEvent去处理具体的操作
                    intercept = true;
                }else{
                    intercept = false;
                }
                lastX = x;
                lastY = y;
                break;
            case MotionEvent.ACTION_UP:
                intercept = false;
                Log.i(TAG, "onInterceptTouchEvent: ACTION_UP");
                break;
        }
        return intercept;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                lastX = ev.getX();
                break;
            case MotionEvent.ACTION_MOVE:
            case MotionEvent.ACTION_UP:
                x = ev.getX();
                deltaX = x - lastX;
                totalX += deltaX;
                Log.i(TAG, "onTouchEvent: totalX = " +  totalX + " , deltaX = " + deltaX);
                if(mContentLL != null){
                    mContentLL.scrollBy((int) -deltaX,0);
                }
                lastX = x;
                break;
        }
        return super.onTouchEvent(ev);
    }
}

布局文件



    
        
            
            
            
        
    

完美解决冲突


外部滑动方向和内部滑动方向不一致.gif

场景2的冲突,利用内部拦截法解决

1. 内部拦截法解决思路,重写子元素的dispatchTouchEvent方法,和父元素的onInterceptTouchEvent方法。

父元素的onInterceptTouchEvent。ACTION_DOWN事件不受FLAG_DISALLOW_INTERCEPT这个标记位的控制,所以一旦父容器拦截ACTION_DOWN事件,那么所有的事件都无法传递到子元素中去,这样内部拦截就无法起作用。

 @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        if(action == MotionEvent.ACTION_DOWN){
            return false;
        }else {
            return true;
        }
    }

子元素的onInterceptTouchEvent。内部拦截法的典型代码,面对不同的滑动策略时只需要修改里面的条件即可。

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                if(父容器需要此类事件){//事件处理交个父容器
                    getParent().requestDisallowInterceptTouchEvent(false);
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

2. 场景2冲突实例

一个可以垂直滑动的MyVerticalViewGroup,内部包含一个可以垂直滑动的MyRecyclerView,两者都可以垂直滑动,需要根据自己需求判断垂直滑动时到底滑动哪一个控件。



    

        
        

        

    


自定义MyVerticalViewGroup

public class MyVerticalViewGroup extends LinearLayout {

    private LinearLayout mContentll;
    private float y,lastY,deltaY;
    private int mImageViewHeight;
    private static final String TAG = "SlidingView03";


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

    public MyVerticalViewGroup(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

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

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mContentll = findViewById(R.id.contentLayout);
        mImageViewHeight = (int) (200 * getResources().getDisplayMetrics().density+0.5);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        if(action == MotionEvent.ACTION_DOWN){
            lastY = ev.getY();
            return false;
        }else {
            lastY = ev.getY();
            return true;
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {//父容器的事件处理
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                lastY = ev.getY();
                Log.i(TAG, "parent onTouchEvent: ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
            case MotionEvent.ACTION_UP:
                y = ev.getY();
                deltaY = y - lastY;
                if(mContentll != null){
                    mContentll.scrollBy(0, -(int) deltaY);
                }
                lastY = y;
                Log.i(TAG, "parent onTouchEvent: ACTION_MOVE ACTION_UP");
                break;
        }
        return super.onTouchEvent(ev);
    }

    @Override
    public void scrollTo(int x, int y) {
        //边界控制
        if(y > 0 ){
            y = 0;
        }
        if( y < 0 && Math.abs(y) > mImageViewHeight){
             y = -mImageViewHeight;
        }
        super.scrollTo(x, y);
    }
}

自定义MyRecyclerView

public class MyRecyclerView extends RecyclerView {


    private static final String TAG = "SlidingView03";
    private float y,lastY,deltaY;


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

    public MyRecyclerView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public MyRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }



    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                getParent().requestDisallowInterceptTouchEvent(true);
                lastY = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                y = ev.getY();
                deltaY = y - lastY;//deltaY > 0 表示向下滑动 ; deltaY < 0 表示向上滑动
                lastY = y;
                boolean isRecyclerViewOnTop = isTop();
                Log.i(TAG, "child dispatchTouchEvent: deltaY = " + deltaY + " ,isTop() =  " + isRecyclerViewOnTop + ", ViewCompat.canScrollVertically(this, 1) = " + ViewCompat.canScrollVertically(this, 1));
                if(( isRecyclerViewOnTop && deltaY > 0 ) || ( deltaY < 0  && !ViewCompat.canScrollVertically(this, 1))){//事件处理交个父容器 -1判读是否可以下滑 1判断是否可以上滑
                    getParent().requestDisallowInterceptTouchEvent(false);
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
//                Log.i(TAG, "onTouchEvent: ACTION_DOWN");
                lastY = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                y = ev.getY();
                deltaY = y - lastY;
                lastY = y;
//                Log.i(TAG, "onTouchEvent: ACTION_MOVE totalY = " + totalY + " , y = " + y + " ,lastY = " + lastY);
                break;
            case MotionEvent.ACTION_UP:
//                Log.i(TAG, "onTouchEvent: ACTION_UP");
                lastY = ev.getY();
                break;

        }
        return super.onTouchEvent(ev);
    }

    public boolean isTop(){
        LinearLayoutManager layoutManager = (LinearLayoutManager) this.getLayoutManager();
        int position = layoutManager.findFirstVisibleItemPosition();
        View firstVisibleChildView = layoutManager.findViewByPosition(position);
        Log.i(TAG, "isTop: position = 0" +" , firstVisibleChildView.getTop() = " + firstVisibleChildView.getTop() + ",RecyclerView " + getScrollY() );
        if(position == 0 && firstVisibleChildView.getTop() == 0){
            return true;
        }else{
            return false;
        }
    }
}

实例演示

SlidingConfict2.gif

总结

  • 场景3的情况就是场景1和场景2的结合,方式一样就是复杂一些。
  • 个人喜欢用外部拦截法,感觉方便一些
  • 场景2中会有一个问题,就是一旦事件交个子元素或者父容器处理,他们会一管到底。知道ACTION_UP为止。利用Android自带嵌套滑动控件解决滑动冲突(NestingScroll,CoordinatorLayout与Behavior)可以很好的解决。

如何成为自定义高手(一)绘制
如何成为自定义高手(二)动画
如何成为自定义高手(三)布局
如何成为自定义高手(四)触摸反馈,事件分发机制
如何成为自定义高手(五)多点触摸
如何成为自定义高手(六)滑动和拖拽
如何成为自定义高手(七)滑动冲突
利用Android自带嵌套滑动控件解决滑动冲突(NestingScroll,CoordinatorLayout与Behavior)

你可能感兴趣的:(如何成为自定义高手(七)滑动冲突)