Android嵌套滑动机制NestedScrolling

简介:

这篇文章本来打算写在简书上的,但是由于页面不能富文本和markdown同时支持,看到Gemini大神的文章中酷炫、赏心悦目的效果后果断放弃简书,看文章本来就会枯燥,如果再没有美观的效果,那岂不是要边看边睡? 互联网给了我们这么多选择,那我肯定选择体验最棒的。

具体效果可以对比一下:

  

说到Gemini,我也是这两天因为了解NestedScrolling时接触到的,粗略看了一下资料和文章浏览数,赞! 我的大神!

好,前番就到这了,开始正题NestedScrolling。

之前了解NestedScrolling的时候看过一些博客,其中就包括Gemini的segmentfault,当时看的时候因为不仔细不以为然,最后才发现这篇博客是对NestedScrolling介绍最清楚的,作为惩罚也好膜拜也罢,把本来可以cv过来的博客手动敲一遍,顺便补充一下自己的一些额外理解。

再次感谢Gemini

Android 在发布 Lillipop 版本后,为了更好的用户体验,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主要有四个类需要关注:

以上四个类都在support-v4包中提供,Lollipop中部分View默认实现了NestedScrollingChild或NestedScrollingParent。

v4包中NestedScrollView同时实现了NestedScrollingChild和NestedScrollingParent。

一般实现NestedScrollingChild就可以了,父View用support-design提供的实现了NestedScrollingParent的CoordinatorLayout即可。

 
 
  1. @Override 
  2.     public void setNestedScrollingEnabled(boolean enabled) { 
  3.         super.setNestedScrollingEnabled(enabled); 
  4.         mChildHelper.setNestedScrollingEnabled(enabled); 
  5.     } 
  6.  
  7.     @Override 
  8.     public boolean isNestedScrollingEnabled() { 
  9.         return mChildHelper.isNestedScrollingEnabled(); 
  10.     } 
  11.  
  12.     @Override 
  13.     public boolean startNestedScroll(int axes) { 
  14.         return mChildHelper.startNestedScroll(axes); 
  15.     } 
  16.  
  17.     @Override 
  18.     public void stopNestedScroll() { 
  19.         mChildHelper.stopNestedScroll(); 
  20.     } 
  21.  
  22.     @Override 
  23.     public boolean hasNestedScrollingParent() { 
  24.         return mChildHelper.hasNestedScrollingParent(); 
  25.     } 
  26.  
  27.     @Override 
  28.     public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) { 
  29.         return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow); 
  30.     } 
  31.  
  32.     @Override 
  33.     public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { 
  34.         return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow); 
  35.     } 
  36.  
  37.     @Override 
  38.     public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { 
  39.         return mChildHelper.dispatchNestedFling(velocityX, velocityY, consumed); 
  40.     } 
  41.  
  42.     @Override 
  43.     public boolean dispatchNestedPreFling(float velocityX, float velocityY) { 
  44.         return mChildHelper.dispatchNestedPreFling(velocityX, velocityY); 
  45.     } 

简单逻辑这样就可以实现嵌套滑动。

以上接口都是业务逻辑中自己调用,NestedScrollingChildHelper是如何实现的呢? 先看一下startNestedScroll方法

 
 
  1. /** 
  2.      * Start a new nested scroll for this view
  3.      * 
  4.      * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass 
  5.      * method/{@link NestedScrollingChild} interface method with the same signature to implement 
  6.      * the standard policy.</p> 
  7.      * 
  8.      * @param axes Supported nested scroll axes. 
  9.      *             See {@link NestedScrollingChild#startNestedScroll(int)}. 
  10.      * @return true if a cooperating parent view was found and nested scrolling started successfully 
  11.      */ 
  12.     public boolean startNestedScroll(int axes) { 
  13.         if (hasNestedScrollingParent()) { 
  14.             // Already in progress 
  15.             return true
  16.         } 
  17.         if (isNestedScrollingEnabled()) { 
  18.             ViewParent p = mView.getParent(); 
  19.             View child = mView; 
  20.             while (p != null) { 
  21.                 if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes)) { 
  22.                     mNestedScrollingParent = p; 
  23.                     ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes); 
  24.                     return true
  25.                 } 
  26.                 if (p instanceof View) { 
  27.                     child = (View) p; 
  28.                 } 
  29.                 p = p.getParent(); 
  30.             } 
  31.         } 
  32.         return false
  33.     } 

