Categories UI & UX

How to easily implement Material Design Bottom App Bar for Android

26 Mar. 2019
1
0
20 minutes

Material Design is a set of guides for visual, interaction and motion design that enhance your engineering workflow. Google provides developers with many Material Design Components to build intuitive and beautiful applications. Material Design Bottom App Bar is part of these components. It is a component that displays navigation and key actions at the bottom of the screen. Note that it inherits from the traditional Top App Bar (also known as Toolbar or Action Bar).

The Material Design Bottom App Bar can provide access to a bottom navigation drawer, if it exists. Furthermore, it can contain up to four actions, including the floating action button.

Demonstration

This post focuses on:

  • Bottom App Bar implementation
  • Bottom App Bar menu control
  • Floating Action Button alongside Bottom App Bar
  • Bottom App Bar with Navigation Drawer
  • Bottom App Bar scrolling behavior
Material Design Bottom App Bar demonstration

Video

Setup

First and foremost, you need to add Material Components library to the dependencies section in your gradle file :

dependencies {
    // ...
    implementation 'com.google.android.material:material:1.1.0-alpha04'
    // ...
  }

This library comes along the newly package Androidx. If your app relies on the original Design Support library, you can migrate your project to Androidx using the option provided by Android Studio.

If you want to keep the original Design Support library, you can also use Material Components through this library :

dependencies {
    // ...
    implementation 'com.android.support:design:28.0.0'
    // ...
  }
You should not use the com.android.support and com.google.android.material dependencies in your app at the same time.

Check you are using AppCompatActivity to ensure that all the Material Components work correctly.

Finally, change your app theme to inherit from a Material Components Theme among the following list :

  • Theme.MaterialComponent
  • Theme.MaterialComponents.NoActionBar
  • Theme.MaterialComponents.Light
  • Theme.MaterialComponents.Light.NoActionBar
  • Theme.MaterialComponents.Light.DarkActionBar

The Theme.MaterialComponents.Light is the theme used in the project.

<style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar">
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>
</style>

Alternatively, you can apply the BottomAppBar Material style directly to your widget in XML as follows:

style="@style/Widget.MaterialComponents.BottomAppBar"

Implementation

The BottomAppBar can be included in your layout as follows:

<androidx.coordinatorlayout.widget.CoordinatorLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    <!-- Other components and views -->

    <com.google.android.material.bottomappbar.BottomAppBar
            android:id="@+id/bottom_app_bar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:backgroundTint="@color/colorPrimary"
            android:layout_gravity="bottom"/>
    
</androidx.coordinatorlayout.widget.CoordinatorLayout>

The background color of the Bottom App bar can be customized using
app:backgroundTint attribute.

Menu control

Menu setup

Sometimes you may want to display menu items in the Bottom App Bar as well as in the Toolbar. We will see how to create two different menus for the Bottom App Bar and the Toolbar.

Let’s create two menus in res/menu directory :

  • Toolbar menu
  • Bottom App Bar menu

The menu that will be displayed in the Toolbar, also known as Top App Bar, contains only one item.

<menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
            android:id="@+id/action_settings"
            android:icon="@drawable/ic_settings_black_24dp"
            android:title="@string/settings"
            app:showAsAction="ifRoom"/>
</menu>

As for the menu that will be displayed in the Bottom App Bar, it contains four items in order to have a clear understanding of the Bottom App Bar behavior with many menu items.

<menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
            android:id="@+id/action_search"
            android:icon="@drawable/ic_search_24dp"
            app:tint="@color/colorAccent"
            android:title="@string/search"
            app:showAsAction="ifRoom"/>
    <item
            android:id="@+id/action_movies"
            android:icon="@drawable/ic_movie_black_24dp"
            android:title="@string/movies"
            app:showAsAction="ifRoom"/>
    <item
            android:id="@+id/action_tv_shows"
            android:icon="@drawable/ic_live_tv_black_24dp"
            android:title="@string/tv_shows"
            app:showAsAction="ifRoom"/>
    <item
            android:id="@+id/action_settings"
            android:icon="@drawable/ic_settings_white_24dp"
            android:title="@string/settings"
            app:showAsAction="ifRoom"/>
</menu>

Once the menus are created, we need to inflate them at the right place. In order to achieve this, you need to inflate the top menu using the menu inflater in onCreateOptionsMenu method.

