BottomNavigationView With FloatingActionButton

May 17, 2018

BottomNavigationView implementation usually involve fragment switching. If I need a FloatingActionButton within some of the fragments, there are 2 options:

  • Solution 1: Put FloatingActionButton in the layout of fragment.
  • Solution 2: Put FloatingActionButton in the same activity layout as BottomNavigationView, while sending event between activity and fragment to handle FloatingActionButton event and ui.

XML layout of Activity with BottomNavigationView (Based on Android Prevent BottomNavigationView Cover/Overlap Content/RecyclerView)

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context=".MainActivity">

    <android.support.design.widget.CoordinatorLayout
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true"
        android:layout_above="@+id/navigation">

        <android.support.design.widget.AppBarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:theme="@style/AppTheme.AppBarOverlay">

            <!-- app:layout_scrollFlags="scroll|enterAlways" -->
            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:background="?attr/colorPrimary"
                app:popupTheme="@style/AppTheme.PopupOverlay" />

        </android.support.design.widget.AppBarLayout>

        <!--  -->
        <FrameLayout
            android:id="@+id/content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior"
            />

    </android.support.design.widget.CoordinatorLayout>

    <android.support.design.widget.BottomNavigationView
        android:id="@+id/navigation"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:background="?android:attr/windowBackground"
        app:menu="@menu/navigation"
         />

</RelativeLayout>

Solution 1

Put FloatingActionButton in the layout of fragment is a more straighforward solution. The following layout xml works well.

<android.support.design.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/fragmentContainer"
    android:layout_height="match_parent"
    android:layout_width="match_parent"
    android:name="com.luasoftware.pixpin.view.PhotoPinFragment"
    tools:context=".view.PhotoPinListFragment"
    >

    <android.support.v7.widget.RecyclerView
        android:id="@+id/list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layoutManager="android.support.v7.widget.LinearLayoutManager"
        tools:listitem="@layout/fragment_photopin_list_item"
        />

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="@dimen/fab_margin"
        app:srcCompat="@drawable/ic_photo_camera_black_24dp" />

</android.support.design.widget.CoordinatorLayout>

If I allow appbar/toolbar to hide on scroll (app:layout_scrollFlags="scroll|enterAlways"), the FloatingActionButton is covered by BottomNavigationView when appbar/toolbar is visible (and appear normally above BottomNavigationView when appbar/toolbar is hidden)

BottomNavigationView Cover FAB

NOTE: I tried a few tweaking with app:layout_anchor="@+id/list" and app:layout_anchorGravity="bottom|end" for FloatingActionButton, but it fail to solve this problem.

If I allow appbar/toolbar to hide on scroll (app:layout_scrollFlags="scroll|enterAlways") with ScrollAwareFABBehavior, not only the FloatingActionButton is covered by BottomNavigationView, the bottom part of RecyclerView is covered by BottomNavigationView as well.

Solution 2

Put FloatingActionButton in the same activity layout as BottomNavigationView might seems like a more complicated solution as it involve sending event between activity and fragment for FloatingActionButton, but things are much simpler and predictable layout wise.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context=".MainActivity">

    <android.support.design.widget.CoordinatorLayout
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true"
        android:layout_above="@+id/navigation">

        <android.support.design.widget.AppBarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:theme="@style/AppTheme.AppBarOverlay">

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:background="?attr/colorPrimary"
                app:layout_scrollFlags="scroll|enterAlways" 
                app:popupTheme="@style/AppTheme.PopupOverlay" />

        </android.support.design.widget.AppBarLayout>

        <!--  -->
        <FrameLayout
            android:id="@+id/content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior"
            />


        <android.support.design.widget.FloatingActionButton
            android:id="@+id/fab"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|end"
            android:layout_margin="@dimen/fab_margin"
            app:layout_behavior="com.luasoftware.lualib2.ScrollAwareFABBehavior"
            app:tint="@android:color/white"
            app:srcCompat="@drawable/ic_photo_camera_black_24dp" />

    </android.support.design.widget.CoordinatorLayout>

    <android.support.design.widget.BottomNavigationView
        android:id="@+id/navigation"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:background="?android:attr/windowBackground"
        app:menu="@menu/navigation"
         />

</RelativeLayout>

To handle event passing between activity and fragment, I use MutableLiveData (SingleLiveEvent to be exact).

class MainActivity : AppCompatActivity() {
    
    companion object {
        val fabClickEvent = SingleLiveEvent<Void>()
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        setSupportActionBar(toolbar)

        fab.setOnClickListener {
            fabClickEvent.call()
        }
    }
}
class PhotoPinListFragment : Fragment() {
    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)


        MainActivity.fabClickEvent.observe(this, Observer {
            // do something
        })
    }
}

If you are using a SnackBar within the fragment, make sure the view is CoordinatorLayout in activity to avoid UI overlap between FloatingActionBar and SnackBar.

activity?.let { activity ->
    if (activity is MainActivity) {
        // container is CoordinatorLayout
        val container = activity.container
        Snackbar.make(container, "Hello", Snackbar.LENGTH_INDEFINITE).show()
    }
}
This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License.