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

View的滑动冲突场景

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

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


    滑动冲突.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);
    }
}

布局文件

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <com.example.chenpeng.julyapplication.SlidingConflict.MyViewPager
        android:layout_width="1500dp"
        android:layout_height="match_parent"
        >
        <LinearLayout
            android:id="@+id/contentLayout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="horizontal">
            <ListView
                android:id="@+id/listView1"
                android:layout_width="500dp"
                android:layout_height="match_parent"/>
            <ImageView
                android:layout_width="500dp"
                android:layout_height="match_parent"
                android:background="@mipmap/bg"/>
            <ListView
                android:id="@+id/listView2"
                android:layout_width="500dp"
                android:layout_height="match_parent"/>
        </LinearLayout>
    </com.example.chenpeng.julyapplication.SlidingConflict.MyViewPager>
</android.support.constraint.ConstraintLayout>

完美解决冲突


外部滑动方向和内部滑动方向不一致.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,两者都可以垂直滑动,需要根据自己需求判断垂直滑动时到底滑动哪一个控件。

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <com.example.chenpeng.julyapplication.SlidingConflict.MyVerticalViewGroup
        android:id="@+id/contentLayout"
        android:layout_width="match_parent"
        android:layout_height="1000dp"
        android:orientation="vertical">

        <ImageView
            android:id="@+id/imageView"
            android:layout_marginTop="-200dp"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:background="@mipmap/bg"/>
        <View
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:background="#789933"/>

        <com.example.chenpeng.julyapplication.SlidingConflict.MyRecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="800dp"/>

    </com.example.chenpeng.julyapplication.SlidingConflict.MyVerticalViewGroup>

</android.support.constraint.ConstraintLayout>

自定义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

总结

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,692评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,482评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,995评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,223评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,245评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,208评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,091评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,929评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,346评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,570评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,739评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,437评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,037评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,677评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,833评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,760评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,647评论 2 354

推荐阅读更多精彩内容