override fun onCreateOptionsMenu(menu: Menu?): Boolean {
    menuInflater.inflate(R.menu.top_menu, menu)
    return true
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.top_menu, menu);
    return true;
}

Afterwards, you need to inflate the bottom menu using replaceMenu method on your BottomAppBar’s instance. As its name suggests, it replaces the content of the BottomAppBar’s menu with the specified menu.

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

     bottom_app_bar.replaceMenu(R.menu.bottom_menu)
}
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    BottomAppBar bottomAppBar = findViewById(R.id.bottom_app_bar);
    bottomAppBar.replaceMenu(R.menu.bottom_menu);
}

Now, a Bottom App Bar should appear at the bottom of the screen with the menu created. Notice that the BottomAppBar’s menu and the Toolbar’s menu are different.

Bottom App Bar with a menu
Note that you can directly inflate your bottom menu through onCreateOptionsMenu method if you have no Toolbar, i.e. your theme extends a NoActionBar theme. In such case, you must also call setSupportActionBar() on the BottomAppBar’s instance

Handling Menu Options

If you have a Toolbar, menu options should be handled as follows:

bottom_app_bar.setOnMenuItemClickListener { item ->
    val menuId = item.itemId

    when (menuId) {
        R.id.action_search -> showSnackbar(getString(R.string.search))
        R.id.action_movies -> showSnackbar(getString(R.string.movies))
        R.id.action_tv_shows -> showSnackbar(getString(R.string.tv_shows))
        R.id.action_settings -> showSnackbar(getString(R.string.settings))
    }

    true
}
bottomAppBar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
    @Override
    public boolean onMenuItemClick(MenuItem item) {
        int menuId = item.getItemId();

        switch (menuId) {
            case R.id.action_search:
                showSnackbar(getString(R.string.search));
                break;
            case R.id.action_movies:
                showSnackbar(getString(R.string.movies));
                break;
            case R.id.action_tv_shows:
                showSnackbar(getString(R.string.tv_shows));
                break;
            case R.id.action_settings:
                showSnackbar(getString(R.string.settings));
                break;
        }
        
        return true;
    }
});

If your app does not have an Action Bar and you followed the instructions written is the note above, menu options should be handled as follows:

override fun onOptionsItemSelected(item: MenuItem?): Boolean {
    val menuId = item.getItemId()

    when (menuId) {
        R.id.action_search -> showSnackbar(getString(R.string.search))
        R.id.action_movies -> showSnackbar(getString(R.string.movies))
        R.id.action_tv_shows -> showSnackbar(getString(R.string.tv_shows))
        R.id.action_settings -> showSnackbar(getString(R.string.settings))
    }

    return true
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
    int menuId = item.getItemId();

    switch (menuId) {
        case R.id.action_search:
            showSnackbar(getString(R.string.search));
            break;
        case R.id.action_movies:
            showSnackbar(getString(R.string.movies));
            break;
        case R.id.action_tv_shows:
            showSnackbar(getString(R.string.tv_shows));
            break;
        case R.id.action_settings:
            showSnackbar(getString(R.string.settings));
            break;
    }

    return true;
}

Here is the method that shows the Snackbar at the right place:

private fun showSnackbar(message: String) {
    Snackbar.make(coordinator_layout, message, Snackbar.LENGTH_SHORT)
        .setAnchorView(if (fab.visibility == View.VISIBLE) fab else bottom_app_bar)
        .show()
}
private void showSnackbar(String message) {
    CoordinatorLayout coordinatorLayout = findViewById(R.id.coordinator_layout);
    Snackbar.make(coordinatorLayout, message, Snackbar.LENGTH_SHORT)
            .setAnchorView(fab.getVisibility() == View.VISIBLE ? fab : bottomAppBar)
            .show();
}
Handling Menu Options

If you would like to go in depth in Material Design Snackbar implementation, you should read this article:

The Bottom App Bar can be coordinated with fragment transactions by replacing the menu using replaceMenu(int) method.

Floating Action Button

A Bottom App Bar is commonly displayed alongside a Floating Action Button in order to highlight this component. The Floating Action Button can be easily anchored to the Bottom App Bar by specifying the id of the Bottom App Bar in app:layout_anchor attribute of the Floating Action Button. It would look like this:

