Android NestedScrolling 实战
来源:互联网 发布:用mac开发java 编辑:程序博客网 时间:2024/06/08 18:17
从 Android 5.0 Lollipop 开始提供一套 API 来支持嵌入的滑动效果。同样在最新的 Support V4 包中也提供了前向的兼容。有了嵌入滑动机制,就能实现很多很复杂的滑动效果。在Android Design Support 库中非常重要的 CoordinatorLayout
组件就是使用了这套机制,实现了 Toolbar 的收起和展开功能,如下图所示:
NestedScrolling 提供了一套父 View 和子 View 滑动交互机制。要完成这样的交互,父 View 需要实现NestedScrollingParent
接口,而子 View 需要实现 NestedScrollingChild
接口。
实现 NestedScrollingChild
首先来说 NestedScrollingChild
。如果你有一个可以滑动的 View,需要被用来作为嵌入滑动的子 View,就必须实现本接口。在此 View 中,包含一个NestedScrollingChildHelper
辅助类。NestedScrollingChild
接口的实现,基本上就是调用本 Helper 类的对应的函数即可,因为 Helper 类中已经实现好了 Child 和 Parent 交互的逻辑。原来的 View 的处理 Touch 事件,并实现滑动的逻辑大体上不需要改变。
需要做的就是,如果要准备开始滑动了,需要告诉 Parent,你要准备进入滑动状态了,调用 startNestedScroll()
。你在滑动之前,先问一下你的 Parent 是否需要滑动,也就是调用dispatchNestedPreScroll()
。如果父类滑动了一定距离,你需要重新计算一下父类滑动后剩下给你的滑动距离余量。然后,你自己进行余下的滑动。最后,如果滑动距离还有剩余,你就再问一下,Parent 是否需要在继续滑动你剩下的距离,也就是调用dispatchNestedScroll()
。
以上是一些基本原理,有了上面的基本思路,可以参考这篇文章,这里面有原理的详细解析。如果还是不清楚,这里有对应的代码可以参考。
实现 NestedScrollingParent
作为一个可以嵌入 NestedScrollingChild 的父 View,需要实现 NestedScrollingParent
,这个接口方法和NestedScrollingChild
大致有一一对应的关系。同样,也有一个 NestedScrollingParentHelper
辅助类来默默的帮助你实现和 Child 交互的逻辑。滑动动作是 Child 主动发起,Parent 就收滑动回调并作出响应。
从上面的 Child 分析可知,滑动开始的调用 startNestedScroll()
,Parent 收到 onStartNestedScroll()
回调,决定是否需要配合 Child 一起进行处理滑动,如果需要配合,还会回调onNestedScrollAccepted()
。
每次滑动前,Child 先询问 Parent 是否需要滑动,即 dispatchNestedPreScroll()
,这就回调到 Parent 的onNestedPreScroll()
,Parent 可以在这个回调中“劫持”掉 Child 的滑动,也就是先于 Child 滑动。
Child 滑动以后,会调用 onNestedScroll()
,回调到 Parent 的 onNestedScroll()
,这里就是 Child 滑动后,剩下的给 Parent 处理,也就是 后于 Child 滑动。
最后,滑动结束,调用 onStopNestedScroll()
表示本次处理结束。
其实,除了上面的 Scroll 相关的调用和回调,还有 Fling 相关的调用和回调,处理逻辑基本一致。
实战
有了这一套官方的嵌套滑动的解决方案,打算把我的 FlyRefresh 的滑动和下来部分用 NestedScrolling 来实现。我在这篇博客中讲了,之前是通过在PullHeaderLayout
的 dispatchTouchEvent()
中小心处理 Touch 事件来实现的。现在回想起来,这种方法相对复杂,需要清楚知道 Parent 和 Child 的滑动状态,这就导致了,只能支持有限的 Child 类型,例如当时只支持 ListView 和 RecyclerView,为了支持更多的类型,还定义了一个IScrollHandler
接口来支持。
让 FlyRefresh 实现 NestedScrollingParent
,就可以支持所有的 NestedScrollingChild
作为FlyRefreshLayout
的子 View。另外,因为 CoordinatorLayout
是如此的重要,大部分的 App 都需要使用它作为顶层的 Layout,为了让FlyRefreshLayout
能够在 CoordinatorLayout 也能使用,所以我还打算同时实现 NestedScrollingChild
接口。关键实现代码如下:
public class PullHeaderLayout extends ViewGroup implements NestedScrollingParent, NestedScrollingChild { private final int[] mScrollOffset = new int[2]; private final int[] mScrollConsumed = new int[2]; private final NestedScrollingParentHelper mParentHelper; private final NestedScrollingChildHelper mChildHelper; ... // NestedScrollingChild @Override public void setNestedScrollingEnabled(boolean 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); } // NestedScrollingParent @Override public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) { return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0; } @Override public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) { mParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes); startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL); } @Override public void onStopNestedScroll(View target) { stopNestedScroll(); } @Override public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { final int myConsumed = moveBy(dyUnconsumed); final int myUnconsumed = dyUnconsumed - myConsumed; dispatchNestedScroll(0, myConsumed, 0, myUnconsumed, null); } @Override public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) { if (dy > 0 && mHeaderController.canScrollUp()) { final int delta = moveBy(dy); consumed[0] = 0; consumed[1] = delta; //dispatchNestedScroll(0, myConsumed, 0, consumed[1], null); } } @Override public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) { if (!consumed) { flingWithNestedDispatch((int) velocityY); return true; } return false; } private boolean flingWithNestedDispatch(int velocityY) { final boolean canFling = (mHeaderController.canScrollUp() && velocityY > 0) || (mHeaderController.canScrollDown() && velocityY < 0); if (!dispatchNestedPreFling(0, velocityY)) { dispatchNestedFling(0, velocityY, canFling); if (canFling) { fling(velocityY); } } return canFling; } @Override public boolean onNestedPreFling(View target, float velocityX, float velocityY) { return flingWithNestedDispatch((int) velocityY); } @Override public int getNestedScrollAxes() { return mParentHelper.getNestedScrollAxes(); } // Touch event hanlder @Override public boolean onTouchEvent(MotionEvent ev) { MotionEvent vtev = MotionEvent.obtain(ev); final int actionMasked = MotionEventCompat.getActionMasked(ev); if (actionMasked == MotionEvent.ACTION_DOWN) { mNestedYOffset = 0; } vtev.offsetLocation(0, mNestedYOffset); switch (actionMasked) { ... case MotionEvent.ACTION_MOVE: ... final int y = (int) MotionEventCompat.getY(ev, activePointerIndex); int deltaY = mLastMotionY - y; if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset)) { deltaY -= mScrollConsumed[1]; vtev.offsetLocation(0, mScrollOffset[1]); mNestedYOffset += mScrollOffset[1]; } if (!mIsBeingDragged && Math.abs(deltaY) > mTouchSlop) { final ViewParent parent = getParent(); if (parent != null) { parent.requestDisallowInterceptTouchEvent(true); } mIsBeingDragged = true; if (deltaY > 0) { deltaY -= mTouchSlop; } else { deltaY += mTouchSlop; } } if (mIsBeingDragged) { // Scroll to follow the motion event mLastMotionY = y - mScrollOffset[1]; final int scrolledDeltaY = moveBy(deltaY); final int unconsumedY = deltaY - scrolledDeltaY; if (dispatchNestedScroll(0, scrolledDeltaY, 0, unconsumedY, mScrollOffset)) { mLastMotionY -= mScrollOffset[1]; vtev.offsetLocation(0, mScrollOffset[1]); mNestedYOffset += mScrollOffset[1]; } } break; ... } ... return true; } ...}
完整的修改,可以看这个 commit。整个修改下来,代码减少了不少,而且更加整洁了。
总结
总体来说,NestedScroll 初看起来有些让人费解,但是真的理解以后,就发现这种设计的优秀之处。把滑动整体封装起来,通过 Helper 来实现 Child 和 Parent 之间的连接和交互。通过接口来回调,实现了 Child 和 Parent 的逻辑独立。
Android 5.0的大部分可以滑动的控件都支持了 NestScrolling 接口,最新的 Support V4 中也一样,相信以后越来越多的第三方库都会支持,到时候各种控件的嵌套滑动就能无缝集成了。
- Android NestedScrolling 实战
- Android NestedScrolling 实战
- Android NestedScrolling 实战
- Android NestedScrolling 实战
- android NestedScrolling嵌套滑动实战之联合滚动fling效果
- android嵌套滑动NestedScrolling
- Android NestedScrolling 解析
- Android:NestedScrolling机制
- NestedScrolling
- NestedScrolling
- Android NestedScrolling与分发机制
- Android NestedScrolling嵌套滑动机制
- Android NestedScrolling,NestedScrollingParent,NestedScrollingParentHelper解析
- 浅析:Android 嵌套滑动机制(NestedScrolling)
- Android 嵌套滑动机制(NestedScrolling)
- 详解:Android嵌套滑动机制 (NestedScrolling)
- 详解:Android嵌套滑动机制 (NestedScrolling)
- Android 嵌套滑动机制(NestedScrolling)
- 关于某些同行盗用“jeecg”关键词在百度竞价中推广的声明
- spring boot conditionBean MissingBean @configuration 配置不存在问题
- 如何使用AndroidStudio将开源项目library发布到jcenter
- 屏蔽退格键
- Spring3MVC+MyBatis+ExtJs3整合开发系列之二:菜单模块演示
- Android NestedScrolling 实战
- 十五、CentOS下FTP安装及配置
- C++中的extern声明变量详解
- spring aop 做redis缓存
- mybatis
- Spring3MVC+MyBatis+ExtJs3整合开发系列之三:人员管理模块
- JS闭包作用及理解
- oracle的用户、表、表空间三者的关系
- Selenium Automation Browser Driver Download URL.