欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android滑动冲突处理

程序员文章站 2022-06-08 17:44:43
...

在我们平时项目开发的过程中,是不是会遇到滑动View之间的相互嵌套,比如外部的Scrollview或SwipeRefreshLayout嵌套内部的Viewpager或横向Recyclerview,如外部Viewpager嵌套内部Viewpager。这样往往就会造成滑动的冲突导致不流畅甚至根本滑不动。事件分发机制见我的另一篇Android事件分发机制,用事实说话

滑动冲突产生的两个根本原因:

  • 外部滑动方向与内部方向不一致
  • 外部滑动方向与内部方向一致
    扎心了,一不一致都可能造成滑动冲突。第一种如ScrollView 嵌套ViewPager,第二种如ViewPager嵌套ViewPager。

解决方案:

外部拦截法:

从父View着手,重写onInterceptTouchEvent方法,在父View需要拦截的时候拦截事件,不需要则不拦截返回false。伪代码如下:

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {

        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_MOVE:
                if(父View拦截条件){
                    return true;
                }
                break;
        }

        return super.onInterceptTouchEvent(ev);
    }

下面我们来看一个真实的案例:

SwipeRefreshLayout嵌套ViewPager,需要流畅横向滑动Viewpager,这种操作下通常会顺带牵连到SwipeRefreshLayout致其拉动,如示例:



Android滑动冲突处理

用外部拦截法自定义一个MySwipeRefreshLayout,在开始滑动的条件下,判断手势的横纵方向的距离大小,判断用户到底是要横向滑动还是纵向滑动。纵向滑动意为拉动刷新,就拦截事件,横向滑动就意为滑动viewpager,就不拦截事件。解决效果:

Android滑动冲突处理

giphy.gif

MySwipeRefreshLayout代码如下:

import android.content.Context;
import android.support.v4.widget.SwipeRefreshLayout;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ViewConfiguration;

public class MySwipeRefreshLayout extends SwipeRefreshLayout{
    private float startX;
    private float startY;
    private float mTouchSlop;

    public MySwipeRefreshLayout(Context context) {
        super(context);

        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    }

    public MySwipeRefreshLayout(Context context, AttributeSet attrs) {
        super(context, attrs);

        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {

        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                startX = ev.getX();
                startY = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                float distanceX = Math.abs(ev.getX() - startX);
                float distanceY = Math.abs(ev.getY() - startY);
                if(distanceX > mTouchSlop && distanceX > distanceY){  //判断为横向滑动
                    return false;
                }

                break;
        }

        return super.onInterceptTouchEvent(ev);
    }
}

getScaledTouchSlop()表示能触发滚动的最小距离,如果小于这个距离就不触发移动控件。

    /**
     * @return Distance in pixels a touch can wander before we think the user is scrolling
     */
    public int getScaledTouchSlop() {
        return mTouchSlop;
    }

内部拦截法:(getParent().requestDisallowInterceptTouchEvent(true),请求父容器不拦截事件)

从子View入手,重写子元素的dispatchTouchEvent方法,父View先不要拦截任何事件,所有的事件传递给子View,如果子View需要此事件就消费掉,不需要此事件的话就通过requestDisallowInterceptTouchEvent(true)方法交给父View处理。伪代码如下:

   @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {

        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                if(事件交给父View条件){
                    getParent().requestDisallowInterceptTouchEvent(false);
                }else{
                    getParent().requestDisallowInterceptTouchEvent(true);
                }
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

父View需要重写:默认不拦截子View事件

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

案例:Viewpager嵌套多个ViewPager:需求是默认滑动内部Viewpager,当内部Viewpager滑动到首页或者末页时,就滑动外部Viewpager切换页面。解决效果:

Android滑动冲突处理

giphy.gif

自定义子Viewpager:

import android.content.Context;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.MotionEvent;

public class InerViewPager extends ViewPager{
    private int itemCount;

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

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

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {

        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                itemCount = getAdapter().getCount();
                if(getCurrentItem() == 0 || getCurrentItem() == itemCount - 1){
                    getParent().requestDisallowInterceptTouchEvent(false);
                }else{
                    getParent().requestDisallowInterceptTouchEvent(true);
                }
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

}


 

相关标签: 滑动冲突