android 滑动冲突

这个知识点是在太大了,是年多个知识点的汇总,很难搞,高级的页面视图效果和动画都离不开他,我们必须想一切办法搞明白~

这对这部分内容我也是新手,本文负责记录下找到的资料,分类汇总下。

处理思路


在开发中,滑动冲突有很多,比如ScrollView嵌套ListView、ScrollView嵌套ViewPager、ViewPager嵌套ScrollView等等各种嵌套之后的滑动冲突

归根结底可以分为两种:

  • 同方向滑动冲突:
    比如ScrollView嵌套ListView,或者是ScrollView嵌套自己

  • 不同方向滑动冲突:
    比如ScrollView嵌套ViewPager,或者是ViewPager嵌套ScrollView,这种情况其实很典型。现在大部分应用最外层都是ViewPager+Fragment 的底部切换(比如微信)结构,这种时候,就很容易出现滑动冲突。不过ViewPager里面无论是嵌套ListView还是ScrollView,滑动冲突是没有的,毕竟是官方的东西,可能已经考虑到了这些,所以比较完善。

处理思路也可以分2种:

  • 从外层处理
  • 从内层处理

从外层处理

比如在外层滑动容器中,判断滑动方向,不是外层滑动容易方向而是内层滑动控件方向的,外层就不拦截了,交给内层

从内层处理

一般,官方提供的 layout,view 都不会拦截 action_down,这个事件,所以内层滑动控件的 dispatchTouchEvent -> onInterceptTouchEvent 肯定执行一次的,这时我们可以在内层 view 的 onInterceptTouchEvent 方法中请求忽略外层容器拦截事件 getParent().requestDisallowInterceptTouchEvent(true) ,然后我们判断是不是需要我们消费的事件,不是的话我们不消费,交给上一层去处理。

内层控件若是 view 的话,是没有 onInterceptTouchEvent 方法的,那么 getParent().requestDisallowInterceptTouchEvent(true) 写在哪里呢 setOnTouchListener,OnTouchListener.onTouch 方法在 view 的事件处理中最先执行,是个合适的位置

参考资料看这个

大体的思路基本都是根据这个逻辑走的,下面我们看看具体的滑动冲突的情况。

ScrollView嵌套ViewPager冲突


自定义一个MyScrollView继承ScrollView,重写 onInterceptTouchEvent方法,在 Action_Move事件中判断,如果水平滑动距离大于竖直滑动距离,则return false,表示不拦截事件,把事件分发到下一级控件,交由下一级处理

public class MyScrollView extends ScrollView{

    private float xDistance;
    private float yDistance;
    private float xLast;
    private float yLast;