<androidx.coordinatorlayout.widget.CoordinatorLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    <com.google.android.material.bottomappbar.BottomAppBar
            android:id="@+id/bottom_app_bar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:backgroundTint="@color/colorPrimary"
            android:layout_gravity="bottom"/>

    <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/fab"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_anchor="@id/bottom_app_bar"
            app:backgroundTint="@color/colorAccent"
            app:tint="@android:color/white"
            app:srcCompat="@drawable/ic_add_black_24dp"/>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

By default, the Floating Action Button is displayed at the top center of the Bottom App Bar. Furthermore, it is at the same elevation and the bar shape transforms to let the FAB dock in a notch carved into the Bottom App Bar.

Bottom App Bar with FAB

However, you can customize it using the various attributes of the Bottom App Bar.

FeaturesAttributes
FAB Alignment Mode app:fabAlignmentMode
FAB Cradle Margin app:fabCradleMargin
FAB Cradle Corner Radiusapp:fabCradleRoundedCornerRadius
FAB Vertical Offsetapp:fabCradleVerticalOffset

The placement of the Floating Action Button can be controlled using app:fabAlignmentMode attribute or setfabAlignmentMode(int) method. It can either be aligned to the center or to the end of the Bottom App Bar. A default animation runs automatically whenever the alignment changes. Therefore, this can be coordinated with a Fragment transition to display a smooth animation between the screens.

The following media shows the behavior of the Bottom App Bar when the FAB alignment and visibility change.

FAB alignment and visibility change

Here is the related piece of code:

bottom_app_bar.replaceMenu(R.menu.bottom_menu)

fab_alignment_button.setOnClickListener {
    if (bottom_app_bar.fabAlignmentMode == BottomAppBar.FAB_ALIGNMENT_MODE_END) {
        bottom_app_bar.fabAlignmentMode = BottomAppBar.FAB_ALIGNMENT_MODE_CENTER
        fab_alignment_button.setText(R.string.end)
    } else {
        bottom_app_bar.fabAlignmentMode = BottomAppBar.FAB_ALIGNMENT_MODE_END
        fab_alignment_button.setText(R.string.center)
    }
}

fab_visibility_button.setOnClickListener {
    if (fab.visibility == View.VISIBLE) {
        fab.hide()
        fab_visibility_button.setText(R.string.show)
    } else {
        fab.show()
        fab_visibility_button.setText(R.string.hide)
    }
}
final FloatingActionButton fab = findViewById(R.id.fab);
final MaterialButton fabAlignmentButton = findViewById(R.id.fab_alignment_button);
final MaterialButton fabVisibilityButton = findViewById(R.id.fab_visibility_button);
final BottomAppBar bottomAppBar = findViewById(R.id.bottom_app_bar);

bottomAppBar.replaceMenu(R.menu.bottom_menu);

fabAlignmentButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        if(bottomAppBar.getFabAlignmentMode() == BottomAppBar.FAB_ALIGNMENT_MODE_END) {
            bottomAppBar.setFabAlignmentMode(BottomAppBar.FAB_ALIGNMENT_MODE_CENTER);
            fabAlignmentButton.setText(R.string.end);
        } else {
            bottomAppBar.setFabAlignmentMode(BottomAppBar.FAB_ALIGNMENT_MODE_END);
            fabAlignmentButton.setText(R.string.center);
        }
    }
});

fabVisibilityButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        if(fab.getVisibility() == View.VISIBLE) {
            fab.hide();
            fabVisibilityButton.setText(getString(R.string.show));
        } else {
            fab.show();
            fabVisibilityButton.setText(getString(R.string.hide));
        }
    }
});

ThefabCradleMargin attribute corresponds to the distance between the Bottom App Bar and the Floating Action Button. The media below shows how this attribute transforms the Bottom App Bar.

FAB cradle margin change

ThefabCradleRoundedCornerRadius attribute specifies the roundness of the corner around the cutout. The media below shows how this attribute transforms the Bottom App Bar.

FAB cradle rounded corner radius change

As for thefabCradleVerticalOffset attribute, it specifies the vertical offset between the Bottom App Bar and the Floating Action Button. The media below shows how this attribute transforms the Bottom App Bar.

FAB cradle vertical offset change

Navigation Drawer Control

