Two different menus for Top App Bar and Bottom App bar with Navigation Components
Asked Answered
A

2

6

I was trying out Android Navigation Architecture Component and was also looking into Material design guidelines. I really got inspired by the design below:

Top and bottom app bar

For the top toolbar I can set it by setSupportActionBar(toolbar) and then in MainActivity:

public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.menu, menu);
    return super.onCreateOptionsMenu(menu);
}

But while trying it out I cannot figure it out how to implement menus on both Top and Bottom app bars for different fragments, specially for bottom app bar.

For example, I want to show a favorite icon on bottom app bar only on DetailFragment, but on MainActivity, it should be gone.

My current codes:

MainActivity

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        NavController navController = Navigation.findNavController(this, R.id.nav_host);
        NavigationUI.setupActionBarWithNavController(this, navController);


        FloatingActionButton fab = findViewById(R.id.fab);
        fab.setOnClickListener(view -> Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                .setAction("Action", null).show());


    }

    @Override
    public boolean onSupportNavigateUp() {
        return Navigation.findNavController(this, R.id.nav_host).navigateUp();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu, menu);
        return super.onCreateOptionsMenu(menu);
    }
}

MainFragment

public class MainFragment extends Fragment {

    public MainFragment() {
        // Required empty public constructor
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_main, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        Button buttonOne = view.findViewById(R.id.button_one);
        buttonOne.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.detailFragment));
    }

}

DetailFragment

public class DetailFragment extends Fragment {

    public DetailFragment() {
        // Required empty public constructor
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_detail, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
    }
}

activity_main.xml

<?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:layout_width="match_parent"
    android:layout_height="match_parent"
    android:animateLayoutChanges="true"
    tools:context=".MainActivity">

    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:animateLayoutChanges="true"
        android:theme="@style/AppTheme.AppBarOverlay">

        <androidx.appcompat.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" />

    </com.google.android.material.appbar.AppBarLayout>

    <fragment
        android:id="@+id/nav_host"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="top"
        android:layout_marginTop="?android:attr/actionBarSize"
        app:defaultNavHost="true"
        app:layout_anchor="@id/bottom_appbar"
        app:layout_anchorGravity="top"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        app:navGraph="@navigation/mobile_navigation" />

    <com.google.android.material.bottomappbar.BottomAppBar
        android:id="@+id/bottom_appbar"
        android:layout_width="match_parent"
        android:layout_height="?android:attr/actionBarSize"
        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_appbar" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

mobile_navigation.xml

<navigation 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/mobile_navigation"
    app:startDestination="@id/mainFragment">
    <fragment
        android:id="@+id/mainFragment"
        android:name="com.example.MainFragment"
        android:label="fragment_main"
        tools:layout="@layout/fragment_main" >
        <action
            android:id="@+id/toAccountFragment"
            app:destination="@id/detailFragment" />
    </fragment>
    <fragment
        android:id="@+id/detailFragment"
        android:name="com.example.DetailFragment"
        android:label="fragment_account"
        tools:layout="@layout/fragment_detail" />
</navigation>

menu.xml

<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
        android:id="@+id/app_bar_settings"
        android:title="@string/action_settings"
        app:showAsAction="never" />
</menu>

bottom_appbar_menu.xml for DetialFragment only

<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
        android:id="@+id/action_bottom_fav"
        android:icon="@drawable/ic_favorite"
        android:title="@string/action_favorite"
        app:showAsAction="ifRoom" />
</menu>

Any help is appreciated.


Updated with possible solution:

This is what I'm able to come up with but is not satisfied as I don't know if it's the write way to do it. I'm posting a possible solution:

1- MainActivity

NavController navController = Navigation.findNavController(this, R.id.nav_host);
NavigationUI.setupWithNavController(toolbar, navController);

2- Creating two different menus for bottom app bar (I didn't tried adding menu items dynamically), one with a blank menu xml for MainFragment and another one that contains a favorite icon for DetailFragment.

For simplicity, overriding onCreateOptionsMenu in MainActivity rather than overriding it with the MainFragment:

public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.menu, menu);
    bottomAppBar.replaceMenu(R.menu.bottom_menu_blank);
    return super.onCreateOptionsMenu(menu);
}

3- Thanks to @ʍѳђઽ૯ท for letting me know about replaceMenu method of Bottom App Bar. In DetailFragment use setHasOptionsMenu(true) and override onCreateOptionsMenu:

public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    super.onCreateOptionsMenu(menu, inflater);
    BottomAppBar bottomAppBar = requireActivity().findViewById(R.id.bottom_appbar);
    bottomAppBar.replaceMenu(R.menu.bottom_menu_fav);
}

If anyone has a better way then please do let know.

Arbitrate answered 24/9, 2018 at 16:46 Comment(0)
U
4

Just use onCreateOptionsMenu() for the Toolbar as usual: (Kotlin)

override fun onCreateOptionsMenu(menu: Menu?): Boolean {
        menuInflater.inflate(R.menu.menu_first, menu)
        return super.onCreateOptionsMenu(menu)
    }

Then declare the Toolbar inside onCreate() and use setSupportActionBar():

val toolbar = findViewById<Toolbar>(R.id.myToolbar)
setSupportActionBar(toolbar)

And after that, replaceMenu() will do the trick: (Inside onCreate())

val bottomBar = findViewById<BottomAppBar>(R.id.bottomAppBar)
bottomBar.replaceMenu(R.menu.menu_main)

Note that if you wanted to use BottomSheetFragment for the NavigationView opening, you'll need setSupportActionBar in order to set menus for the BottomAppBar and I couldn't still find a way to fix this.

Untuck answered 24/9, 2018 at 17:32 Comment(2)
Thank you for the input but it's not a full answer, however +1 for this. Full ans? Ex: I have to use BottomAppBar#replaceMenu in on MainActivity's method onCreateOptionsMenu. To show a different menu on a fragment, again I have to use BottomAppBar#replaceMenu with a different menu xml file. To do that, I have to use setHasOptionsMenu(true) and override onCreateOptionsMenu within fragment. However, I was looking for more specific answer if anyone has any other than what I've written. But again, thank you.Arbitrate
That's all of it actually. Your question title asks what I answered and what's related to answer but it seems like you need to add menus inside Fragment. Well, yeah. use setHasOptionsMenu(true) and onCreateOptionsMenu inside the Fragment and it will show what you're looking for. However, I've added a note you may wanna consider reading it.Munday
B
0

To have more than one Toolbar (or BottomAppBar), you will have to inflate the other one manually. When you call setSupportActionBar() and onCreateOptionsMenu(), you are essentially doing this:

private boolean inflateBottomAppBar() {
    BottomAppBar bottomAppBar = findViewById(R.id.bottomAppBar);
    Menu bottomMenu = bottomAppBar.getMenu();
    getMenuInflater().inflate(R.menu.menu_bottom, bottomMenu);
    for (int i = 0; i < bottomMenu.size(); i++) {
        bottomMenu.getItem(i).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
            @Override
            public boolean onMenuItemClick(MenuItem menuItem) {
                return onOptionsItemSelected(menuItem);
            }
        });
    }
    return super.onCreateOptionsMenu(menu);
}

Where R.id.bottomAppBar is the id of the BottomAppBar and R.menu.menu_bottom is the id of the menu items.

Call this method in your onCreateOptionsMenu() after you inflate the main toolbar and you will be good to go. All the item clicks will be handled normally by the onOptionsItemSelected() method.

This will also work if you are making two or more regular toolbars.

Brina answered 8/9, 2019 at 21:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.