    /**
     * 在该方法中进行判断
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                xDistance = yDistance = 0.0f;
                xLast = ev.getX();
                yLast = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                final float curX = ev.getX();
                final float curY = ev.getY();

                xDistance += Math.abs(curX - xLast);
                yDistance += Math.abs(curY - yLast);

                if(xDistance > yDistance)
                    return false;

                break;
            default:
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }
}

参考资料:

ScrollView 嵌套 ScrollView


在 onInterceptTouchEvent 事件拦截函数内巧用 getParent().requestDisallowInterceptTouchEvent(true) 就可以让所有的父控件不拦截我们的事件了。

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        getParent().requestDisallowInterceptTouchEvent(true);
        return super.onInterceptTouchEvent(ev);
    }

参考例子:

NestedScrollView 嵌套 RecyclerView


这是最简单的处理嵌套滚动冲突的办法了,典型案例:NestedScrollView 嵌套 RecyclerView

参考例子:

给 RecyclerView 设置禁止滚动,RecyclerView 就会忽略 view 的复用机制,当前屏幕控件恩那个显示多少条 item ,就显示多少条 item,剩下的会被忽略。

这适用于 item 数量能偶一屏显示的列表。对于数量多的列表需要考重写布局管理器

//布局文件的RecyclerView中设置
android:nestedScrollingEnabled="false" 
//或者Java代码设置
recyclerView.setNestedScrollingEnabled(false);

说下缺点,禁用 RecyclerView 的滚动之后,在滚动嵌套中,局域内层的 RecyclerView 还是会首先收到触摸事件的,对于非滚动方向的事件在处理后,上层滚动控件比如 NestedScrollView 是收不到的。

典型的例子就是在这个嵌套页面中,我们在内层列表的位置斜的角度稍微大点上下滑,你会发现我滑动的手势被列表吃了,外面的滚动控件不会滚动。

viewpager嵌套 webview


webview 中加载的网页有时是需要处理手势操作的,比如页面头部位有一个 binner ,是页面的部分需要手势操作而不是页面全部,页面的手势操作需要 webview 能够消费手势事件才行。若是此时 webview 的外部是像 viewpager 这样也需要消费手势事件的话就产生事件冲突了,那么应该怎么做到平衡呢,原则是内层的控件需要时优先吧手势事件给内层控件,内层控件不需要时声明不消费事件,交给上一级处理。

参考这个例子:

网页提供 js 方法,告知我们页面可滑动元素距屏幕顶部的位置,并调用 JAVA 方法同步我们的代码内,然后在 webview 的 onTouch (view 没有 onInterceptTouchEvent 方法) 方法中获取当前触摸点的距离屏幕左上角的位置,然后和页面中可滑动元素距屏幕左上角的位置做比对,触摸点在页面可滑动元素范围内,webview 就接受事件,若不再那么 webview 就不接受事件,

@Override
        public boolean onTouch(View view, MotionEvent motionEvent) {
            //获取y轴坐标
            float y = motionEvent.getRawY();
            switch (motionEvent.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    getHTMLPosition();
                    if (null != mPagerDesc) {
                        int top = mPagerDesc.top;
                        int bottom = top + (mPagerDesc.bottom - mPagerDesc.top);
                        //将css像素转换为android设备像素并考虑通知栏高度
                        top = (int) (top * metric.density) + height 
                        bottom = (int) (bottom * metric.density) + height    
                        //如果触摸点的坐标在轮播区域内,则由webview来处理事件,否则由viewpager来处理
                        if (y > top && y < bottom) {
                            webview.requestDisallowInterceptTouchEvent(true);
                        } else {
                            webview.requestDisallowInterceptTouchEvent(false);
                        }
                    }
                    break;
                case MotionEvent.ACTION_UP:
                    break;
                case MotionEvent.ACTION_MOVE:
                    break;
            }

禁止 Viewpager 滑动


平时我们有时是由这个需求的,那么怎么处理 Viewpager 呢,其实看过上面之后,这个问题给为其实可以自己解决的了了。

我们不让 Viewpager 拦截事件,Viewpager 就那不到事件就不能滑动,我们不让 Viewpager 消费事件,那么在 Viewpager 内层的 view 不处理事件时把事件回传给上级的 Viewpager 时,Viewpager 也不会实际产生话滑动,我们把 Viewpager 实际处理事件的代码全部删掉。然后这么写就行

public class CustomViewPager extends ViewPager {

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        return false;
    }
}

最后说下


我们在处理 view 时,大家要是不想在每次都重写 view.setOnTouchListener 方法,那么就自己定义一个继承 view 的类,然后在构造方法中自己调一下 setOnTouchListener 方法,传自己的 OnTouchListener 对象进去

小例子:view 只处理 x 轴放先发的滑动,y 轴方向的发回给外层容器

xml

    <com.bloodcrown.aaa02.MyLayout
        android:layout_width="match_parent"
        android:layout_height="350dp"
        android:background="@color/colorAccent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toTopOf="parent">=

        <com.bloodcrown.aaa02.MyView
            android:id="@+id/view_a"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:layout_gravity="center"
            android:background="@color/colorPrimaryDark"/>

    </com.bloodcrown.aaa02.MyLayout>

外层容器

public class MyLayout extends FrameLayout {

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {

        if( event.getAction() == MotionEvent.ACTION_MOVE ){
            Log.d("AAA", " 外层容器_onInterceptTouchEvent: ");
            return true;
        }
        return false;
    }
}

自定义 view

public class MyView extends View {

    public MyView(Context context) {
        super(context);
        setOnTouchListener(touchListener);
    }

    public MyView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        setOnTouchListener(touchListener);
    }

    public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setOnTouchListener(touchListener);
    }

    View.OnTouchListener touchListener = new View.OnTouchListener() {

        int lastX, lastY;
        int x, y;

        @Override
        public boolean onTouch(View v, MotionEvent event) {

            getParent().requestDisallowInterceptTouchEvent(true);

            if (event.getAction() == MotionEvent.ACTION_DOWN) {
                lastX = (int) event.getX();
                lastY = (int) event.getY();
            }

            if (event.getAction() == MotionEvent.ACTION_MOVE) {
                x = (int) event.getX();
                y = (int) event.getY();
                if (Math.abs(x - lastX) >= Math.abs(y - lastY)) {
                    Log.d("AAA", "内层获取事件,判定方向为 x 轴滑动,处理事件");
                    getParent().requestDisallowInterceptTouchEvent(true);
                } else {
                    Log.d("AAA", "内层获取事件,判定方向为 y 轴滑动,事件交给外层容器处理");
                    getParent().requestDisallowInterceptTouchEvent(false);
                }
            }
            return true;
        }
    };

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