FloatingActionButton滚动时的显示与隐藏小结

来源:互联网 发布:使用数据库 编辑:程序博客网 时间:2024/05/19 13:22

编辑推荐:稀土掘金,这是一个针对技术开发者的一个应用,你可以在掘金上获取最新最优质的技术干货,不仅仅是Android知识、前端、后端以至于产品和设计都有涉猎,想成为全栈工程师的朋友不要错过!

FloatingActionButton的显示和隐藏其实很容易谷歌到,之所以写这篇文章是感觉这个知识点有点让人困惑,可以找到多种实现方式,而且兼容包里的FloatingActionButton还不断的变化。 

基本来说,如果是使用官方的FloatingActionButton,列表滚动时的显示与隐藏都是使用自定义FloatingActionButton.Behavior来实现的。

NestedScroll的实现方式

我们先来看看比较标准的做法:

首先在xml中定义FloatingActionButton

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <!--
  3.   ~ Copyright (C) 2015 The Android Open Source Project
  4.   ~
  5.   ~ Licensed under the Apache License, Version 2.0 (the "License");
  6.   ~ you may not use this file except in compliance with the License.
  7.   ~ You may obtain a copy of the License at
  8.   ~
  9.   ~      http://www.apache.org/licenses/LICENSE-2.0
  10.   ~
  11.   ~ Unless required by applicable law or agreed to in writing, software
  12.   ~ distributed under the License is distributed on an "AS IS" BASIS,
  13.   ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.   ~ See the License for the specific language governing permissions and
  15.   ~ limitations under the License.
  16.   -->
  17.  
  18. <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
  19.     xmlns:app="http://schemas.android.com/apk/res-auto"
  20.     android:id="@+id/main_content"
  21.     android:layout_width="match_parent"
  22.     android:layout_height="match_parent">
  23.  
  24.     <android.support.design.widget.AppBarLayout
  25.         android:id="@+id/appbar"
  26.         android:layout_width="match_parent"
  27.         android:layout_height="wrap_content"
  28.         android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
  29.  
  30.         <android.support.v7.widget.Toolbar
  31.             android:id="@+id/toolbar"
  32.             android:layout_width="match_parent"
  33.             android:layout_height="?attr/actionBarSize"
  34.             android:background="?attr/colorPrimary"
  35.             app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
  36.             app:layout_scrollFlags="scroll|enterAlways|snap" />
  37.  
  38.         <android.support.design.widget.TabLayout
  39.             android:id="@+id/tabs"
  40.             android:layout_width="match_parent"
  41.             android:layout_height="wrap_content"
  42.             app:tabGravity="fill"
  43.          />
  44.  
  45.     </android.support.design.widget.AppBarLayout>
  46.  
  47.     <android.support.v4.view.ViewPager
  48.         android:id="@+id/viewpager"
  49.         android:layout_width="match_parent"
  50.         android:layout_height="match_parent"
  51.         app:layout_behavior="@string/appbar_scrolling_view_behavior" />
  52.  
  53.     <android.support.design.widget.FloatingActionButton
  54.         app:fabSize="normal"
  55.         android:layout_width="wrap_content"
  56.         android:layout_height="wrap_content"
  57.         android:layout_gravity="end|bottom"
  58.         android:layout_margin="@dimen/fab_margin"
  59.         app:layout_behavior="com.jcodecraeer.fabhideandshow.behavior.ScrollAwareFABBehaviorDefault" />
  60.  
  61. </android.support.design.widget.CoordinatorLayout>

我们这里使用的是比较常见的toolbar+tab+viewpager布局。

