androidandroid-layoutfloating-action-buttonandroid-coordinatorlayoutandroid-snackbar

Floating Action Button Not Floating With Snackbar If anchorView set within CoordinatorLayout


When I set snackbar.anchorView = adContainer my FAB doesn't float.

val snackbar = Snackbar.make(main_content, "Item Deleted", Snackbar.LENGTH_INDEFINITE)
        snackbar.anchorView = adContainer
        snackbar.setAction("Dismiss"){

        }
        snackbar.show()

If I don't set an anchor view it floats ok.

Question - how can I get the FAB to float up and down if I set the snackbar anchor view to be adContainer?

snackbar with anchorview set

snackbar without anchorview

My XML file-

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main_content"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/rootConstraintLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Show Snackbar"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <androidx.constraintlayout.widget.ConstraintLayout
            android:id="@+id/adContainer"
            android:layout_width="match_parent"
            android:layout_height="64dp"
            android:layout_marginStart="1dp"
            android:layout_marginTop="1dp"
            android:layout_marginEnd="1dp"
            android:layout_marginBottom="1dp"
            android:background="#F44336"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/floatingActionButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="end|bottom"
        android:layout_marginBottom="96dp"
        android:layout_marginEnd="32dp"
        android:clickable="true"
        app:layout_anchorGravity="end|bottom"
        app:srcCompat="@android:drawable/ic_input_add"
        android:focusable="true" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

Thanks to @Zain for his answer. With my code below I had managed to get the FAB working except there was no bottom margin. With Zains solution there was a bottom margin.

val snackbar = Snackbar.make(main_content, "Item Deleted", Snackbar.LENGTH_INDEFINITE)
            snackbar.setAction("Dismiss"){

            }
            val snackbarView = snackbar.view
            val params = snackbarView.layoutParams as CoordinatorLayout.LayoutParams
            params.anchorId = R.id.adContainer
            params.bottomMargin = 16// this line didn't work
            params.gravity = Gravity.TOP
            params.anchorGravity = Gravity.TOP
            snackbarView.layoutParams = params
            snackbar.show()

Solution

  • You need to create a custom CoordinatorLayout.Behavior class with FloatingActionButton as the view type that this Behavior operates on

    The credits of below custom java class back to this Repo. I just converted it into Kotlin.

    You can use either the Kotlin or Java versions.

    Kotlin:

    class BottomNavigationFABBehavior constructor(context: Context, attrs: AttributeSet?) :
        CoordinatorLayout.Behavior<FloatingActionButton>(context, attrs) {
    
      override fun layoutDependsOn(
          parent: CoordinatorLayout,
          child: FloatingActionButton,
          dependency: View
      ): Boolean {
            return dependency is SnackbarLayout
        }
    
        override fun onDependentViewRemoved(
            parent: CoordinatorLayout,
            child: FloatingActionButton,
            dependency: View
        ) {
            child.translationY = 0.0f
        }
    
        override fun onDependentViewChanged(
            parent: CoordinatorLayout,
            child: FloatingActionButton,
            dependency: View
        ): Boolean {
            return updateButton(child, dependency)
        }
    
        private fun updateButton(child: View, dependency: View): Boolean {
            return if (dependency is SnackbarLayout) {
                val oldTranslation = child.translationY
                val height = dependency.getHeight().toFloat()
                val newTranslation = dependency.getTranslationY() - height
                child.translationY = newTranslation
                oldTranslation != newTranslation
            } else {
                false
            }
        }
    }
    

    Java

    
    public final class BottomNavigationFABBehavior
            extends CoordinatorLayout.Behavior<FloatingActionButton> {
    
      public BottomNavigationFABBehavior(@Nullable Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
      }
    
      public boolean layoutDependsOn(
              @Nullable CoordinatorLayout parent,
              @NonNull FloatingActionButton child,
              @NonNull View dependency) {
        return dependency instanceof Snackbar.SnackbarLayout;
      }
    
      public void onDependentViewRemoved(
              @NonNull CoordinatorLayout parent,
              @NonNull FloatingActionButton child,
              @NonNull View dependency) {
        child.setTranslationY(0.0f);
      }
    
      public boolean onDependentViewChanged(
              @NonNull CoordinatorLayout parent,
              @NonNull FloatingActionButton child,
              @NonNull View dependency) {
        return this.updateButton(child, dependency);
      }
    
      private boolean updateButton(View child, View dependency) {
        if (dependency instanceof Snackbar.SnackbarLayout) {
          float oldTranslation = child.getTranslationY();
          float height = (float) dependency.getHeight();
          float newTranslation = dependency.getTranslationY() - height;
          child.setTranslationY(newTranslation);
          return oldTranslation != newTranslation;
        } else {
          return false;
        }
      }
    }
    

    Then use this class in your fab app:layout_behavior attribute to make the CoordinatorLayout functions with the needed behavior

    The full layout:

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/main_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <androidx.constraintlayout.widget.ConstraintLayout
            android:id="@+id/rootConstraintLayout"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
            <Button
                android:id="@+id/button"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Show Snackbar"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintHorizontal_bias="0.5"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />
    
            <androidx.constraintlayout.widget.ConstraintLayout
                android:id="@+id/adContainer"
                android:layout_width="match_parent"
                android:layout_height="64dp"
                android:layout_marginStart="1dp"
                android:layout_marginTop="1dp"
                android:layout_marginEnd="1dp"
                android:layout_marginBottom="1dp"
                android:background="#F44336"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent" />
        </androidx.constraintlayout.widget.ConstraintLayout>
    
        <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/floatingActionButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="top"
            app:fabSize="normal"
            app:layout_anchor="@id/adContainer"
            app:layout_anchorGravity="end|top"
            app:layout_behavior=".BottomNavigationFABBehavior"
            app:srcCompat="@android:drawable/ic_input_add"
            app:useCompatPadding="true" />
    
    </androidx.coordinatorlayout.widget.CoordinatorLayout>
    

    And of course you need to keep snackbar.anchorView = adContainer as-is

    Preview