源码在文章末尾。
引文
有时候我们的页面内容过长的时候,滑动到页面底部用户再滑动到顶部很麻烦,Android不像iOS可以点击statusBar回到顶部,一般都是双击Toolbar/ActionBar或者在底部放一个按钮。
今天就底部放一个回到顶部按钮这个效果来做一个基于Behavior的实现。那么我们传统的方式来做就是监听这个滑动View,比如:ScrollView/ListView/RecyclerView/GridView等,那么如果我们使用了CoordinatorLayout,那么一切将会变得非常so easy。
今天我们就利用自定义Behavior来实现这个回到顶部按钮的渐显的动画效果。如果对我的Behavior博文感兴趣的,那么看官可以在文章顶部找到我其它关于Behavior的博文。
自定义Bahavior的实现?
我们选中CoordinatorLayout.Behavior后ctrl + t后发现有很多实现类,哪么我们选用哪个呢?这里就要看我们要操作(显示/隐藏)的按钮是谁了,到底能不能用系统已经实现了的基类?所以又抛出了以下问题。
自定义Behavior继承系统的哪个BaseBahavior?
这个问题其实就so easy了,只要接触过MD的人肯定听过一个FAB吧,也就是我们的FloatingActionButton了,所以我们这里也使用的是FloatingActionButton,所以我们自定义的Behavior也是基于FloatingActionButton的。
因此我们从中CoordinatorLayout.Behavior后ctrl + t的里面看到一个FloatingActionButton.Behavior,这个家伙就是我们要继承的,利用它来控制FloatingActionButton的显示和隐藏动画。
ScaleUpShowBehavior的实现
因为是向上滑动手指,出现下面部分的页面,显示Button是,所以我们暂且把它叫ScaleUpShowBehavior的实现。
接下来一大波代码来袭,首先我们要继承FloatingActionButton.Behavior:
public class ScaleUpShowBehavior extends FloatingActionButton.Behavior {public ScaleUpShowBehavior(Context context, AttributeSet attrs) {super();}}接下来实现这里面重要的三个方法:
// 页面开始滑动。onStartNestedScroll();// 页面正在滑动。onNestedScroll();// 页面停止滑动。onStopNestedScroll();第一个方法onStartNestedScroll:
if (dyConsumed > 0 && dyUnconsumed == 0) {System.out.println("上滑中。。。");}if (dyConsumed == 0 && dyUnconsumed > 0) {System.out.println("到边界了还在上滑。。。");}if (dyConsumed < 0 && dyUnconsumed == 0) {System.out.println("下滑中。。。");}if (dyConsumed == 0 && dyUnconsumed < 0) {System.out.println("到边界了,还在下滑。。。");}因此我们在的时候上滑,也就是用户需要看页面的下部分的时候显示FAB:
if (((dyConsumed > 0 && dyUnconsumed == 0) || (dyConsumed == 0 && dyUnconsumed > 0))&& child.getVisibility() != View.VISIBLE) {// 显示AnimatorUtil.scaleShow(child, null);}那么相反的,在用户手指下滑,显示页面上半部分的时候隐藏FAB:
if (((dyConsumed < 0 && dyUnconsumed == 0) || (dyConsumed == 0 && dyUnconsumed < 0))&& child.getVisibility() != View.GONE && !isAnimatingOut) {AnimatorUtil.scaleHide(child, viewPropertyAnimatorListener);}那么这里的完整的代码就是:
@Overridepublic void onNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {//if (dyConsumed > 0 && dyUnconsumed == 0) {//System.out.println("上滑中。。。");//}//if (dyConsumed == 0 && dyUnconsumed > 0) {//System.out.println("到边界了还在上滑。。。");//}//if (dyConsumed < 0 && dyUnconsumed == 0) {//System.out.println("下滑中。。。");//}//if (dyConsumed == 0 && dyUnconsumed < 0) {//System.out.println("到边界了,还在下滑。。。");//}if (((dyConsumed > 0 && dyUnconsumed == 0) || (dyConsumed == 0&& dyUnconsumed > 0)) && child.getVisibility() != View.VISIBLE) {// 显示AnimatorUtil.scaleShow(child, null);} else if (((dyConsumed < 0 && dyUnconsumed == 0) || (dyConsumed == 0&& dyUnconsumed < 0)) && child.getVisibility() != View.GONE && !isAnimatingOut) {AnimatorUtil.scaleHide(child, viewPropertyAnimatorListener);}}动画的与FAB显示隐藏的实现
AnimatorUtil.scaleShow();AnimatorUtil.scaleHide();isAnimatingOut;viewPropertyAnimatorListener;这是什么鬼呢?
// 显示viewpublic static void scaleShow(View view, ViewPropertyAnimatorListener viewPropertyAnimatorListener) {view.setVisibility(View.VISIBLE);ViewCompat.animate(view).scaleX(1.0f).scaleY(1.0f).alpha(1.0f).setDuration(800).setListener(viewPropertyAnimatorListener).setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR).start();}AnimatorUtil.scaleHide()用来渐渐隐藏FAB,当然也是我们自定义的动画啦:
// 隐藏viewpublic static void scaleHide(View view, ViewPropertyAnimatorListener viewPropertyAnimatorListener) {ViewCompat.animate(view).scaleX(0.0f).scaleY(0.0f).alpha(0.0f).setDuration(800).setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR).setListener(viewPropertyAnimatorListener).start();}viewPropertyAnimatorListener和isAnimatingOut用来监听隐藏动画的执行,当动画执行完毕后才view.setVisibility(View.GONE);了,这就是套路啊哈哈:
private boolean isAnimatingOut = false;ViewPropertyAnimatorListener viewPropertyAnimatorListener = new ViewPropertyAnimatorListener() {@Overridepublic void onAnimationStart(View view) {isAnimatingOut = true;}@Overridepublic void onAnimationEnd(View view) {isAnimatingOut = false;view.setVisibility(View.GONE);}@Overridepublic void onAnimationCancel(View arg0) {isAnimatingOut = false;}};ScaleUpShowBehavior的使用
<android.support.design.widget.FloatingActionButtonandroid:id="@+id/fab"...app:layout_behavior="@string/scale_up_show_behavior" />当然你也完全在xml布局中直接写这个类的全类名,但是这样子不利于以后修改这个类所在的包:
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent"><android.support.design.widget.AppBarLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:theme="@style/AppTheme.AppBarOverlay"><android.support.v7.widget.Toolbarandroid:id="@+id/toolbar"android:layout_width="match_parent"android:layout_height="?attr/actionBarSize"app:popupTheme="@style/AppTheme.PopupOverlay" /></android.support.design.widget.AppBarLayout><android.support.v7.widget.RecyclerViewandroid:id="@+id/recyclerView"android:layout_width="match_parent"android:layout_height="match_parent"app:layout_behavior="@string/appbar_scrolling_view_behavior" /><android.support.design.widget.FloatingActionButtonandroid:id="@+id/fab"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="bottom|end"android:layout_margin="16dp"android:src="@mipmap/abc_ic_ab_back_top"app:layout_behavior="@string/scale_up_show_behavior"app:layout_scrollFlags="scroll|enterAlways|snap" /></android.support.design.widget.CoordinatorLayout>然后给RecyclerView随便给点数据,跑起来看看哈哈,是不是完美啊?
private boolean isInitializeFAB = false;@Overridepublic void onWindowFocusChanged(boolean hasFocus) {super.onWindowFocusChanged(hasFocus);if (!isInitializeFAB) {isInitializeFAB = true;hideFAB();}}private void hideFAB() {FAB.postDelayed(new Runnable() {@Overridepublic void run() {AnimatorUtil.scaleHide(FAB, new ViewPropertyAnimatorListener() {@Overridepublic void onAnimationStart(View view) {}@Overridepublic void onAnimationEnd(View view) {FAB.setVisibility(View.GONE);}@Overridepublic void onAnimationCancel(View view) {}});}}, 500);}完美啊!