codepath教程:浮動操作按鈕詳解
概覽
浮動操作按鈕 (簡稱 FAB) 是: “一個特殊的promoted操作案例。因為一個浮動在UI之上的圓形圖標而顯得格外突出,同時它還具有特殊的手勢行為”
比如,如果我們在使用email app,在列出收件箱郵件列表的時候,promoted操作可能就是新建一封郵件。
浮動操作按鈕代表一個屏幕之內最基本的額操作。關于FAB按鈕的更多信息和使用案例請參考谷歌的官方設計規范。
用法
谷歌在2015年的 I/O大會上公布了可以創建浮動操作按鈕的支持庫,但是在這之前,則須使用諸如makovkastar/FloatingActionButton 和 futuresimple/android-floating-action-button 這樣的第三方庫。
Design Support Library
首先確保你按照Design Support Library中的指導來配置。
現在你可以把android.support.design.widget.FloatingActionButton添加到布局中了。其中src屬性指的是浮動按鈕所要的圖標。
<android.support.design.widget.FloatingActionButton android:src="@drawable/ic_done" app:fabSize="normal" android:layout_width="wrap_content" android:layout_height="wrap_content" />
另外,如果在布局的最頂部聲明了xmlns:app="http://schemas.android.com/apk/res-auto命名空間,你還可以定義一個fabSize屬性,該屬性決定按鈕是正常大小還是小號。
放置浮動操作按鈕需要使用CoordinatorLayout。CoordinatorLayout幫助我們協調它所包含的子view之間的交互,這一點在我們后面講如何根據滾動的變化讓按鈕動畫隱藏與顯示的時候有用。但是目前我們能從CoordinatorLayout得到的好處是它可以讓一個元素浮動在另一個元素之上。我們只需讓FloatingActionButton和ListView被包含在CoordinatorLayout中,然后使用layout_anchor 與 layout_anchorGravity 屬性就可以了。
<android.support.design.widget.CoordinatorLayout android:id="@+id/main_content" xmlns:android="<ListView android:id="@+id/lvToDoList" android:layout_width="match_parent" android:layout_height="match_parent"></ListView>
<android.support.design.widget.FloatingActionButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|right" android:layout_margin="16dp" android:src="@drawable/ic_done" app:layout_anchor="@id/lvToDoList" app:layout_anchorGravity="bottom|right|end" /> </android.support.design.widget.CoordinatorLayout></pre>
按鈕應該處于屏幕的右下角。建議在手機上下方的margin設置為16dp而平板上設置為24dp。上面的例子中,使用的是16dp。
而根據谷歌的設計規范,drawable的尺寸應該是24dp。
![]()
浮動操作按鈕的動畫
當用戶往下滾動一個頁面,浮動操作按鈕應該消失,一旦向上滾動,則重現。
![]()
要讓這個過程有動畫效果,你需要利用好CoordinatorLayout,CoordinatorLayout幫助協調定義在里面的view之間的動畫。
用RecyclerView替換ListViews
目前,你需要用RecyclerView來替換ListViews。就如這節所描述的,RecyclerView是ListViews的繼承者。根據谷歌的這篇文章所講的,不支持CoordinatorLayout和ListView一起使用。你可以查看這篇指南,它幫助你過渡到RecyclerView。
<android.support.v7.widget.RecyclerView android:id="@+id/lvToDoList" android:layout_width="match_parent" android:layout_height="match_parent" </android.support.v7.widget.RecyclerView>同時你還必須把RecyclerView升級到v22版本,之前的v21不支持與CoordinatorLayout一起工作,確保你的build.gradle 文件是這樣的:
compile 'com.android.support:recyclerview-v7:22.2.0'使用CoordinatorLayout
接下來,你需要現為浮動操作按鈕實現CoordinatorLayout Behavior。這個類用于定義按鈕該如何響應包含在同一CoordinatorLayout之內的其它view。
創建一個繼承自 FloatingActionButton.Behavior 名叫ScrollAwareFABBehavior.java的類。目前浮動操作按鈕默認的behavior是為Snackbar讓出空間,就如這個視頻中的效果。
我們想繼承這個behavior,暗示我們希望處理垂直方向上的滾動事件:
public class ScrollAwareFABBehavior extends FloatingActionButton.Behavior {@Override public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View directTargetChild, View target, int nestedScrollAxes) { return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL || super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes); }}</pre>
因為這個類要處理滾動,另外一個onNestedScroll() 方法將被調用,我們可以檢查Y的位置,并決定按鈕是否動畫進入或退出:
public class ScrollAwareFABBehavior extends FloatingActionButton.Behavior { // ...@Override public void onNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
if (dyConsumed > 0 && !this.mIsAnimatingOut && child.getVisibility() == View.VISIBLE) { animateOut(child); } else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) { animateIn(child); } }
// ...}</pre>
因為FloatingActionButton.Behavior的基類已經有了animateIn() 和 animateOut()方法,同時它也設置了一個私有變量mIsAnimatingOut,這些方法和變量都是私有的,所以現在我們需要重新實現這些動畫方法。
public class ScrollAwareFABBehavior extends FloatingActionButton.Behavior {private static final android.view.animation.Interpolator INTERPOLATOR = new FastOutSlowInInterpolator(); private boolean mIsAnimatingOut = false;
// Same animation that FloatingActionButton.Behavior uses to // hide the FAB when the AppBarLayout exits private void animateOut(final FloatingActionButton button) { if (Build.VERSION.SDK_INT >= 14) { ViewCompat.animate(button).scaleX(0.0F).scaleY(0.0F).alpha(0.0F) .setInterpolator(INTERPOLATOR).withLayer() .setListener(new ViewPropertyAnimatorListener() { public void onAnimationStart(View view) { ScrollAwareFABBehavior.this.mIsAnimatingOut = true; }
public void onAnimationCancel(View view) { ScrollAwareFABBehavior.this.mIsAnimatingOut = false; }
public void onAnimationEnd(View view) { ScrollAwareFABBehavior.this.mIsAnimatingOut = false; view.setVisibility(View.GONE); } }).start(); } else { Animation anim = AnimationUtils.loadAnimation(button.getContext(), R.anim.fab_out); anim.setInterpolator(INTERPOLATOR); anim.setDuration(200L); anim.setAnimationListener(new Animation.AnimationListener() { public void onAnimationStart(Animation animation) { ScrollAwareFABBehavior.this.mIsAnimatingOut = true; }
public void onAnimationEnd(Animation animation) { ScrollAwareFABBehavior.this.mIsAnimatingOut = false; button.setVisibility(View.GONE); }
@Override public void onAnimationRepeat(final Animation animation) { } }); button.startAnimation(anim); } }
// Same animation that FloatingActionButton.Behavior // uses to show the FAB when the AppBarLayout enters private void animateIn(FloatingActionButton button) { button.setVisibility(View.VISIBLE); if (Build.VERSION.SDK_INT >= 14) { ViewCompat.animate(button).scaleX(1.0F).scaleY(1.0F).alpha(1.0F) .setInterpolator(INTERPOLATOR).withLayer().setListener(null) .start(); } else { Animation anim = AnimationUtils.loadAnimation(button.getContext(), R.anim.fab_in); anim.setDuration(200L); anim.setInterpolator(INTERPOLATOR); button.startAnimation(anim); } } }</pre>
最后一步就是把這個CoordinatorLayout Behavior與浮動操作按鈕聯系起來。我們可以在xml的自定義屬性pp:layout_behavior中定義它:
<android.support.design.widget.FloatingActionButton app:layout_behavior="com.codepath.floatingactionbuttontest.ScrollAwareFABBehavior" />因為我們是在xml中靜態的定義這個behavior,為了讓 layout inflation順利進行,我們必須實現一個構造函數。
public class ScrollAwareFABBehavior extends FloatingActionButton.Behavior { // ...public ScrollAwareFABBehavior(Context context, AttributeSet attrs) { super(); }
// ...}</pre>
如果你忘記實現這個方法,你會看到“Could not inflate Behavior subclass”錯誤信息。完整的用法可以看看這個example code 。
注:通常,當我們實現CoordinatorLayout behavior的時候,我們需要實現ayoutDependsOn() 和 onDependentViewChanged(),它們用于跟蹤CoordinatorLayout中其他view的變化。不過既然我們只需要監控滾動變化,我們就直接使用為浮動操作按鈕定義的現成behavior,就如這篇博客討論的,這個behavior現在被實現來跟蹤Snackbar和AppBarLayout的變化。
注意這里有一個已知的bug :在和RecyclerView使用的時候,如果滾動過快,會觸發NullPointerException,文檔在這里。該問題會在這個庫的下一版本被修復。
使用FloatingActionButton (第三方)
使用makovkastar/FloatingActionButton 庫可以讓浮動操作按鈕的設置變的非常簡單。可以參考library 文檔 以及例子源碼 。
First, add as a dependency to your app/build.gradle:
首先,在app/build.gradle:中添加一個依賴:
dependencies { compile 'com.melnykov:floatingactionbutton:1.2.0' }接下來,在布局中添加com.melnykov.fab.FloatingActionButton 。記得在根布局中屬性中添加xmlns:fab
<FrameLayout xmlns:android="<ListView android:id="@android:id/list" android:layout_width="match_parent" android:layout_height="match_parent" />
<com.melnykov.fab.FloatingActionButton android:id="@+id/fab" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|right" android:layout_margin="16dp" android:src="@drawable/ic_action_content_new" fab:fab_type="normal" fab:fab_shadow="true" fab:fab_colorNormal="@color/primary" fab:fab_colorPressed="@color/primary_pressed" fab:fab_colorRipple="@color/ripple" /> </FrameLayout></pre>
依附到list
接下來,我們可以選擇將FAB和一個ListView, ScrollView 或者 RecyclerView 關聯起來,這樣按鈕就會隨著list的向下滾動而隱藏,向上滾動而重現:
ListView listView = (ListView) findViewById(android.R.id.list); FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); fab.attachToListView(listView); // or attachToRecyclerView我們可以使用fab.attachToRecyclerView(recyclerView)來依附到一個RecyclerView,或者使用fab.attachToScrollView(scrollView)來依附到一個ScrollView。
調整按鈕類型
浮動操作按鈕有兩種大小:默認的,這應該是最常用的情況,以及mini的,這應該只用于銜接屏幕上的其他元素。
注:我認為下圖這種使用實在是太雞肋了。
![]()
我可以把FAB的按鈕類型調整為“正常”或者“mini”
<com.melnykov.fab.FloatingActionButton ... fab:fab_type="mini" />FAB的顯示和隱藏
分別顯示和隱藏按鈕:
// 帶動畫的顯示和隱藏 fab.show(); fab.hide(); // 不帶動畫的 fab.show(false); fab.hide(false);監聽滾動事件
我們可以監聽所關聯的list的滾動事件,以管理FAB的狀態:
FloatingActionButton fab = (FloatingActionButton) root.findViewById(R.id.fab); fab.attachToListView(list, new ScrollDirectionListener() { @Override public void onScrollDown() { Log.d("ListViewFragment", "onScrollDown()"); }@Override public void onScrollUp() { Log.d("ListViewFragment", "onScrollUp()"); } }, new AbsListView.OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { Log.d("ListViewFragment", "onScrollStateChanged()"); }
@Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { Log.d("ListViewFragment", "onScroll()"); } });</pre>
手動實現
除了使用庫之外,我們還可以自己開發動操作按鈕。關于手動實現浮動操作按鈕,可以查看big nerd ranch guide 以及 survivingwithandroid walkthrough。
來自:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0718/3197.html