继续创作,加快生长!这是我参加「日新方案 10 月更文应战」的第20天,点击检查活动详情

前置常识

  • 了解 View 的分发事情(没了解也没事)
  • Android 开发基础

PS:本文写给未曾遇见过滑动抵触的 Android 初学者,当然也欢迎大佬们对我点拨一二☺️

前言

假如你是一名Android 新手,那么你很可能没有遇见过滑动抵触,甚至不知道滑动抵触是什么?那是由于你的业务需求可能还不够杂乱,作为一名初学者,没有将多种组件结合运用,那自然就没有遇见到滑动抵触了。

可是,滑动抵触等到遇到的时候再去翻阅资料、学习处理,那么就会显得过于仓惶。所以本篇文章,我将用一个简单的案例,带你来一场与滑动抵触的完美邂逅。

PS:本篇不会尚详细展开解说滑动抵触的各种场景和全面的处理思路,在后续的文章才会进行详细解说。

邂逅时刻

前面咱们说到,滑动抵触的呈现,是根据较为杂乱的组合运用场景。下面,我提出一个稍微杂乱一点的需求,而这个需求直接完成之后,是会呈现滑动抵触的。

需求描绘是这样子的:我需求在一个页面中,展现三个榜单(电影榜,电视剧榜,综艺榜),这三个榜单分别是通过点击导航栏或者左右滑动展现出来,并且榜单能够上下滑动来检查更多的榜单数据。

根据上述的需求,咱们会想到:导航栏能够运用 TabLayout ,榜单左右滑动运用 Viewpager 载入Fragment ,点击展现的话就吧 TabLayoutViewpager 联动即可。最后的榜单上下滑动检查数据,咱们能够在 Fragment 里边载入 NestedScrollView 做纵向的页面滚动。

想好了完成的办法,咱们能够将这个功能完成出来啦,完成的作用如下:

与滑动冲突的首次邂逅

上述完成具体可参阅第四届青训营 这个专栏,具体代码在此处。

可是当咱们完成之后,咱们会发现,这个功能不顺手。怎样不顺手呢?便是当我有时想下滑的时候,他变成了左右滑动了,而一般突变为左右滑动的触发点是我快速滑动的时候。咱们能够看一下下方的动图。

下图中,以同样的视点滑动,慢滑动的时候,事情被子View捕获,完成的是 子View上下滑动

快速滑动的时候,事情被父布局的View捕获,完成的是外层 ViewPager2左右滑动

与滑动冲突的首次邂逅

是的,这种情况,便是滑动抵触。具体来说是,咱们的左右滑动和上下滑动发送抵触了,导致软件无法判别清楚咱们是想履行哪一种滑动指令。当然,上面的滑动抵触并不是很严重,不会影响业务,只会影响体会罢了。

作为一名负责人的 coder,与滑动抵触的相遇只是开端时刻,若没有产生常识的沉积或是使得问题被处理,必然不能算一场美好的相遇啦!

所以,让咱们一起来体会一把怎么处理这个问题,提高用户运用体会吧。

问题的处理

在这里,笔者先笼统的奉告你,滑动抵触的处理大致分为:外部处理法和内部处理法。而其原理,便是根据前文View系统(上)|青训营笔记 – ()中讲到的事情分发机制了。这里我引证其一小段代码简要的说明下其分发机制。

View 的事情分发是,首先 View 层层分发下来,若是 onInterceptTouchEvent(ev)true 就阻拦,为 false 就继续下发。

当某一层级阻拦后,就调用 onTouchEvent(event) 来处理,若是该层无法处理,就传递给父层的 onTouchEvent(event) 来处理。如此层层传递直到有对应能够处理的父层。

//伪代码
public boolean dispatchTouchEvent(MotionEvent ev){
    boolean result = false;
    if(onInterceptTouchEvent(ev)){
        result = onTouchEvent(ev);
    }else{
        result = child.dispatchTouchEvent(ev);
    }
    return result;
}

与滑动冲突的首次邂逅

现在咱们再次说回这两种处理办法,其中重写 onInterceptTouchEvent() 办法便是外部处理法,重写 dispatchTouchEvent() 便是内部处理法

咱们这里选用的是 内部处理法。鄙人面的代码中,假如你是小白,那无需很深刻的理解我处理的进程,大致了解即可。

