Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Navigation Architecture Component - how to set/change custom back or hamburger icon with navigation controller?

I am trying to implement the newly introduced Navigation Architecture Component provided with Jetpack. as far it's very cool and useful for managing navigation flow of your app.

I have already setup the basic navigation including drawer layout with the toolbar in MainActivity like this:

class MainActivity : AppCompatActivity() {

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

        val navController = Navigation.findNavController(this, R.id.mainNavFragment)

        // Set up ActionBar
        setSupportActionBar(toolbar)
        NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout)

        // Set up navigation menu
        navigationView.setupWithNavController(navController)
    }

    override fun onSupportNavigateUp(): Boolean {
        return NavigationUI.navigateUp(Navigation.findNavController(this, R.id.mainNavFragment), drawerLayout)
    }
}

With this layout:

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/appbar"
        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"
            app:popupTheme="@style/AppTheme.PopupOverlay"
            app:navigationIcon="@drawable/ic_home_black_24dp"/>

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

    <fragment
        android:id="@+id/mainNavFragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:navGraph="@navigation/nav_main"/>

</LinearLayout>

It works fins. But, the real question is when provided custom design for an app,

how can I set custom icon for hamburger or the back icon?

which is as of now, being handled by NavigatoinController itself.

enter image description here

I already tried options below, but it doesn't work:

app:navigationIcon="@drawable/ic_home_black_24dp" //1
supportActionBar.setHomeAsUpIndicator(R.drawable.ic_android_black_24dp) //2

Thanks!

like image 236
Harry's Lab Avatar asked Oct 30 '18 09:10

Harry's Lab


1 Answers

Actually, I don't know this is a feature or a bug of Google.

Before giving my solution to change a custom back icon. Let see how Google implemented.

Everything starts when we call :

val navController = findNavController(R.id.nav_host_fragment)
        appBarConfiguration = AppBarConfiguration(
            setOf(
                R.id.homeFragment,
                R.id.categoryFragment,
                R.id.cartFragment,
                R.id.myAccountFragment,
                R.id.moreFragment
            ),
        )
 toolbar.setupWithNavController(navController, appBarConfiguration)

setupWithNavController() is an extension of Toolbar.

fun Toolbar.setupWithNavController(
    navController: NavController,
    configuration: AppBarConfiguration = AppBarConfiguration(navController.graph)
) {
    NavigationUI.setupWithNavController(this, navController, configuration)
}

And in the NavigationUI, you can see that Google just listen the change of destination and set a click event when the user click on back button.

public static void setupWithNavController(@NonNull Toolbar toolbar,
            @NonNull final NavController navController,
            @NonNull final AppBarConfiguration configuration) {
        navController.addOnDestinationChangedListener(
                new ToolbarOnDestinationChangedListener(toolbar, configuration));
        toolbar.setNavigationOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                navigateUp(navController, configuration);
            }
        });
    }

And go more detail, you can see this function:

private void setActionBarUpIndicator(boolean showAsDrawerIndicator) {
        boolean animate = true;
        if (mArrowDrawable == null) {
            mArrowDrawable = new DrawerArrowDrawable(mContext);
            // We're setting the initial state, so skip the animation
            animate = false;
        }
        setNavigationIcon(mArrowDrawable, showAsDrawerIndicator
                ? R.string.nav_app_bar_open_drawer_description
                : R.string.nav_app_bar_navigate_up_description);
        float endValue = showAsDrawerIndicator ? 0f : 1f;
        if (animate) {
            float startValue = mArrowDrawable.getProgress();
            if (mAnimator != null) {
                mAnimator.cancel();
            }
            mAnimator = ObjectAnimator.ofFloat(mArrowDrawable, "progress",
                    startValue, endValue);
            mAnimator.start();
        } else {
            mArrowDrawable.setProgress(endValue);
        }
    }

and here is setNavigationIcon:

@Override
    protected void setNavigationIcon(Drawable icon,
            @StringRes int contentDescription) {
        Toolbar toolbar = mToolbarWeakReference.get();
        if (toolbar != null) {
            boolean useTransition = icon == null && toolbar.getNavigationIcon() != null;
            toolbar.setNavigationIcon(icon);
            toolbar.setNavigationContentDescription(contentDescription);
            if (useTransition) {
                TransitionManager.beginDelayedTransition(toolbar);
            }
        }
    }

I just saw that every time a destination change, and the destination is not a root page. Google creates a DrawerArrowDrawable object and then Google set this icon for navigation Icon. I think it is reason why Toolbar doesn't show our custom icon.

At this point, I think we have some solutions to deal with it. One simple solution is add addOnDestinationChangedListener() method in onCreate() method of Activity and whenever the user changes to new page (destination) where we will check and decide whether we will show the custom back icon or not. Like this:

val navController = findNavController(R.id.nav_host_fragment)
        navController.addOnDestinationChangedListener { _, destination, _ ->
          (appBarConfiguration.topLevelDestinations.contains(destination.id)) {
                    toolbar.navigationIcon = null

                } else {
                    toolbar.setNavigationIcon(R.drawable.ic_arrow_back_white)

                }
        }
like image 143
NhatVM Avatar answered Sep 30 '22 15:09

NhatVM