您的位置:首页 > 移动开发 > Android开发

Android 嵌套滑动机制(NestedScrolling)

2016-04-17 11:22 956 查看
Android 在发布 
Lollipop
版本之后,为了更好的用户体验,Google为Android的滑动机制提供了
NestedScrolling
特性

NestedScrolling
的特性可以体现在哪里呢?

比如你使用了
Toolbar
,下面一个
ScrollView
,向上滚动隐藏
Toolbar
,向下滚动显示
Toolbar
,这里在逻辑上就是一个
NestedScrolling
 ——
因为你在滚动整个
Toolbar
在内的View的过程中,又
嵌套
滚动了里面的
ScrollView




效果如上图【别嫌弃我】

在这之前,我们知道
Android
Touch
事件的分发是有自己一套机制的。主要是有是三个函数:
dispatchTouchEvent
onInterceptTouchEvent
onTouchEvent


这种分发机制有一个漏洞:

如果子view获得处理touch事件机会的时候,父view就再也没有机会去处理这个touch事件了,直到下一次手指再按下。

也就是说,我们在滑动子View的时候,如果子View对这个滑动事件不想要处理的时候,只能抛弃这个touch事件,而不会把这些传给父view去处理。

但是Google新的
NestedScrolling
机制就很好的解决了这个问题。

我们看看如何实现这个
NestedScrolling
,首先有几个类(接口)我们需要关注一下

NestedScrollingChild
NestedScrollingParent
NestedScrollingChildHelper
NestedScrollingParentHelper

以上四个类都在
support-v4
包中提供,Lollipop的View默认实现了几种方法。

实现接口很简单,这边我暂时用到了
NestedScrollingChild
系列的方法(因为Parent是support-design提供的
CoordinatorLayout

@Override
public void setNestedScrollingEnabled(boolean enabled) {
super.setNestedScrollingEnabled(enabled);
mChildHelper.setNestedScrollingEnabled(enabled);
}

@Override
public boolean isNestedScrollingEnabled() {
return mChildHelper.isNestedScrollingEnabled();
}

@Override
public boolean startNestedScroll(int axes) {
return mChildHelper.startNestedScroll(axes);
}

@Override
public void stopNestedScroll() {
mChildHelper.stopNestedScroll();
}

@Override
public boolean hasNestedScrollingParent() {
return mChildHelper.hasNestedScrollingParent();
}

@Override
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {
return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow);
}

@Override
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
}

@Override
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
return mChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
}

@Override
public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
return mChildHelper.dispatchNestedPreFling(velocityX, velocityY);
}


对,简单的话你就这么实现就好了。

这些接口都是我们在需要的时候自己调用的。childHelper干了些什么事呢?,看一下
startNestedScroll
方法
/**
* Start a new nested scroll for this view.
*
* <p>This is a delegate method. Call it from your {@link android.view.View View} subclass
* method/{@link NestedScrollingChild} interface method with the same signature to implement
* the standard policy.</p>
*
* @param axes Supported nested scroll axes.
*             See {@link NestedScrollingChild#startNestedScroll(int)}.
* @return true if a cooperating parent view was found and nested scrolling started successfully
*/
public boolean startNestedScroll(int axes) {
if (hasNestedScrollingParent()) {
// Already in progress
return true;
}
if (isNestedScrollingEnabled()) {
ViewParent p = mView.getParent();
View child = mView;
while (p != null) {
if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes)) {
mNestedScrollingParent = p;
ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes);
return true;
}
if (p instanceof View) {
child = (View) p;
}
p = p.getParent();
}
}
return false;
}


可以看到这里是帮你实现一些跟
NestedScrollingParent
交互的一些方法。
ViewParentCompat
是一个和父view交互的兼容类,它会判断api version,如果在Lollipop以上,就是用view自带的方法,否则判断是否实现了
NestedScrollingParent
接口,去调用接口的方法。

那么具体我们怎么使用这一套机制呢?比如子View这时候我需要通知父view告诉它我有一个嵌套的touch事件需要我们共同处理。那么针对一个只包含scroll交互,它整个工作流是这样的:


一、startNestedScroll

首先子view需要开启整个流程(内部主要是找到合适的能接受nestedScroll的parent),通知父View,我要和你配合处理TouchEvent


二、dispatchNestedPreScroll

在子View的
onInterceptTouchEvent
或者
onTouch
中(一般在MontionEvent.ACTION_MOVE事件里),调用该方法通知父View滑动的距离。该方法的第三第四个参数返回父view消费掉的scroll长度和子View的窗体偏移量。如果这个scroll没有被消费完,则子view进行处理剩下的一些距离,由于窗体进行了移动,如果你记录了手指最后的位置,需要根据第四个参数
offsetInWindow
计算偏移量,才能保证下一次的touch事件的计算是正确的。

如果父view接受了它的滚动参数,进行了部分消费,则这个函数返回true,否则为false。

这个函数一般在子view处理scroll前调用。


三、dispatchNestedScroll

向父view汇报滚动情况,包括子view消费的部分和子view没有消费的部分。

如果父view接受了它的滚动参数,进行了部分消费,则这个函数返回true,否则为false。

这个函数一般在子view处理scroll后调用。


四、stopNestedScroll

结束整个流程。

整个对应流程是这样

子view父view
startNestedScrollonStartNestedScroll、onNestedScrollAccepted
dispatchNestedPreScrollonNestedPreScroll
dispatchNestedScrollonNestedScroll
stopNestedScrollonStopNestedScroll
一般是子view发起调用,父view接受回调。

我们最需要关注的是
dispatchNestedPreScroll
中的
consumed
参数。
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) ;


它是一个
int
型的数组,长度为2,第一个元素是父view消费的
x
方向的滚动距离;第二个元素是父view消费的
y
方向的滚动距离,如果这两个值不为0,则子view需要对滚动的量进行一些修正。正因为有了这个参数,使得我们处理滚动事件的时候,思路更加清晰,不会像以前一样被一堆的滚动参数搞混。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: