本文字数:4688字
预计阅读时间:16分钟
VideoDownload
下载b站、抖音视频仅用于学习。
Android NestedScrolling 机制-基础篇
其实NestedScrolling对于现在的Android开发已经是一个很常见的交互效果,当我们需要实现一些好看却又比较复杂的滑动变换时,基本上就需要借助NestedScrolling机制。
先看下一个比较常见的示例效果
这套效果是利用官方提供的CoordinatorLayout实现的,当然CoordinatorLayout就是NestedScrolling机制典型的官方应用例子。
一 对比
这里我会先介绍下基础的概念和机制实现,后续结合对整个机制的理解实现一个简单模仿NestedScrollView的Demo,以便更好的加深理解。
效果如图:
传统的View事件分发机制
传统事件分发处理主要涉及到Activity、ViewGroup和View这三个主要的类。
事件分发机制 对应分发方法是dispatchTouchEvent ,只要事件能够传递到当前处理的View,则此方法一定被调用。
事件拦截机制对应的拦截方法是onInterceptTouchEvent,拦截之后则不往下传递。(此方法只有ViewGroup拥有,此方法会在dispatchTouchEvent 中被调用。
事件的响应机制对应的是onTouchEvent方法。
对于传统的事件从分发,拦截到处理的一般流程:
由当前的Activity接收系统的Touch事件回调,调用dispatchTouchEvent开始分发Touch事件
上层ViewGroup根据onInterceptTouchEvent判断是否要中断Touch事件
中断,则拦截Touch事件,并会回调onTouchEvent,进行处理
不中断,则继续调用其子View的dispatchTouchEvent继续分发Touch事件
直到有子View消费掉了Touch事件,则整个过程就结束了
传统的Touch事件分发是由上向下的整个过程类似于一个单向的水流事件,中间有一环将水流拦截,则下游便不会再有水流经过。
这样导致的问题,就是在一个Touch事件流中,只能有一个View或ViewGroup对当前Touch事件做出反应。
原则上当Touch事件被拦截后,是无法再次交还给下层子View去处理的(除非手动干预事件的分发)。
因此我们示例中的滑动效果,我们滑动的是下面的内容区域View,但是滚动的却是外部的ViewGroup,那么就必须由上面的ViewGroup拦截Touch事件并进行处理。
而示例效果确实上层的ViewGroup滑动一定程度后,又交换给了下面的子View进行滑动处理,显然这种无间断顺滑交互,按照传统的Touch事件的分发机制,是很难实现的。
由此就引出了我们今天的主角NestedScrolling机制。
NestedScrolling机制
基础流程大致是这样的:
首先需要原有的Touch事件处理先交给子View,当然父View可以拦截,拦截后本次处理便无法交给子View去处理了
当子View接收到Touch事件时,会转换为NestedScrolling事件,也就是dx,dy (后续统称为NestedScrolling事件),并开始发起NestedScrolling事件分发。
子View首先将对应的NestedScrolling事件发送给父View处理,待父View处理完成后则返回对应的处理和未处理的偏移量
子View根据剩余偏移量继续处理NestedScrolling事件,并再次通知父View处理剩余的偏移量
父View处理完成,子view则最后发起NestedScrolling事件终结,父view进行收尾工作
从上述的流程中可以轻易的得出NestedScrolling事件,是一种由下向上发起,但是在处理过程中,会不断询问上层View的处理,整个过程是给予了上层和下层多个参与处理的机会。
不难得出NestedScroll的机制本质上是给View与View之间提供了一种关联的机制,以实现View之间协同处理原来的Touch事件,来解决传统Touch事件机制无法回溯到父View的一锤子买卖的问题。当然其中的前提条件是子View需要作为Touch事件的处理者。
二 实现
1 主要接口
- NestedScrollingParent
- NestedScrollingChild
最新的支持库中,还有
- NestedScrollingParent2
- NestedScrollingChild2
由于新增的两个类原理上不影响对NestedScrolling机制的分析,在此就不对这两个类多做描述。
首先了解一下NestedScrollingParent的接口方法
boolean onStartNestedScroll(@NonNull View child, @NonNull View target, @ScrollAxis int axes);
void onNestedScrollAccepted(@NonNull View child, @NonNull View target, @ScrollAxis int axes);
void onStopNestedScroll(@NonNull View target);
void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed);
void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed);
boolean onNestedFling(@NonNull View target, float velocityX, float velocityY, boolean consumed);
boolean onNestedPreFling(@NonNull View target, float velocityX, float velocityY);
int getNestedScrollAxes();
首先Parent中的方法基本都是on开头的响应式的方法,我们这里需要着重关注这几个方法
onStartNestedScroll 对发起嵌套滑动的子view做出回应,这个子View不一定是直接子View,例如viewPager嵌套的RecycleView。如果此父View接受嵌套滚动操作,则需要返回true。
onStopNestedScroll 对终止嵌套滑动的子view做出回应。
onNestedPreScroll 这个方法会接收子view中的NestedScrolling事件的滑动距离dx,dy,并交给父View处理,其中consumed数组,即是记录父View处理的dx,dy的消耗。因此这个方法是是父View在子view滚动之前,进行NestedScrolling事件处理的恰当时机。
onNestedScroll 这个方法包含了NestedScrolling事件的已消耗和未消耗的滑动距离