CoordinatorLayout与快速返回的实现
英文原文:Quick return with CoordinatorLayout
本文地址:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0818/3315.html 转载注明出处。
一个可以添加到滚动视图中的漂亮UI元素就是quick return view(快速返回视图)-一个可以在用户向某个方向滚动的时候消失,然后向另外方向滚动时显示的元素。
换句话说就是Google+ app中浮动操作按钮所做的事情。
我现在处于从ListView到RecyclerView的转换过程当中,其中一个需要重新实现的效果就是quick return view之类的效果。
有相当多的热门库通过ListView实现了这个功能,比如 Lars Werkman的。Nick Butcher 和Roman Nurik的滚动技巧 启发了很多这样的库。在design support library发布之前,Makovastar的 FAB 库 是许多需要FAB的应用的选择。这个库提供了连接到滚动视图的功能,让FAB可以在列表向下滚动的时候自动消失。
但是我们的快速返回视图并不是一个FAB,而且我感觉可以不需要像现有解决办法那样的复杂。
剧透:答案是 CoordinatorLayout。
我想起了AppBarLayout是如何利用CoordinatorLayout的 Behavior机制来在nested scroll view滚动的时候折叠Toolbar的。不过我被吓到了,CoordinatorLayout是崭新的东西,而且我以为Behavior很复杂。
但其实Behavior并不复杂。我将演示如何创建一个提供quick return功能的CoordinatorLayout Behavior。
CoordinatorLayout
首先,我们需要一个布局。我们的布局非常简单,只是在一个CoordinatorLayout中包含一个RecyclerView和footer view。
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:text="QuickReturn Footer"/>
</android.support.design.widget.CoordinatorLayout>
接下来,让我们看看CoordinatorLayout.Behavior类。它提供了很多回调方法来接收来自于CoordinatorLayout其它view的事件,好吧,其实是那些你想与之协作的view。一个Behavior与一个特定的View相关-回调中通过“child”引用这个view。许多回调还会传递一个“target” 或者一个 触发该回调的view:“dependency”。
有些方法是为了让CoordinatorLayout知道你的Behavior关心的是什么View,比如,如果你想让一个View基于一个ImageView的表现作什么事情,你可以使用下面的代码重写:
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency instanceof ImageView;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
// Adjust the child View accordingly
return true;
}
Scrolling Behavior
但是,我们的Behavior只关心滚动事件。一个Behavior会自动接收某些事件,而不需要我们去声明任何依赖。
不过,CoordinatorLayout会让Behavior知道它里面任意一个子view滚动何时开始,但是要了解更多的滚动信息的话,我们需要让CoordinatorLayout知道我们关注的是什么:
@Override
public boolean onStartNestedScroll(CoordinatorLayout parent,
View child, View target, View target,int scrollAxes) {
return (scrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
}
我只关心RecyclerView中的垂直滚动,因此只在滚动事件包括垂直信息的时候才返回true。
接下来我们将响应滚动事件。我们有两个回调可以利用:onNestedScroll() 和 onNestedPreScroll()。存在着两个方法的原因是一些Behaviors(比如和AppBarLayout使用的)可能会消费掉部分滚动事件。既然AppBarLayout允许toolbar随着内容的滚动而滚动出去,它就会消费掉任意多的滚动距离向其他view暗示我已经计算了一段滚动距离了。
我想让quick return view的显示和隐藏不受其它view的影响,因此我只需要使用onNestedPreScroll()获取用户滚动的距离就好了。
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int\[\] consumed) {
if (dy > 0 && mDySinceDirectionChange < 0
|| dy < 0 && mDySinceDirectionChange > 0) {
mDySinceDirectionChange = 0;
}
mDySinceDirectionChange += dy;
if (mDySinceDirectionChange > child.getHeight()
&& child.getVisibility() == View.VISIBLE) {
hide(child);
} else if (mDySinceDirectionChange < 0
&& child.getVisibility() == View.GONE) {
show(child);
}
}
这里的代码有点多,我们慢慢道来。
首先,我们的Behavior跟踪和计算自从用户上次改变滚动方向之后,目标view滚动了多少距离。这可以让behavior在用户改变了滑动方向但手指并没有离开的情况下也能正确响应。因为Behavior跟踪的是用户滚动的累加距离,我们可以在隐藏view之前先等待距离超过一个设定的值。在这里这个值就是quick return view的高度。
同时,我还在显示或者隐藏之前检查了child是否可见-一会儿你会知道为什么。
quick return view的显示与隐藏
直接设置quick return的visibility为GONE是可以的,但是我不希望它就像变了戏法似的突然消失了,我想它进入和退出时都有动画效果。
ViewPropertyAnimator可以轻易做到这点:
private void hide(final View view) {
view.animate()
.translationY(view.getHeight())
.setInterpolator(INTERPOLATOR)
.setDuration(200)
.start();
}
private void show(final View view) {
view.animate()
.translationY(0)
.setInterpolator(INTERPOLATOR)
.setDuration(200)
.start();
}
这就是需要做的全部工作!
不过,还有更多的细节需要处理。我告诉过我只想在view可见的时候才去隐藏view,在隐藏的时候才去显示。
if (mDySinceDirectionChange > child.getHeight() && child.getVisibility() == View.VISIBLE) {
hide(child);
} else if (mDySinceDirectionChange < 0 && child.getVisibility() == View.GONE) {
show(child);
}
另一个问题是在动画期间如果用户改变滚动方向Behavior的响应不太正确。为此我们需要在方向改变的时候取消动画:
if (dy > 0 && mDySinceDirectionChange < 0
|| dy < 0 && mDySinceDirectionChange > 0) {
// We detected a direction change- cancel existing animations and reset our cumulative delta Y
child.animate().cancel();
mDySinceDirectionChange = 0;
}
最后,一旦view离开了屏幕,我们可以通过把view的visibility设置成GONE来优化下布局的效果。
为此,我们为animation添加一个AnimatorListener,在animation完成的时候更新view的visibility:
animator.setListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {}
@Override
public void onAnimationEnd(Animator animator) {
// Prevent drawing the View after it is gone
view.setVisibility(View.GONE);
}
@Override
public void onAnimationCancel(Animator animator) {
// Canceling a hide should show the view
show(view);
}
@Override
public void onAnimationRepeat(Animator animator) {}
});
这里是hide() 方法中的animator listener 。show() 方法中类似。
使用 behavior
在quick return behavior发挥作用之前还有最后一个任务!我们需要把Behavior和quick return view关联。
最简单的办法就是在布局中使用app:layout_behavior属性。
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/quickreturn_background"
android:gravity="center"
android:padding="16dp"
android:layout_gravity="bottom"
android:textColor="@android:color/white"
android:text="QuickReturn Footer"
app:layout_behavior=".QuickReturnFooterBehavior"/>
结束
CoordinatorLayout Behavior可以做如此多的事情-Material风格的折叠式toolbar,FloatingActionButton自动为Snackbar让出空间,等等。我强烈推荐熟悉它以便我们自己去创建这些美好的交互。
代码
如果你想试试Behavior或者查看整个工作代码,这里是我在github上的例子.。
最后,如果你认证看了这篇文章,你还可以阅读这篇codepath教程:浮动操作按钮详解 中的“使用CoordinatorLayout”一节,里面的自定义浮动操作按钮Behavior的内容对本文是很好的补充。
--译者注。