android 嵌套分组拖动_浅析:Android 嵌套滑动机制(NestedScrolling)

谷歌在发布安卓 Lollipop版本之后,为了更好的用户体验,Google为Android的滑动机制提供了NestedScrolling特性

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

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

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

dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent。

这种分发机制让移动应用安全检测平台-爱内测(ineice.com)发现有一个漏洞,据爱内测的CTO介绍:

如果子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.

*

*

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.

*

*

@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

startNestedScroll onStartNestedScroll、onNestedScrollAccepted

dispatchNestedPreScroll    onNestedPreScroll

dispatchNestedScroll  onNestedScroll

stopNestedScroll onStopNestedScroll

一般是子view发起调用,父view接受回调。

我们最需要关注的是dispatchNestedPreScroll中的consumed参数。

public

boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[]

offsetInWindow) ;

它是一个int型的数组,长度为2,第一个元素是父view消费的x方向的滚动距离;第二个元素是父view消费的y方向的滚动距离,如果这两个值不为0,则子view需要对滚动的量进行一些修正。正因为有了这个参数,使得我们处理滚动事件的时候,思路更加清晰,不会像以前一样被一堆的滚动参数搞混。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值