Android Design Support Library--FloatingActionButton及其Behavior的使用
引言
如果說前面提到的TextInputLayout、SnackBar的應用還不是很常見的話,那麼今天提到的FloatingActionButton絕對是一個隨處可見的Material Design控制元件了,無論是我們常用的知乎、印象筆記或者是可愛的谷歌全家桶套裝都可以見到FloatingActionButton的身影,今天就來說說FloatingActionButton。
關於使用
其實我相信很多人都用過了Material Design控制元件了,但是還是要說一下,畢竟有些人接觸的晚一些,一些人接觸的早一些,先從最簡單的使用看起:
屬性值 | 作用 |
---|---|
app:elevation | 設定FAB未按下時的景深 |
app:pressedTranslationZ | 設定FAB按下時的景深 |
app:fabSize | 設定FAB的大小,預設只有normal和mini兩種選項 |
app:borderWidth | 設定FAB的邊框寬度 |
android:src | 設定FAB的drawaber |
app:rippleColor | 設定FAB按下時的背景色 |
app:backgroundTint | 設定FAB未按下時的背景色 |
app:layout_anchor | 設定FAB的錨點 |
app:layout_anchorGravity | 設定FAB相對於錨點的位置 |
app:layout_behavior | 設定FAB的Behavior行為屬性 |
大部分的屬性還是很好理解的,這裡要提一下幾個注意的點
- app:borderWidth :這個一般設定為0dp,不然的話在4.1的sdk上FAB會顯示為正方形,而且在5.0以後的sdk沒有陰影效果
-
app:rippleColor:當我使用
com.android.support:design:23.2.0
的時候這個屬性會失效,建議使用最新的com.android.support:design:23.3.0'
或者適當的降低版本 - android:layout_marginBottom :由於FAB 支援庫仍然存在一些 bug,在 Kitkat 和 Lollipop 中分別執行示例程式碼,可以看到如下結果:
Lollipop 中的 FAB:
Kitkat 中的 FAB:
很容易看出,Lollipop 中存在邊緣顯示的問題。為了解決此問題,API21+ 的版本統一定義底部與右邊緣空白為 16dp,Lollipop 以下版本統一設定為 0dp.解決辦法:
values/dimens.xml
<dimen name="fab_margin_right">0dp</dimen>
<dimen name="fab_margin_bottom">0dp</dimen>
values-v21/dimens.xml
<dimen name="fab_margin_right">16dp</dimen>
<dimen name="fab_margin_bottom">16dp</dimen>
佈局檔案的 FAB 中,也設定相應的值:
<android.support.design.widget.FloatingActionButton
...
...
android:layout_marginBottom="@dimen/fab_margin_bottom"
android:layout_marginRight="@dimen/fab_margin_right"/>
-
app:layout_anchor:和
app:layout_anchorGravity
屬性一起搭配使用,可以做出不同的效果:
最簡單的使用
<android.support.design.widget.FloatingActionButton
...
...
app:layout_anchor="@id/mRecycleView"
app:layout_anchorGravity="bottom|right|end"
...
/>
更酷炫的效果
<android.support.design.widget.FloatingActionButton
...
app:layout_anchor="@id/collapsingToolbarLayout"
app:layout_anchorGravity="bottom|center"
...
/>
可以看出我們只要使用app:layout_anchor
屬性設定一個控制元件作為FAB的錨,然後通過app:layout_anchorGravity
屬性放置FAB在這個相對的錨的位置,就能做出你想要的效果。
- app:layout_behavior:這個屬性接下來會重點講,也就是這個屬性成就了Material Design的眾多動畫互動效果,我們熟知的SnackBar配合FAB可以側滑以及APPBarLayout等動畫效果都是通過Behavior做出來的
自定義Behavior
如果你還記得這張圖的話:
或者說你見過這種互動效果:
其實這些都是通過Behavior
這個類做出來的,以上的兩種動畫都是預設自帶的Behavior,在CoordinatorLayout
內部有對Behavior
類的描述:
/**
* Interaction behavior plugin for child views of {@link CoordinatorLayout}.
*
* <p>A Behavior implements one or more interactions that a user can take on a child view.
* These interactions may include drags, swipes, flings, or any other gestures.</p>
*
* @param <V> The View type that this Behavior operates on
*/
public static abstract class Behavior<V extends View> {
可以看到這是一個抽象類,我們可以在各個Material Design去實現這個類,這裡提到FAB,我們可以找一下FAB中的預設Behavior互動的實現:
/**
* Behavior designed for use with {@link FloatingActionButton} instances. It's main function
* is to move {@link FloatingActionButton} views so that any displayed {@link Snackbar}s do
* not cover them.
*/
public static class Behavior extends CoordinatorLayout.Behavior<FloatingActionButton> {
// We only support the FAB <> Snackbar shift movement on Honeycomb and above. This is
// because we can use view translation properties which greatly simplifies the code.
private static final boolean SNACKBAR_BEHAVIOR_ENABLED = Build.VERSION.SDK_INT >= 11;
這裡只貼出一部分,如果英文不差的話看得懂註釋的意思:大致就是說我們這裡只提供API 11以上的Snackbar和FAB的運動互動效果,也就是我們上面動圖中看到的效果:當出現了一個SnackBar時候,FAB會自動向上移動一段距離,當SnackBar消失的時候FAB會回到原來位置,那麼如何定義一個屬於我們自己的Behavior,先來看看需要用到的知識:
其實細分的話有兩種情況:
1、當一個View的變化依賴於另一個View的尺寸、位置等變化的時候,我們只需要關注以下兩種方法:
* @param parent 第一個引數不用解釋吧
* @param 你要依賴別的View的那個View
* @param dependency 你要依賴的View
* @return return 如果找到了你依賴的那個View就返回true
* @see #onDependentViewChanged(CoordinatorLayout, android.view.View, android.view.View)
*/
public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) {
return false;
}
* @param parent 同上,不解釋
* @param child 同上
* @param dependency 同上
* @return 如果這個Behavior改變了child的位置或者尺寸大小就返回true
*/
public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) {
return false;
}
其實FAB裡面就是實現了這兩種方法來與SnackBar互動的,看一下標準寫法:
@Override
public boolean layoutDependsOn(CoordinatorLayout parent,
FloatingActionButton child, View dependency) {
// We're dependent on all SnackbarLayouts (if enabled)
return SNACKBAR_BEHAVIOR_ENABLED && dependency instanceof Snackbar.SnackbarLayout;
}
...
...
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child,
View dependency) {
if (dependency instanceof Snackbar.SnackbarLayout) {
updateFabTranslationForSnackbar(parent, child, dependency);
} else if (dependency instanceof AppBarLayout) {
// If we're depending on an AppBarLayout we will show/hide it automatically
// if the FAB is anchored to the AppBarLayout
updateFabVisibility(parent, (AppBarLayout) dependency, child);
}
return false;
}
2、另一種情況是當一個View監聽CoordinatorLayout內部滑動的View進行互動時,我們需要關注的方法稍微多一點,這些方法都寫在了NestedScrollingParent介面裡面,而且CoordinatorLayout已經對這個介面有了預設實現:
onStartNestedScroll
* @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
* associated with
* @param child the child view of the CoordinatorLayout this Behavior is associated with
* @param directTargetChild the child view of the CoordinatorLayout that either is or
* contains the target of the nested scroll operation
* @param target the descendant view of the CoordinatorLayout initiating the nested scroll
* @param nestedScrollAxes the axes that this nested scroll applies to. See
* {@link ViewCompat#SCROLL_AXIS_HORIZONTAL},
* {@link ViewCompat#SCROLL_AXIS_VERTICAL} 滑動時是橫軸和縱軸
* @return true if the Behavior wishes to accept this nested scroll
*
* @see NestedScrollingParent#onStartNestedScroll(View, View, int)
*/
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout,
V child, View directTargetChild, View target, int nestedScrollAxes) {
return false;
}
onNestedPreScroll
* @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
* associated with
* @param child the child view of the CoordinatorLayout this Behavior is associated with
* @param target the descendant view of the CoordinatorLayout performing the nested scroll
* @param dx the raw horizontal number of pixels that the user attempted to scroll
* @param dy the raw vertical number of pixels that the user attempted to scroll
* @param consumed out parameter. consumed[0] should be set to the distance of dx that
* was consumed, consumed[1] should be set to the distance of dy that
* was consumed
*
* @see NestedScrollingParent#onNestedPreScroll(View, int, int, int[])
*/
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target,
int dx, int dy, int[] consumed) {
// Do nothing
}
onNestedFling
* @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
* associated with
* @param child the child view of the CoordinatorLayout this Behavior is associated with
* @param target the descendant view of the CoordinatorLayout performing the nested scroll
* @param velocityX horizontal velocity of the attempted fling
* @param velocityY vertical velocity of the attempted fling
* @param consumed true if the nested child view consumed the fling
* @return true if the Behavior consumed the fling
*
* @see NestedScrollingParent#onNestedFling(View, float, float, boolean)
*/
public boolean onNestedFling(CoordinatorLayout coordinatorLayout, V child, View target,
float velocityX, float velocityY, boolean consumed) {
return false;
}
onNestedScroll
* @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
* associated with
* @param child the child view of the CoordinatorLayout this Behavior is associated with
* @param target the descendant view of the CoordinatorLayout performing the nested scroll
* @param dxConsumed horizontal pixels consumed by the target's own scrolling operation
* @param dyConsumed vertical pixels consumed by the target's own scrolling operation
* @param dxUnconsumed horizontal pixels not consumed by the target's own scrolling
* operation, but requested by the user
* @param dyUnconsumed vertical pixels not consumed by the target's own scrolling operation,
* but requested by the user
*
* @see NestedScrollingParent#onNestedScroll(View, int, int, int, int)
*/
public void onNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target,
int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
// Do nothing
}
如果是碼農的話上面的英文註釋應該不難吧,這四個方法的區別如下:
- onStartNestedScroll :當你想要初始化一個滑動的時候呼叫
- onNestedPreScroll 和onNestedScroll:存在著兩個方法的原因是一些Behaviors(比如和AppBarLayout使用的)可能會消費掉部分滾動事件,我們可以在onNestedPreScroll方法內部計算需要滾動的距離,具體的話請看這裡
- onNestedScroll:當target正嘗試滑動或者已經滑動時候呼叫這個方法
- onNestedFling:看到Fling就明白是這是Fling情況下呼叫的方法,Fling最直觀的體現是你滑動一個ListView時鬆手的時候ListView還會因為慣性自動滑動一小段距離
這麼看可能太籠統了,看一下這一類Behavior的實際體現,我們自己自定義一個Behavior:
public class FadeBehavior extends FloatingActionButton.Behavior {
/**
* 因為是在XML中使用app:layout_behavior定義靜態的這種行為,
* 必須實現一個建構函式使佈局的效果能夠正常工作。
* 否則 Could not inflate Behavior subclass error messages.
* @param context
* @param attrs
*/
public FadeBehavior(Context context, AttributeSet attrs) {
super();
}
/**
* 處理垂直方向上的滾動事件
*
* @param coordinatorLayout
* @param child
* @param directTargetChild
* @param target
* @param nestedScrollAxes
* @return
*/
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout,
FloatingActionButton child, View directTargetChild, View target, int nestedScrollAxes) {
// Ensure we react to vertical scrolling
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL ||
super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target,
nestedScrollAxes);
}
/**
* 檢查Y的位置,並決定按鈕是否動畫進入或退出
* @param coordinatorLayout
* @param child
* @param target
* @param dxConsumed
* @param dyConsumed
* @param dxUnconsumed
* @param dyUnconsumed
*/
@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 && child.getVisibility() == View.VISIBLE) {
// User scrolled down and the FAB is currently visible -> hide the FAB
child.hide();
} else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) {
// User scrolled up and the FAB is currently not visible -> show the FAB
child.show();
}
}
}
這裡繼承了FAB的Behavior寫了一個我們自己的實現,注意實現自己的Behavior的時候一定要重寫兩個引數的構造方法,因為CoordinatorLayout會從我們在XML中定義的app:layout_behavior屬性去找這個Behavior,瞭解自定義View的對這個應該不會陌生,一般的寫法是:
app:layout_behavior=".FadeBehavior "
在查資料的過程中發現很多人把自定義Behavior類所在的包名也寫進去了,其實親測沒必要這樣做,而且CoordinatorLayout裡面也有專門的方法去解析:
static Behavior parseBehavior(Context context, AttributeSet attrs, String name) {
if (TextUtils.isEmpty(name)) {
return null;
}
final String fullName;
if (name.startsWith(".")) {
// Relative to the app package. Prepend the app package name.
fullName = context.getPackageName() + name;
} else if (name.indexOf('.') >= 0) {
// Fully qualified package name.
fullName = name;
} else {
// Assume stock behavior in this package (if we have one)
fullName = !TextUtils.isEmpty(WIDGET_PACKAGE_NAME)
? (WIDGET_PACKAGE_NAME + '.' + name)
: name;
}
try {
Map<String, Constructor<Behavior>> constructors = sConstructors.get();
if (constructors == null) {
constructors = new HashMap<>();
sConstructors.set(constructors);
}
Constructor<Behavior> c = constructors.get(fullName);
if (c == null) {
final Class<Behavior> clazz = (Class<Behavior>) Class.forName(fullName, true,
context.getClassLoader());
c = clazz.getConstructor(CONSTRUCTOR_PARAMS);
c.setAccessible(true);
constructors.put(fullName, c);
}
return c.newInstance(context, attrs);
} catch (Exception e) {
throw new RuntimeException("Could not inflate Behavior subclass " + fullName, e);
}
}
可以看到用這種方式的系統會自動給我們加上包名,寫太多反而顯的累贅,這個自定義Behavior應該很好理解,效果就是隨著RecycleView的滑動FAB會隱藏/顯示,是一個很常見的效果:
只要向上滾動FAB就會消失,向下滾動FAB就是顯示,這裡要注意的是FAB可以與RecycleView形成這種效果,但是暫時並不支援ListView,沒關係,反正RecycleView當成ListView來用就好,接下來仿照實現知乎的FAB效果的實現,先看一下知乎的效果:
可以很清楚的看到FAB隨著RecycleView的滑動呈現出滾動推出的效果,並且點選FAB會出現旋轉效果並且彈出一個蒙版,我們可以先自定義一個用於執行FAB旋轉的Behavior,可以看到這裡FAB是逆時針旋轉135度,那麼程式碼就可以這麼寫:
public class RotateBehavior extends CoordinatorLayout.Behavior<FloatingActionButton> {
private static final String TAG = RotateBehavior.class.getSimpleName();
public RotateBehavior() {
}
public RotateBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
return dependency instanceof Snackbar.SnackbarLayout;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
float translationY = getFabTranslationYForSnackBar(parent, child);
float percentComplete = -translationY / dependency.getHeight();
child.setRotation(-135 * percentComplete);
child.setTranslationY(translationY);
return true;
}
private float getFabTranslationYForSnackBar(CoordinatorLayout parent,
FloatingActionButton fab) {
float minOffset = 0;
final List<View> dependencies = parent.getDependencies(fab);
for (int i = 0, z = dependencies.size(); i < z; i++) {
final View view = dependencies.get(i);
if (view instanceof Snackbar.SnackbarLayout && parent.doViewsOverlap(fab, view)) {
//view.getHeight()固定為144
//ViewCompat.getTranslationY(view)從144-0,再從0-144
minOffset = Math.min(minOffset,
ViewCompat.getTranslationY(view) - view.getHeight());
Log.d("TranslationY",ViewCompat.getTranslationY(view)+"");
Log.d("Height",view.getHeight()+"");
}
}
return minOffset;
}
}
這裡可能就這段程式碼比較難理解:
minOffset = Math.min(minOffset,
ViewCompat.getTranslationY(view) - view.getHeight());
我在上面打了兩個Log,分別得出了ViewCompat.getTranslationY(view)
和view.getHeight()
,這樣看程式碼就比較容易看懂,但是為什麼ViewCompat.getTranslationY(view)
是正數呢,這裡的的View我們都知道指的是SnackBar,我們都知道向上移動的話getTranslationY
應該是負數啊,其實SnackBar的原始碼中有一個這樣的動作:
ViewCompat.setTranslationY(mView, mView.getHeight());
ViewCompat.animate(mView)
.translationY(0f)
.setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR)
.setDuration(ANIMATION_DURATION)
也就是說SnackBar一開始就向下移動了mView.getHeight()的長度,當SnackBar出現的時候只是向著它原來的位置移動,本質上還是相當於從它原來的位置移動了一段距離,只是這個距離隨著SnackBar向上浮動的越來越多而變得越來越小,直至回到原來的位置,這麼說應該可以理解了,接下來我們在XML檔案中加入一個TextView作為蒙版:
<TextView
android:id="@+id/hide"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffff"
android:visibility="gone" />
因為CoordinatorLayout相當於幀佈局是一層一層疊加的所以這個蒙版放在RecycleView和FAB中間,整個佈局程式碼:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/coor"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/mRecycleView"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v7.widget.RecyclerView>
<TextView
android:id="@+id/hide"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffff"
android:visibility="gone" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/fab_margin_bottom"
android:layout_marginEnd="@dimen/fab_margin_right"
android:src="@mipmap/plus"
app:backgroundTint="#0767C8"
app:borderWidth="0dp"
app:elevation="6dp"
app:fabSize="normal"
app:layout_anchor="@id/mRecycleView"
app:layout_anchorGravity="bottom|right|end"
app:layout_behavior=".FadeBehavior"
app:pressedTranslationZ="12dp"
app:rippleColor="#0767C8" />
</android.support.design.widget.CoordinatorLayout>
看看效果:
是不是有一個很奇怪的地方,知乎的FAB並沒有SnackBar彈出啊,那就說明一開始的思路錯了,但是一個FAB只能設定一個app:layout_behavior
,如果我們把這個Behavior用作FAB的旋轉效果那麼FAB的滾動移出檢視的效果就沒了,所以換一種思路,用Object動畫來做FAB的旋轉效果:
//開始旋轉
public void turnLeft(View v) {
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(v, "rotation", 0, -155, -135);
objectAnimator.setDuration(300);
objectAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
objectAnimator.start();
hide.setVisibility(View.VISIBLE);
AlphaAnimation alphaAnimation = new AlphaAnimation(0, 0.75f);
alphaAnimation.setDuration(300);
alphaAnimation.setFillAfter(true);
hide.startAnimation(alphaAnimation);
hide.setClickable(true);
isOpen = true;
}
//回到起始位置
public void turnRight(View v) {
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(v, "rotation", -135, 20, 0);
objectAnimator.setDuration(300);
objectAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
objectAnimator.start();
hide.setVisibility(View.GONE);
AlphaAnimation alphaAnimation = new AlphaAnimation(0.75f, 0);
alphaAnimation.setDuration(300);
alphaAnimation.setFillAfter(true);
hide.startAnimation(alphaAnimation);
hide.setClickable(false);
isOpen = false;
}
//注:hide就是TextView控制元件(蒙版)
然後實現FAB的滾動移出檢視效果的Behavior:
public class ScrollAwareFABBehavior extends FloatingActionButton.Behavior {
//先慢後快再慢
private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator();
private boolean mIsAnimatingOut = false;
public ScrollAwareFABBehavior(Context context, AttributeSet attrs) {
super();
}
//初始條件
@Override
public boolean onStartNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child,
final View directTargetChild, final View target, final int nestedScrollAxes) {
//垂直滾動
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL
|| super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
}
@Override
public void onNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child,
final View target, final int dxConsumed, final int dyConsumed,
final int dxUnconsumed, final int dyUnconsumed) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
if (dyConsumed > 0 && !this.mIsAnimatingOut && child.getVisibility() == View.VISIBLE) {
// User scrolled down and the FAB is currently visible -> hide the FAB
animateOut(child);
} else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) {
// User scrolled up and the FAB is currently not visible -> show the FAB
animateIn(child);
}
}
// 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) {
//withLayer()使動畫中的某些操作變得更順暢,加速渲染,API 14以後
ViewCompat.animate(button).translationY(button.getHeight() + getMarginBottom(button)).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 {
}
}
// 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).translationY(0)
.setInterpolator(INTERPOLATOR).withLayer().setListener(null)
.start();
} else {
}
}
private int getMarginBottom(View v) {
int marginBottom = 0;
final ViewGroup.LayoutParams layoutParams = v.getLayoutParams();
if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
marginBottom = ((ViewGroup.MarginLayoutParams) layoutParams).bottomMargin;
}
return marginBottom;
}
最後實現的效果:
這裡部分參考了仿知乎FloatingActionButton浮動按鈕動畫效果實現
至於FAB彈出的InBox這裡就不去實現了,比較麻煩,可以參考第三方的實現:
FloatingActionButtonPlus
寫在末尾
主要參考:
浮動操作按鈕的選擇
FloatingActionButton.Behavior
codepath教程:浮動操作按鈕詳解
Design Support Library (II): Floating Action Button
CoordinatorLayout高階用法-自定義Behavior
寫文章不容易,如果可以的話請給個贊
相關文章
- Material Design 之 Behavior 的使用和自定義 BehaviorMaterial Design
- Android初步進階之Design Support Library庫簡單使用(一)Android
- Material Design 控制元件知識梳理(1) Android Design Support Library 是什麼Material Design控制元件Android
- Android Support Annotations 使用詳解Android
- 使用Android Support Annotations優化你的程式碼Android優化
- ThinkPHP3.2 中 behavior 的使用PHP
- Android中IntentService的使用及其原始碼解析AndroidIntent原始碼
- Android註解使用之使用Support Annotations註解優化程式碼Android優化
- 深入淺出Android Support AnnotationsAndroid
- This template depends on the Android Support libraryAndroid
- Error:Conflict with dependency 'com.android.support:support-annotations' in projErrorAndroid
- Android UI進階之旅2 Material Design之RecyclerView的使用AndroidUIMaterial DesignView
- HTTPS 原理淺析及其在 Android 中的使用HTTPAndroid
- 重要的ui元件——BehaviorUI元件
- Android com.android.support衝突解決Android
- Android之Material DesignAndroidMaterial Design
- Android Support 庫各版本功能介紹Android
- Ten Reasons Why Android Should Support OpenCLAndroid
- Android Support Library 學習入門Android
- Android Studio中的外掛ButterKnife的配置及其使用方法Android
- AndroidX 和 Android support 無法共存的問題Android
- com.android.support衝突的解決辦法Android
- Android開發進階——自定義View的使用及其原理探索AndroidView
- Android UI美化之 shape的使用及其屬性總結AndroidUI
- 探索新的Android Material Design支援庫AndroidMaterial Design
- android原始碼檢視 android-support-v4.jarAndroid原始碼JAR
- Android 分享會:Material Design 在 Android 中的應用AndroidMaterial Design
- WPF MenuItem behavior MVVMUIMVVM
- vim 外掛:perl-support的修改和使用
- android.support.v4.app.Fragment vs android.app.Fragment 的區別AndroidAPPFragment
- Android Support Library 的新增功能 | 中文教學視訊Android
- Design Patterns in Android:裝飾模式Android模式
- Android中Design庫之TabLayoutAndroidTabLayout
- 談談 Android Material Design 中的 Tint(著色)AndroidMaterial Design
- Xamarin.Android提示找不到mono.Android.Support.v4AndroidMono
- 開啟Android Studio報錯"required plugin “Android Support” is disabled"AndroidUIPlugin
- Android Material Design控制元件使用(二)——FloatButton TextInputEditText TextInputLayout 按鈕AndroidMaterial Design控制元件
- 提高Android Support Library穩定性的三個關鍵方法Android