其中ScrollAwareFABBehaviorDefault是我们自定义的一个Behavior。

  1. public class ScrollAwareFABBehaviorDefault  extends FloatingActionButton.Behavior{
  2.  
  3.     public ScrollAwareFABBehaviorDefault(Context context, AttributeSet attrs) {
  4.         super();
  5.     }
  6.  
  7.     @Override
  8.     public boolean onStartNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child,
  9.                                        final View directTargetChild, final View target, final int nestedScrollAxes) {
  10.         // Ensure we react to vertical scrolling
  11.         return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL
  12.                 || super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
  13.     }
  14.  
  15.     @Override
  16.     public void onNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child,
  17.                                final View target, final int dxConsumed, final int dyConsumed,
  18.                                final int dxUnconsumed, final int dyUnconsumed) {
  19.         super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
  20.         if (dyConsumed > 0 && child.getVisibility() == View.VISIBLE) {
  21.             // User scrolled down and the FAB is currently visible -> hide the FAB
  22.             child.hide();
  23.         } else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) {
  24.             // User scrolled up and the FAB is currently not visible -> show the FAB
  25.             child.show();
  26.         }
  27.     }
  28. }

然后我们在MainActivity里面写好主界面的其它代码-从github api获取数据,显示列表。Fab在列表滚动时候的显示与隐藏就完成了。看效果

Untitled.gif

为什么说这个方法比较官方呢?这是因为在fab显示与隐藏时使用的动画直接借用了FloatingActionButton内置的动画效果,直接在条件恰当的时候调用hide()和show()方法。这两个方法是在22.2.1版本才加进去的,在这之前需要自己写。具体写法参见:http://stackoverflow.com/questions/31381474/menu-and-autohide-floatingactionbutton-of-android-design-support-library 。

然而我并不喜欢这种效果,我更喜欢上下移动的动画方式。

所以我们只能自己定义动画效果了,其实也很简单:

  1. public class ScrollAwareFABBehavior extends FloatingActionButton.Behavior {
  2.     private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator();
  3.     private boolean mIsAnimatingOut = false;
  4.  
  5.     public ScrollAwareFABBehavior(Context context, AttributeSet attrs) {
  6.         super();
  7.     }
  8.  
  9.     @Override
  10.     public boolean onStartNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child,
  11.                                        final View directTargetChild, final View target, final int nestedScrollAxes) {
  12.         // Ensure we react to vertical scrolling
  13.         return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL
  14.                 || super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
  15.     }
  16.  
  17.     @Override
  18.     public void onNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child,
  19.                                final View target, final int dxConsumed, final int dyConsumed,
  20.                                final int dxUnconsumed, final int dyUnconsumed) {
  21.         super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
  22.         if (dyConsumed > 0 && !this.mIsAnimatingOut && child.getVisibility() == View.VISIBLE) {
  23.             // User scrolled down and the FAB is currently visible -> hide the FAB
  24.             animateOut(child);
  25.         } else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) {
  26.             // User scrolled up and the FAB is currently not visible -> show the FAB
  27.             animateIn(child);
  28.         }
  29.     }
  30.  
  31.     // Same animation that FloatingActionButton.Behavior uses to hide the FAB when the AppBarLayout exits
  32.     private void animateOut(final FloatingActionButton button) {
  33.         if (Build.VERSION.SDK_INT >= 14) {
  34.             ViewCompat.animate(button).translationY(button.getHeight() + getMarginBottom(button)).setInterpolator(INTERPOLATOR).withLayer()
  35.                     .setListener(new ViewPropertyAnimatorListener() {
  36.                         public void onAnimationStart(View view) {
  37.                             ScrollAwareFABBehavior.this.mIsAnimatingOut = true;
  38.                         }
  39.  
  40.                         public void onAnimationCancel(View view) {
  41.                             ScrollAwareFABBehavior.this.mIsAnimatingOut = false;
  42.                         }
  43.  
  44.                         public void onAnimationEnd(View view) {
  45.                             ScrollAwareFABBehavior.this.mIsAnimatingOut = false;
  46.                             view.setVisibility(View.GONE);
  47.                         }
  48.                     }).start();
  49.         } else {
  50.  
  51.         }
  52.     }
  53.  
  54.     // Same animation that FloatingActionButton.Behavior uses to show the FAB when the AppBarLayout enters
  55.     private void animateIn(FloatingActionButton button) {
  56.         button.setVisibility(View.VISIBLE);
  57.         if (Build.VERSION.SDK_INT >= 14) {
  58.             ViewCompat.animate(button).translationY(0)
  59.                     .setInterpolator(INTERPOLATOR).withLayer().setListener(null)
  60.                     .start();
  61.         } else {
  62.  
  63.         }
  64.     }
  65.  
  66.     private int getMarginBottom(View v) {
  67.         int marginBottom = 0;
  68.         final ViewGroup.LayoutParams layoutParams = v.getLayoutParams();
  69.         if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
  70.             marginBottom = ((ViewGroup.MarginLayoutParams) layoutParams).bottomMargin;
  71.         }
  72.         return marginBottom;
  73.     }
  74. }

