处理滑动的基本思路都是基于View
事件传递机制,嵌套滑动(NestedScroll
)机制也不例外。android
大量控件都实现了嵌套滑动机制,有不少源码能够学习。例如RecyclerView
与SwipeRefreshLayout
,下面提供一个使用嵌套滑动机制来实现仿知乎日报效果Demo,效果以下:git
假设两个View
分别为Parent
和Child
,前者是后者的布局容器。github
通常说来,Child
处于主动地位,其滑动将带动Parent
作跟随滑动。在嵌套滑动中优先对Child
的事件作出响应。segmentfault
Parent
虽然处于被动跟随地位,也要有必定自主权,可以决定本身参与Child
发起的嵌套滑动事件。Parent
经常是Child
所处的布局容器,继承ViewGroup
类。数组
嵌套滑动机制的使用十分简单,只须要Parent
和Child
中分别实现两个接口。app
1.Child
实现NestedScrollingChild
接口。
实际上该接口的实现能够彻底使用帮助类NestedScrollingChildHelper
来代理,惟一要处理的是如何在Child
的触摸事件方法中调用该接口的方法。布局
例如使用下列方法开启/关闭嵌套滑动学习
startNestedScroll(int axes);
启动嵌套滑动。spa
stopNestedScroll();
中止嵌套滑动。代理
同时嵌套滑动是一个微小增量过程,使用下列方法在每一步滑动中向Parent
分发消息。
dispatchNestedPreScroll
dispatchNestedScroll()
2.Parent
实现NestedScrollingParent
接口。
该接口中若干方法的实现与具体业务逻辑有关,应该用户本身实现。以下列方法判断父布局控件是否要参与子控件发起的嵌套滑动。
onStartNestedScroll
判断是否参与嵌套滑动。
onNestedScrollAccepted
肯定参与嵌套滑动后将执行,可用做配置初始化。
在嵌套滑动过程当中,要处理Child
所分发的步进事件。
onNestedPreScroll
响应Child
的dispatchNestedPreScroll
方法。
onNestedScroll
响应Child
的dispatchNestedScroll
方法。
在一次嵌套滑动事件中,Parent
和Child
处于问答式交互。以用户触摸事件开端,Child
控件会询问Parent
是否参与滑动;然后在每一次增量滑动先后,两者都会沟通。具体以下图
1.若是在触摸事件中调用了startNestedScroll
方法,实际是由NestedScrollingChildHelper
类实现
public boolean startNestedScroll(int axes) { 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; }
子控件会不断上溯寻找可以响应嵌套滑动事件的父容器,而且会调用父控件的onStartNestedScroll
方法和onNestedScrollAccepted
方法。
2.再看子控件在滑动前的分发事件方法dispatchNestedPreScroll
。
参数dx
与dy
应该是子控件中触摸事件滑动距离,能够计算出来。
consumed
是一个数组,应该以零值传入,表示父控件是否消费了滑动事件。在父控件调用后,若是consumed
不为0,整个方法返回true
,表示父控件消费了滑动事件。
offsetInWindow
表示执行先后子控件在屏幕上的偏移量。
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { //若是开启嵌套滑动,且父控件存在,且滑动有效 mView.getLocationInWindow(offsetInWindow); int startX = offsetInWindow[0]; int startY = offsetInWindow[1]; consumed[0] = 0; consumed[1] = 0; ViewParentCompat.onNestedPreScroll( mNestedScrollingParent, mView, dx, dy, consumed); mView.getLocationInWindow(offsetInWindow); offsetInWindow[0] -= startX; offsetInWindow[1] -= startY; b}
Action Bar的布局以下
<android.support.design.widget.AppBarLayout android:id="@+id/appbar" android:layout_height="wrap_content" android:layout_width="match_parent" android:theme="@style/AppTheme.AppBarOverlay"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:background="?attr/colorPrimaryDark"//须要更改 android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:popupTheme="@style/AppTheme.PopupOverlay"/>
要作到彻底透明,要更改如下属性
1.AppBarLayout背景的透明度
2.Toolbar背景透明度
3.Toolbar背景度
4.此外Toolbar的背景配置不能使用android:background="?attr/colorPrimary"
,必需要换一种近似颜色。
mAppBarLayout.getBackground().setAlpha(alpha); mToolbar.getBackground().setAlpha(alpha); mToolbar.setAlpha(alpha);
此外为了使得Toolbar
不遮挡子控件的内容,应该去掉子控件的behave
属性。
为了去掉AppBar遗留的阴影,应使用以下方法
mAppBarLayout.setElevation(0.1f);
该值越大,阴影效果越明显,但不能设置为0,设置一个较小值便可近似无阴影。