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.
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!
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)
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With