可以看到这里是帮你实现了与NestedScrollingParent交互的一些方法。

ViewParentCompat是一个和父View交互的兼容类,判断API version,如果在Lollipop上就调用View自带的方法,否则判断如果实现了NestedScrollingParent,则调用实现接口的方法。

子View与父View的交互流程如下:

一、startNestedScroll

首先子View需要开启整个流程(通过屏幕滑动触发touch事件),通过NestedScrollingChildHelper找到并通知实现了NestedScrollingParent的父View中onStartNestedScroll和onNestedScrollAccepted方法。

二、dispatchNestedPreScroll

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

如果父View接受了滚动参数并部分消费,则该函数返回true,否则返回false。该函数一般在子View处理Scroll前调用。

三、dispatchNestedScroll

向父View汇报滚动情况,包括子View已消费和未消费的值。

如果父View接受了滚动参数,部分消费则函数返回true,否则返回false。

该函数一般在子View处理Scroll后调用。

四、stopNestedScroll

结束整个嵌套滑动流程。

流程中NestedScrollingChild和NestedScrollingParent对应如下:

NestedScrollingChildImpl NestedScrollingParentImpl
onStartNestedScroll onStartNestedScrollonNestedScrollAccepted
dispatchNestedPreScroll onNestedPreScroll
dispatchNestedScroll onNestedScroll
stopNestedScroll onStopNestedScroll

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

需要关注dispatchNestedPreScroll中的consumed参数:

 
 
  1. public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) ; 

该参数是一个int类型的数组,长度为2,第一个元素是父View消费的x轴方向的滚动距离,第二个元素是父View消费的y轴方向的滚动距离,如果两个值均不为0,则表示父View已消费滚动距离,则需要对子View滚动距离进行修正,正因为有该参数,使得处理滚动事件时思路更加清晰,不会像以前一样被一堆滚动参数搞混。

自己理解的NestedScrolling简要流程图(不包含Fling事件及返回值的逻辑):





作者:路一直都在
来源:51CTO
目录
相关文章
|
1月前
|
前端开发 编译器 Android开发
构建高效Android应用:探究Kotlin协程的异步处理机制
【4月更文挑战第2天】在现代移动应用开发中,提供流畅且响应迅速的用户体验是至关重要的。随着Android平台的发展,Kotlin语言凭借其简洁性和功能性编程的特点成为了主流选择之一。特别地,Kotlin协程作为一种新型的轻量级线程管理机制,为开发者提供了强大的异步处理能力,从而显著提升了应用程序的性能和响应速度。本文将深入探讨Kotlin协程在Android中的应用,分析其原理、实现以及如何通过协程优化应用性能。
|
4月前
|
JavaScript Android开发
使用贝叶斯曲线滑动安卓屏幕(autojsPro7)
使用贝叶斯曲线滑动安卓屏幕(autojsPro7)
70 0
|
4月前
|
存储 Java Android开发
Android系统升级的机制概要
Android系统升级的机制概要
46 0
|
3天前
|
Android开发
Android Loader机制
Android Loader机制
10 1
|
1月前
|
API 调度 Android开发
探索Android应用程序的后台运行机制
在移动应用开发中,了解和掌握Android应用程序的后台运行机制至关重要。本文将深入探讨Android平台上应用程序的后台运行原理及其影响因素,包括后台服务、广播接收器、JobScheduler等关键组件,以及如何有效管理后台任务以提升应用性能和用户体验。
19 3
|
9月前
|
移动开发 Android开发
h5滑动底部兼容安卓
h5滑动底部兼容安卓
60 0
|
4月前
|
Android开发 Kotlin 索引
Android Compose——ScrollableTabRow和LazyColumn同步滑动
Android Compose——ScrollableTabRow和LazyColumn同步滑动
102 0
|
5月前
|
Android开发 容器
[Android]View的事件分发机制(源码解析)
[Android]View的事件分发机制(源码解析)
37 0
|
5月前
|
消息中间件 缓存 安全
android开发,使用kotlin学习消息机制Handler
android开发,使用kotlin学习消息机制Handler
94 0
|
5月前
|
安全 Android开发 Kotlin
android开发,使用kotlin学习Android权限机制
android开发,使用kotlin学习Android权限机制
43 0