处理滑动的基本思路都是基于View
事件传递机制,嵌套滑动(NestedScroll
)机制也不例外。
大量控件都实现了嵌套滑动机制,有很多源码可以学习。例如RecyclerView
与SwipeRefreshLayout
,下面提供一个使用嵌套滑动机制来实现仿知乎日报效果Demo,效果如下:
嵌套滑动机制原理(NestedScroll)
假设两个View
分别为Parent
和Child
,前者是后者的布局容器。
一般说来,Child
处于主动地位,其滑动将带动Parent
做跟随滑动。在嵌套滑动中优先对Child
的事件做出响应。
Parent
虽然处于被动跟随地位,也要有一定自主权,能够决定自己参与Child
发起的嵌套滑动事件。Parent
常常是Child
所处的布局容器,继承ViewGroup
类。
嵌套滑动机制的使用十分简单,只需要Parent
和Child
中分别实现两个接口。
1.Child
实现NestedScrollingChild
接口。
实际上该接口的实现可以完全使用帮助类NestedScrollingChildHelper
来代理,唯一要处理的是如何在Child
的触摸事件方法中调用该接口的方法。
例如使用下列方法开启/关闭嵌套滑动
startNestedScroll(int axes);
启动嵌套滑动。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的完全透明
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,设置一个较小值即可近似无阴影。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。