A Bottom App Bar can display a navigation drawer icon to open a bottom navigation drawer. It should show a Bottom Sheet at a higher elevation than the bar. First and foremost, you need to specify the navigation icon of the Bottom App Bar as well as the content description for accessibility.

<com.google.android.material.bottomappbar.BottomAppBar
        android:id="@+id/bottom_app_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:backgroundTint="@color/colorPrimary"
        android:layout_gravity="bottom"
        app:navigationIcon="@drawable/ic_menu_24dp"
        android:contentDescription="@string/show_navigation_drawer"/>

The second step consists in creating the bottom sheet that will display the bottom menu. It should be placed inside the Coordinator Layout.

<FrameLayout
        android:id="@+id/bottom_drawer"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:elevation="16dp"
        app:behavior_hideable="true"
        app:layout_behavior="@string/bottom_sheet_behavior">

    <com.google.android.material.navigation.NavigationView
            android:id="@+id/navigation_view"
            app:itemIconTint="@color/grey"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:menu="@menu/bottom_menu"/>
</FrameLayout>

Once that the bottom sheet is created, the bottom drawer must be initialized in onCreate method. Its visibility state (hidden or expanded) must be stored in order to handle the back button action. Indeed, when a user clicks on the back button whereas the Bottom Sheet is expanded, the normal behaviour would be to close the Bottom Sheet.

private lateinit var bottomDrawerBehavior: BottomSheetBehavior<View>

private fun setupBottomDrawer() {
    bottomDrawerBehavior = BottomSheetBehavior.from(bottom_drawer)
    bottomDrawerBehavior.state = BottomSheetBehavior.STATE_HIDDEN

    bottom_app_bar.setNavigationOnClickListener {
        bottomDrawerBehavior.state = BottomSheetBehavior.STATE_HALF_EXPANDED
    }
}

override fun onBackPressed() {
    if (bottomDrawerBehavior.state != BottomSheetBehavior.STATE_HIDDEN) {
        bottomDrawerBehavior.state = BottomSheetBehavior.STATE_HIDDEN
    } else {
        super.onBackPressed()
    }
}
private BottomSheetBehavior<View> bottomDrawerBehavior;

private void setUpBottomDrawer() {
    View bottomDrawer = coordinatorLayout.findViewById(R.id.bottom_drawer);
    bottomDrawerBehavior = BottomSheetBehavior.from(bottomDrawer);
    bottomDrawerBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);

    bottomAppBar.setNavigationOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            bottomDrawerBehavior.setState(BottomSheetBehavior.STATE_HALF_EXPANDED);
        }
    });
}
    
@Override
public void onBackPressed() {
    if (bottomDrawerBehavior.getState() != BottomSheetBehavior.STATE_HIDDEN) {
        bottomDrawerBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
    } else {
        super.onBackPressed();
    }
}

Here is a demonstration of the bottom navigation:

Bottom Drawer demonstration

Scrolling behavior

By default, BottomAppBar visibility does not change upon scroll. However, it can appear or disappear upon scroll. In such case, the Bottom App Bar should be hidden while scrolling downward. If a Floating Action Button is present, it should be detached from the Bottom App Bar and remains on screen. Scrolling upward should reveal the Bottom App Bar and attache back the Floating Action Button if one is present.

In order to achieve this, the value of app:hideOnScroll attribute should be true.

<com.google.android.material.bottomappbar.BottomAppBar
        android:id="@+id/bottom_app_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:backgroundTint="@color/colorPrimary"
        android:layout_gravity="bottom"
        app:hideOnScroll="true"
        app:navigationIcon="@drawable/ic_menu_24dp"
        android:contentDescription="@string/show_navigation_drawer"/>

Here is the expected scrolling behavior:

Bottom App Bar scrolling behavior

Download

  • Kotlin
  • Java

Conclusion

This concludes our Material Design Bottom App Bar course. We have discussed the basics features of the Material Design Bottom App Bar as well as the advanced features. The Bottom App Bar is really easy to use and can enhance your application look with its fresh style. In addition to offering a nice look, this component improves the navigation flow by highlighting key actions. Furthermore, it allows users to use key features of the application with a single hand on their mobile device. Therefore, the Bottom App Bar enhances ergonomy. Finally, this component offers flexibility in that it can transform according to the context.

Leave a Reply

Your email address will not be published. Required fields are marked *