我想在使用导航组件和底部导航菜单时在工具栏中使用我自己的后退图标。
我已经尝试了所有可能的解决方案,但仍然显示默认的后退图标。
当我写作时
binding.toolbar.setNavigationIcon(R.drawable.ic_arrow_back)
在 onCreate MainActivity 中,会显示该图标,但当我导航到下一个屏幕时,它使用默认值。
我添加了 onDestinationListener
val destinationChangedListener =
NavController.OnDestinationChangedListener { controller, destination, arguments ->
when(destination.id){
R.id.subCategoriesFragment -> {
// Nothing is working
supportActionBar?.setHomeButtonEnabled(true)
supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_arrow_back)
binding.toolbar.setNavigationIcon(R.drawable.ic_arrow_back)
}
else -> {
binding.toolbar.navigationIcon = null
}
}
}
我正在使用谷歌的导航扩展来管理所有底部菜单的后退。
扩展代码如下。
/**
* Manages the various graphs needed for a [BottomNavigationView].
*
* This sample is a workaround until the Navigation Component supports multiple back stacks.
*/
fun BottomNavigationView.setupWithNavController(
navGraphIds: List<Int>,
fragmentManager: FragmentManager,
containerId: Int,
intent: Intent
): LiveData<NavController> {
// Map of tags
val graphIdToTagMap = SparseArray<String>()
// Result. Mutable live data with the selected controlled
val selectedNavController = MutableLiveData<NavController>()
var firstFragmentGraphId = 0
// First create a NavHostFragment for each NavGraph ID
navGraphIds.forEachIndexed { index, navGraphId ->
val fragmentTag = getFragmentTag(index)
// Find or create the Navigation host fragment
val navHostFragment = obtainNavHostFragment(
fragmentManager,
fragmentTag,
navGraphId,
containerId
)
// Obtain its id
val graphId = navHostFragment.navController.graph.id
if (index == 0) {
firstFragmentGraphId = graphId
}
// Save to the map
graphIdToTagMap[graphId] = fragmentTag
// Attach or detach nav host fragment depending on whether it's the selected item.
if (this.selectedItemId == graphId) {
// Update livedata with the selected graph
selectedNavController.value = navHostFragment.navController
attachNavHostFragment(fragmentManager, navHostFragment, index == 0)
} else {
detachNavHostFragment(fragmentManager, navHostFragment)
}
}
// Now connect selecting an item with swapping Fragments
var selectedItemTag = graphIdToTagMap[this.selectedItemId]
val firstFragmentTag = graphIdToTagMap[firstFragmentGraphId]
var isOnFirstFragment = selectedItemTag == firstFragmentTag
// When a navigation item is selected
setOnNavigationItemSelectedListener { item ->
LocalBroadcastManager.getInstance(context).sendBroadcast(Intent(SELECTED_RESELETED_ITEM).apply {
putExtra("item", item.itemId)
})
// Don't do anything if the state is state has already been saved.
if (fragmentManager.isStateSaved) {
false
} else {
val newlySelectedItemTag = graphIdToTagMap[item.itemId]
if (selectedItemTag != newlySelectedItemTag) {
// Pop everything above the first fragment (the "fixed start destination")
fragmentManager.popBackStack(firstFragmentTag,
FragmentManager.POP_BACK_STACK_INCLUSIVE)
val selectedFragment = fragmentManager.findFragmentByTag(newlySelectedItemTag)
as NavHostFragment
// Exclude the first fragment tag because it's always in the back stack.
if (firstFragmentTag != newlySelectedItemTag) {
// Commit a transaction that cleans the back stack and adds the first fragment
// to it, creating the fixed started destination.
fragmentManager.beginTransaction()
.setCustomAnimations(
R.anim.nav_default_enter_anim,
R.anim.nav_default_exit_anim,
R.anim.nav_default_pop_enter_anim,
R.anim.nav_default_pop_exit_anim)
.attach(selectedFragment)
.setPrimaryNavigationFragment(selectedFragment)
.apply {
// Detach all other Fragments
graphIdToTagMap.forEach { _, fragmentTagIter ->
if (fragmentTagIter != newlySelectedItemTag) {
detach(fragmentManager.findFragmentByTag(firstFragmentTag)!!)
}
}
}
.addToBackStack(firstFragmentTag)
.setReorderingAllowed(true)
.commit()
}
selectedItemTag = newlySelectedItemTag
isOnFirstFragment = selectedItemTag == firstFragmentTag
selectedNavController.value = selectedFragment.navController
true
} else {
false
}
}
}
// Optional: on item reselected, pop back stack to the destination of the graph
setupItemReselected(graphIdToTagMap, fragmentManager)
// Handle deep link
setupDeepLinks(navGraphIds, fragmentManager, containerId, intent)
// Finally, ensure that we update our BottomNavigationView when the back stack changes
fragmentManager.addOnBackStackChangedListener {
if (!isOnFirstFragment && !fragmentManager.isOnBackStack(firstFragmentTag)) {
this.selectedItemId = firstFragmentGraphId
}
// Reset the graph if the currentDestination is not valid (happens when the back
// stack is popped after using the back button).
selectedNavController.value?.let { controller ->
if (controller.currentDestination == null) {
controller.navigate(controller.graph.id)
}
}
}
return selectedNavController
}
private fun BottomNavigationView.setupDeepLinks(
navGraphIds: List<Int>,
fragmentManager: FragmentManager,
containerId: Int,
intent: Intent
) {
navGraphIds.forEachIndexed { index, navGraphId ->
val fragmentTag = getFragmentTag(index)
// Find or create the Navigation host fragment
val navHostFragment = obtainNavHostFragment(
fragmentManager,
fragmentTag,
navGraphId,
containerId
)
// Handle Intent
if (navHostFragment.navController.handleDeepLink(intent)
&& selectedItemId != navHostFragment.navController.graph.id) {
this.selectedItemId = navHostFragment.navController.graph.id
}
}
}
private fun BottomNavigationView.setupItemReselected(
graphIdToTagMap: SparseArray<String>,
fragmentManager: FragmentManager
) {
setOnNavigationItemReselectedListener { item ->
LocalBroadcastManager.getInstance(context).sendBroadcast(Intent(SELECTED_RESELETED_ITEM).apply {
putExtra("item", item.itemId)
})
val newlySelectedItemTag = graphIdToTagMap[item.itemId]
val selectedFragment = fragmentManager.findFragmentByTag(newlySelectedItemTag)
as NavHostFragment
val navController = selectedFragment.navController
// Pop the back stack to the start destination of the current navController graph
navController.popBackStack(
navController.graph.startDestination, false
)
}
}
private fun detachNavHostFragment(
fragmentManager: FragmentManager,
navHostFragment: NavHostFragment
) {
fragmentManager.beginTransaction()
.detach(navHostFragment)
.commitNow()
}
private fun attachNavHostFragment(
fragmentManager: FragmentManager,
navHostFragment: NavHostFragment,
isPrimaryNavFragment: Boolean
) {
fragmentManager.beginTransaction()
.attach(navHostFragment)
.apply {
if (isPrimaryNavFragment) {
setPrimaryNavigationFragment(navHostFragment)
}
}
.commitNow()
}
private fun obtainNavHostFragment(
fragmentManager: FragmentManager,
fragmentTag: String,
navGraphId: Int,
containerId: Int
): NavHostFragment {
// If the Nav Host fragment exists, return it
val existingFragment = fragmentManager.findFragmentByTag(fragmentTag) as NavHostFragment?
existingFragment?.let { return it }
// Otherwise, create it and return it.
val navHostFragment = NavHostFragment.create(navGraphId)
fragmentManager.beginTransaction()
.add(containerId, navHostFragment, fragmentTag)
.commitNow()
return navHostFragment
}
private fun FragmentManager.isOnBackStack(backStackName: String): Boolean {
val backStackCount = backStackEntryCount
for (index in 0 until backStackCount) {
if (getBackStackEntryAt(index).name == backStackName) {
return true
}
}
return false
}
private fun getFragmentTag(index: Int) = "bottomNavigation#$index"
任何人都可以帮忙,我如何使用自己的后退图标。
导航组件的开发者没有提供更改图标的方法。
但这可以通过下一种方式实现:
package androidx.navigation.ui;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.graphics.drawable.DrawerArrowDrawable;
import androidx.customview.widget.Openable;
import androidx.navigation.FloatingWindow;
import androidx.navigation.NavController;
import androidx.navigation.NavDestination;
import java.lang.ref.WeakReference;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* The abstract OnDestinationChangedListener for keeping any type of app bar updated.
* This handles both updating the title and updating the Up Indicator, transitioning between
* the drawer icon and up arrow as needed.
* @hide
*/
public abstract class ModifiedAbstractAppBarOnDestinationChangedListener
implements NavController.OnDestinationChangedListener {
private final Context mContext;
private final Set<Integer> mTopLevelDestinations;
@Nullable
private final WeakReference<Openable> mOpenableLayoutWeakReference;
private DrawerArrowDrawable mArrowDrawable;
private ValueAnimator mAnimator;
ModifiedAbstractAppBarOnDestinationChangedListener(@NonNull Context context,
@NonNull AppBarConfiguration configuration) {
mContext = context;
mTopLevelDestinations = configuration.getTopLevelDestinations();
Openable openableLayout = configuration.getOpenableLayout();
if (openableLayout != null) {
mOpenableLayoutWeakReference = new WeakReference<>(openableLayout);
} else {
mOpenableLayoutWeakReference = null;
}
}
protected abstract void setTitle(CharSequence title);
protected abstract void setNavigationIcon(Drawable icon, @StringRes int contentDescription, int destinationId);
@Override
public void onDestinationChanged(@NonNull NavController controller,
@NonNull NavDestination destination, @Nullable Bundle arguments) {
if (destination instanceof FloatingWindow) {
return;
}
Openable openableLayout = mOpenableLayoutWeakReference != null
? mOpenableLayoutWeakReference.get()
: null;
if (mOpenableLayoutWeakReference != null && openableLayout == null) {
controller.removeOnDestinationChangedListener(this);
return;
}
int id = destination.getId();
CharSequence label = destination.getLabel();
if (label != null) {
// Fill in the data pattern with the args to build a valid URI
StringBuffer title = new StringBuffer();
Pattern fillInPattern = Pattern.compile("\\{(.+?)\\}");
Matcher matcher = fillInPattern.matcher(label);
while (matcher.find()) {
String argName = matcher.group(1);
if (arguments != null && arguments.containsKey(argName)) {
matcher.appendReplacement(title, "");
//noinspection ConstantConditions
title.append(arguments.get(argName).toString());
} else {
throw new IllegalArgumentException("Could not find " + argName + " in "
+ arguments + " to fill label " + label);
}
}
matcher.appendTail(title);
setTitle(title);
}
boolean isTopLevelDestination = NavigationUI.matchDestinations(destination,
mTopLevelDestinations);
if (openableLayout == null && isTopLevelDestination) {
setNavigationIcon(null, 0, id);
} else {
setActionBarUpIndicator(openableLayout != null && isTopLevelDestination, id);
}
}
private void setActionBarUpIndicator(boolean showAsDrawerIndicator, int destinationId) {
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,
destinationId);
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);
}
}
}
package androidx.navigation.ui;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.widget.Toolbar;
import androidx.navigation.NavController;
import androidx.navigation.NavDestination;
import androidx.transition.TransitionManager;
import java.lang.ref.WeakReference;
/**
* The OnDestinationChangedListener specifically for keeping a Toolbar updated.
* This handles both updating the title and updating the Up Indicator, transitioning between
* the drawer icon and up arrow as needed.
* @hide
*/
public class ModifiedToolbarOnDestinationChangedListener extends
ModifiedAbstractAppBarOnDestinationChangedListener {
private final WeakReference<Toolbar> mToolbarWeakReference;
public ModifiedToolbarOnDestinationChangedListener(
@NonNull Toolbar toolbar, @NonNull AppBarConfiguration configuration) {
super(toolbar.getContext(), configuration);
mToolbarWeakReference = new WeakReference<>(toolbar);
}
@Override
public void onDestinationChanged(@NonNull NavController controller,
@NonNull NavDestination destination, @Nullable Bundle arguments) {
Toolbar toolbar = mToolbarWeakReference.get();
if (toolbar == null) {
controller.removeOnDestinationChangedListener(this);
return;
}
super.onDestinationChanged(controller, destination, arguments);
}
@Override
protected void setTitle(CharSequence title) {
mToolbarWeakReference.get().setTitle(title);
}
@Override
protected void setNavigationIcon(Drawable icon, @StringRes int contentDescription, int destinationId) {
Toolbar toolbar = mToolbarWeakReference.get();
if (toolbar != null) {
Drawable modifiedIcon = getModifiedIcon(icon, destinationId);
boolean useTransition = modifiedIcon == null && toolbar.getNavigationIcon() != null;
toolbar.setNavigationIcon(modifiedIcon);
toolbar.setNavigationContentDescription(contentDescription);
if (useTransition) {
TransitionManager.beginDelayedTransition(toolbar);
}
}
}
@Nullable
protected Drawable getModifiedIcon(@Nullable Drawable drawable, int destinationId) {
return drawable;
}
}
fun setupWithNavController(toolbar: Toolbar, navController: NavController, configuration: AppBarConfiguration) {
val listener = object : ModifiedToolbarOnDestinationChangedListener(toolbar, configuration) {
override fun getModifiedIcon(drawable: Drawable?, destinationId: Int): Drawable? {
// Return the drawable in depend on destinationId
if (drawable is DrawerArrowDrawable){
drawable.color = Icon.getInstance().iconsColor
}
return drawable
}
}
navController.addOnDestinationChangedListener(listener)
toolbar.setNavigationOnClickListener { NavigationUI.navigateUp(navController, configuration) }
}
如果您使用导航组件并且在 Activity_main.xml 上设置了工具栏,那么这可能会帮助您。要显示自定义的后图标,您可以在每个片段中执行以下操作。
private fun setUpToolbar() {
(requireActivity() as MainActivity).findViewById<MaterialToolbar>(R.id.toolbar).apply {
setNavigationIcon(YOUR_AWSOME_BACK_ICON)
}
}