当单行 Snackbar 被刷掉时,FAB 没有动画

Posted

技术标签:

【中文标题】当单行 Snackbar 被刷掉时,FAB 没有动画【英文标题】:FAB not animated when one-line Snackbar is swiped away 【发布时间】:2016-07-29 13:39:42 【问题描述】:

我有一个支持FloatingActionButtonCoordinatorLayout,我想在其中显示一个Snackbar..

<android.support.design.widget.CoordinatorLayout
    android:id="@+id/rootView"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_
    android:layout_>

    <RelativeLayout
        android:layout_
        android:layout_>
        ...
    </RelativeLayout>

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab_add_field"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_
        android:layout_
        android:layout_gravity="end|bottom"
        android:layout_margin="@dimen/margin_default"
        android:layout_marginBottom="@dimen/profile_floating_margin_bottom"
        android:layout_marginRight="@dimen/profile_floating_margin_right"
        android:clickable="true"
        android:src="@drawable/icon_plus"
        app:backgroundTint="@color/brand_green"
        app:borderWidth="0dp"
        app:elevation="@dimen/fac_icon_elevation"/>
</android.support.design.widget.CoordinatorLayout>

现在我正在展示这样的 Snackbar

Snackbar.make(mRootView, "Something went wrong", Snackbar.LENGHT_SHORT)
        .show();

当它显示时,FAB 向上滑动,当它(在 LENGHT_SHORT 之后)消失时,FAB 向下滑动,一切正常。

但是,如果我将 Snackbar 滑开,FAB 会向下移动而没有幻灯片动画。它只是闪烁到它的初始位置。

有趣的是,如果 Snackbar 有两条线(无论它是否有动作)并且被滑开,FAB 会以通常的滑动动画正确地动画回到它的位置。

这是android.support.design.widget.CoordinatorLayout 中的错误吗? 或android.support.design.widget.FloatingActionButton?有什么解决方法吗?

【问题讨论】:

你解决了这个问题吗? 【参考方案1】:

不使用基于Matteo Destro's post 的ValueAnimator 的Kotlin 解决方案:

class SnackBarAwareBehavior : CoordinatorLayout.Behavior<View> 

    companion object 
        private const val DURATION = 180
    

    constructor() : super() 

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs) 

    override fun layoutDependsOn(
        parent: CoordinatorLayout,
        child: View,
        dependency: View
    ): Boolean 
        return dependency is Snackbar.SnackbarLayout
    

    override fun onDependentViewChanged(
        parent: CoordinatorLayout,
        child: View,
        dependency: View
    ): Boolean 
        if (dependency.visibility == View.VISIBLE) 
            moveChildUp(child, dependency.height + dependency.marginBottom)
            return true
        
        return false
    

    override fun onDependentViewRemoved(parent: CoordinatorLayout, child: View, dependency: View) 
        moveChildToInitialPosition(child)
    

    private fun moveChildUp(child: View, translation: Int) 
        child.animate()
            .translationY((-translation).toFloat())
            .setInterpolator(DecelerateInterpolator())
            .setDuration(DURATION.toLong())
            .start()
    

    private fun moveChildToInitialPosition(child: View) 
        child.animate()
            .translationY(0f)
            .setInterpolator(AccelerateInterpolator())
            .setDuration(DURATION.toLong())
            .start()
    

【讨论】:

【参考方案2】:

我最近遇到了同样的问题,我最终为 FAB 创建了一个自定义布局行为类,以便在轻扫小吃栏时执行动画。

创建这个类:

public class SnackbarAwareBehavior extends CoordinatorLayout.Behavior<View> 

    private static final int ANIMATION_DURATION = 180;

    public SnackbarAwareBehavior() 
        super();
    

    public SnackbarAwareBehavior(Context context, AttributeSet attrs) 
        super(context, attrs);
    

    @Override
    public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull View child, @NonNull View dependency) 
        return dependency instanceof Snackbar.SnackbarLayout;
    

    @Override
    public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent, @NonNull View child, @NonNull View dependency) 
        if (dependency.getVisibility() == View.VISIBLE) 
            // Change the translation of the FAB only if the snackbar is visible
            child.setTranslationY(dependency.getTranslationY() - getTranslationYBottom(dependency));
            return true;
        
        return false;
    

    @Override
    public void onDependentViewRemoved(@NonNull CoordinatorLayout parent, @NonNull View child, @NonNull View dependency) 
        if (dependency.getVisibility() == View.VISIBLE) 
            child.setTranslationY(0f);
         else 
            // Execute the animation only if the snackbar has been swiped away, i.e. when it's not visible
            ValueAnimator animator = new ValueAnimator();
            animator.setFloatValues(child.getTranslationY(), 0f);
            animator.setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR);
            animator.setDuration(ANIMATION_DURATION);
            animator.addUpdateListener(animation -> child.setTranslationY((float) animation.getAnimatedValue()));
            animator.start();
        
    

    private int getTranslationYBottom(View view) 
        // Get the translation position of the snackbar when it's hidden.
        // Method taken from BaseTransientBottomBar.Behavior class.
        int translationY = view.getHeight();
        ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
        if (layoutParams instanceof ViewGroup.MarginLayoutParams) 
            translationY += ((ViewGroup.MarginLayoutParams) layoutParams).bottomMargin;
        
        return translationY;
    


然后在你的布局中:

 <android.support.design.widget.FloatingActionButton
      ...
      app:layout_behavior="your.package.SnackbarAwareBehavior">

【讨论】:

以上是关于当单行 Snackbar 被刷掉时,FAB 没有动画的主要内容,如果未能解决你的问题,请参考以下文章

获取小吃店支持库的高度

小米Java程序员第二轮面试10个问题,你是否会被刷掉?

在 CoordinatorLayout 中安排一个 BottomNavigationView、一个 FAB 和一个 FrameLayout 并使其与 Snackbar 一起使用

浮动操作按钮在 SECOND Snackbar 下消失

自定义类中的 Snackbar 未显示

如何设置小吃栏和浮动操作按钮之间的距离?