咱们分析上面的需求得出,产生抵触的是 ViewpagerNestedScrollView ;而由于咱们运用的是 Viewpager2 ,其没有默许处理滑动抵触,且无法承继,所以咱们选择运用 NestedScrollView 来处理。所以选用的是 内部处理法

下面给出重写代码:

public class NestedScrollViewVP extends NestedScrollView {
    public NestedScrollViewVP(@NonNull Context context) {
        super(context);
    }
    public NestedScrollViewVP(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }
    public NestedScrollViewVP(@NonNull Context context, @Nullable AttributeSet attrs,
                              int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    private int startX, startY;
    boolean isDisallowIntercept = false;
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                startX = (int) ev.getX();
                startY = (int) ev.getY();
                getParent().requestDisallowInterceptTouchEvent(true);//告知viewgroup不要去阻拦我
                break;
            case MotionEvent.ACTION_MOVE:
                int endX = (int) ev.getX();
                int endY = (int) ev.getY();
                int disX = endX - startX;
                int disY = endY - startY;
                //视点正确,则让上层view别阻拦我的事情
                float r = (float)Math.abs(disY)/Math.abs(disX);
                if (r > 0.6f) isDisallowIntercept = true;
                getParent().requestDisallowInterceptTouchEvent(isDisallowIntercept);
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                isDisallowIntercept = false;
                getParent().requestDisallowInterceptTouchEvent(false);
                break;
            default:
                break;
        }
        return super.dispatchTouchEvent(ev);
    }
}

上面的而代码中,咱们承继了 NestedScrollView 之后,主要对 dispatchTouchEvent() 办法进行了重写。修正逻辑大致如下

  1. ACTION_DOWN 中,记载手指触发的初始方位,然后恳求父View不对事情做阻拦,默许先把事情传递给 NestedScrollView
  2. ACTION_MOVE 中,计算当时的方位与初始方位构成的视点,假如它的正切值大于 0.6 则认为这是上下滑动的指令,需求父View不对事情做阻拦。
  3. ACTION_UPACTION_CANCEL 中,则恳求父View进行事情阻拦,让其回到默许的状态。

上面有一点需求注意:在上述的代码中,咱们设置了一个变量 isDisallowIntercept记载是否阻挠父View阻拦事情。这个变量的设置很重要,它到达的作用是记载是否产生纵向滑动;假如有产生纵向滑动就恳求父View禁止阻拦事情,让事情都交给 NestedScrollView 处理,这样子接连的上下滑动就不会被判别为左右滑动了。

为何不设置这样子一个变量就会被判别为左右滑动呢?请看下图,首次滑动的轨道1是被判别为纵向滑动的,而轨道2咱们直观上去是纵向滑动,可是实际上系统记载的起点->结尾的轨道是赤色轨道,由于起点是一直不变的,这也就导致了轨道2被判定为横向滑动。所以咱们需求一个变量来记载第一次滑动的方向,以供很好的判别。

这个办法中,咱们只需求考虑捕获纵向滑动的事情,让纵向滑动不会被误判为横向滑动就行,而不必考虑横变纵的问题。由于触发横向滑动后,是被父View阻拦处理的,一旦父View阻拦后,NestedScrollView 中的事情就直接变为 ACTION_CANCEL 类型了,NestedScrollView 中的事情分发办法直接不会被履行了。

所以,通过上述的处理,该页面在每次触点按下之后,只需触点不脱离,那么处理事情的布局就不会变化。

与滑动冲突的首次邂逅

最后,咱们在布局文件中引证咱们的修正的子类即可。

<com.qxy.potatos.module.videorank.myview.NestedScrollViewVP
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">
</com.qxy.potatos.module.videorank.myview.NestedScrollViewVP>

下面是处理了抵触之后的作用,以及不会呈现纵向滑动变成横向滑动的问题了,体会感得到极大的提高。

与滑动冲突的首次邂逅

如上所示,咱们完美处理了它的滑动抵触,提高了用户体会。

从本文中,你也学到了何为滑动抵触,且窥探了大致的处理进程。算是初试滑动抵触,完成了从0到1的进步

后续咱们会继续解说滑动抵触的原理以及处理办法,敬请期待

参阅

免费在线视频转Gif,动图制造一键完成 (apowersoft.cn)

View系统(上)|青训营笔记 – ()