效果如下:

Untitled.gif

好了,前两种其实都是比较推崇的方式,只是效果不同而已。

Depend的实现方式

下面这种方式也可以实现fab的显示与隐藏,动画接近于上面的第二种效果,但是并不推崇这种方式。

它是这样定义Behavior的:

  1. public class ScrollAwareFABBehaviorDepend  extends FloatingActionButton.Behavior{
  2.     private int toolbarHeight;
  3.  
  4.     public ScrollAwareFABBehaviorDepend(Context context, AttributeSet attrs) {
  5.         super();
  6.         this.toolbarHeight = getToolbarHeight(context);
  7.     }
  8.  
  9.     @Override
  10.     public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton fab, View dependency) {
  11.         return super.layoutDependsOn(parent, fab, dependency) || (dependency instanceof AppBarLayout);
  12.     }
  13.  
  14.     @Override
  15.     public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton fab, View dependency) {
  16.         boolean returnValue = super.onDependentViewChanged(parent, fab, dependency);
  17.         if (dependency instanceof AppBarLayout) {
  18.             CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) fab.getLayoutParams();
  19.             int fabBottomMargin = lp.bottomMargin;
  20.             int distanceToScroll = fab.getHeight() + fabBottomMargin;
  21.             float ratio = (float)dependency.getY()/(float)toolbarHeight;
  22.             fab.setTranslationY(-distanceToScroll * ratio);
  23.         }
  24.         return returnValue;
  25.     }
  26.  
  27.     public static int getToolbarHeight(Context context) {
  28.         final TypedArray styledAttributes = context.getTheme().obtainStyledAttributes(
  29.                 new int[]{R.attr.actionBarSize});
  30.         int toolbarHeight = (int) styledAttributes.getDimension(0, 0);
  31.         styledAttributes.recycle();
  32.  
  33.         return toolbarHeight;
  34.     }
  35. }

它的显示与隐藏是根据AppBarLayout的Y值来决定的,我们知道如果按照最上面的方式定义主界面布局,列表滚动的时候toolbar会显示和隐藏,而toolbar是AppBarLayout的一部分,因此可以让Behavior依赖于AppBarLayout,当AppBarLayout变化的时候会调用onDependentViewChanged,然后在这里获取AppBarLayout的高度移动的距离,然后根据这个距离来判定FloatingActionButton上下移动的距离,从而实现了FloatingActionButton的显示和隐藏。这个实现方式我是在这里找到的:http://stackoverflow.com/questions/31457099/android-fab-to-hide-when-navigating-between-different-fragments-in-a-viewpager 

代码很简单,但是我并不推崇这种方式,原因:

1.由于只是简单的移动了FloatingActionButton的Y值,效果并不好。

Untitled.gif

gif图几乎看不出来和上一种方式的区别。


2.FloatingActionButton依赖于AppBarLayout,如果没有AppBarLayout则会出问题,而且即使有AppBarLayout如果AppBarLayout不会变化也会失效。

3.从逻辑上说FloatingActionButton的显示与隐藏本应该是依赖于列表的滚动的,但是这里却是依赖于AppBarLayout,实际上是列表的滚动让AppBarLayout发生变化,然后AppBarLayout再去通知fab。

不过如果你坚持使用这种方法也没有大问题。

阅读全文
0 1
原创粉丝点击