I have a CoordinatorLayout
with support FloatingActionButton
in which I want to display a Snackbar
..
<android.support.design.widget.CoordinatorLayout
android:id="@+id/rootView"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
...
</RelativeLayout>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab_add_field"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
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>
Now I'm showing the Snackbar like this
Snackbar.make(mRootView, "Something went wrong", Snackbar.LENGHT_SHORT)
.show();
When it's displayed, FAB slides up, when it (after LENGHT_SHORT) disappears, FAB slides down, all working fine.
However, if I swipe the Snackbar away, FAB moves down without the slide animation. It just flashes to its initial position.
Interestingly, if the Snackbar has two lines (no matter if it has an action or not) and is swiped away, FAB animates back to its place correctly with the usual slide animation.
Is that a bug in android.support.design.widget.CoordinatorLayout
or android.support.design.widget.FloatingActionButton
? Any workaround for this?
Kotlin solution without using of a ValueAnimator
based on Matteo Destro's post:
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()
}
}
I've encountered the same problem recently and I've ended up creating a custom layout behavior class for the FAB to execute an animation when the snackbar is swiped away.
Create this class:
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;
}
}
Then in your layout:
<android.support.design.widget.FloatingActionButton
...
app:layout_behavior="your.package.SnackbarAwareBehavior">
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With