Navigation Componentを教科書通り、BasicActivityなどのサンプルのまま実装すると、NavigationGraphとActionBarがリンクして、startDestinationに指定したFragmentではActionBarのUPボタンが表示されず、遷移先ではUPボタンが表示されるという動作になります。
FirstFragment | SecondFragment |
---|---|
![]() |
![]() |
しかし、SingleActivityで最初から作る場合はともかく、一部画面からNavigation Componentを適用していこうとした場合など、トップのActivityでもUPボタンが欲しい場合があります。これをどうすれば良いかを調べるのにチョット苦労したのでご紹介
結論
AppBarConfigurationを変更すれば良いです。
サンプルでは以下のようになっていますが、
val navController = findNavController(R.id.nav_host_fragment_content_main)
appBarConfiguration = AppBarConfiguration(navController.graph)
setupActionBarWithNavController(navController, appBarConfiguration)
以下のように変更します。
val navController = findNavController(R.id.nav_host_fragment_content_main)
appBarConfiguration = AppBarConfiguration.Builder().build()
setupActionBarWithNavController(navController, appBarConfiguration)
これだけです。
FirstFragment | SecondFragment |
---|---|
![]() |
![]() |
あとは、navController.navigateUp
がNavigationGraph内でのup操作ができなくなるとfalseが返るので、以下のsuper.onSupportNavigateUp()
の場所に実行したい処理を書きます。
override fun onSupportNavigateUp(): Boolean {
val navController = findNavController(R.id.nav_host_fragment_content_main)
return navController.navigateUp(appBarConfiguration)
|| super.onSupportNavigateUp()
}
どういうことか
setupActionBarWithNavController
の処理を追ってみましょう
fun AppCompatActivity.setupActionBarWithNavController(
navController: NavController,
configuration: AppBarConfiguration = AppBarConfiguration(navController.graph)
) {
NavigationUI.setupActionBarWithNavController(this, navController, configuration)
}
NavigationUI.setupActionBarWithNavController
を呼び出しています。その内容は
public static void setupActionBarWithNavController(@NonNull AppCompatActivity activity,
@NonNull NavController navController,
@NonNull AppBarConfiguration configuration) {
navController.addOnDestinationChangedListener(
new ActionBarOnDestinationChangedListener(activity, configuration));
}
NavController に OnDestinationChangedListener を登録してますね。ActionBarOnDestinationChangedListener
の中を見てみましょう
@RestrictTo(RestrictTo.Scope.LIBRARY)
class ActionBarOnDestinationChangedListener extends
AbstractAppBarOnDestinationChangedListener {
private final AppCompatActivity mActivity;
ActionBarOnDestinationChangedListener(@NonNull AppCompatActivity activity,
@NonNull AppBarConfiguration configuration) {
super(activity.getDrawerToggleDelegate().getActionBarThemedContext(), configuration);
mActivity = activity;
}
@Override
protected void setTitle(CharSequence title) {
ActionBar actionBar = mActivity.getSupportActionBar();
actionBar.setTitle(title);
}
@Override
protected void setNavigationIcon(Drawable icon,
@StringRes int contentDescription) {
ActionBar actionBar = mActivity.getSupportActionBar();
if (icon == null) {
actionBar.setDisplayHomeAsUpEnabled(false);
} else {
actionBar.setDisplayHomeAsUpEnabled(true);
ActionBarDrawerToggle.Delegate delegate = mActivity.getDrawerToggleDelegate();
delegate.setActionBarUpIndicator(icon, contentDescription);
}
}
}
ActionBarのタイトル変更と、今回目的のActionBarのアイコンの変更が行われていますね。まさに今回調べたかったsetDisplayHomeAsUpEnabled
を実行している箇所を見つけました。setNavigationIcon
のiconがnullの時にUpボタンが消えるみたいですね。
親クラスのAbstractAppBarOnDestinationChangedListener
を見てみます。
@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;
}
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);
} else {
setActionBarUpIndicator(openableLayout != null && isTopLevelDestination);
}
}
見つけました、openableLayout がnull、つまり、DrawerLayoutとかがなく、isTopLevelDestinationがtrueの時にiconがnullでコールされていて、ボタンがなくなります。
matchDestinations
が何をやっているかというと、
static boolean matchDestinations(@NonNull NavDestination destination,
@NonNull Set<Integer> destinationIds) {
NavDestination currentDestination = destination;
do {
if (destinationIds.contains(currentDestination.getId())) {
return true;
}
currentDestination = currentDestination.getParent();
} while (currentDestination != null);
return false;
}
はい、ようするにdestinationIds
にdestination
が含まれるかを調べてますね。
ではdestinationIds
つまりmTopLevelDestinations
はどこから来ているのかというと、
AbstractAppBarOnDestinationChangedListener(@NonNull Context context,
@NonNull AppBarConfiguration configuration) {
mContext = context;
mTopLevelDestinations = configuration.getTopLevelDestinations();
はい、AppBarConfiguration
ですね。
今度は、AppBarConfiguration(navController.graph)
が何をやっているのか調べましょう。
inline fun AppBarConfiguration(
navGraph: NavGraph,
drawerLayout: Openable? = null,
noinline fallbackOnNavigateUpListener: () -> Boolean = { false }
) = AppBarConfiguration.Builder(navGraph)
.setOpenableLayout(drawerLayout)
.setFallbackOnNavigateUpListener(fallbackOnNavigateUpListener)
.build()
public Builder(@NonNull NavGraph navGraph) {
mTopLevelDestinations.add(NavigationUI.findStartDestination(navGraph).getId());
}
ということで、NavigationGraphからstartDestinationを探し出して、そのIDがセットされています。
AppBarConfiguration.Builder()と引数なしでコールすると、以下のコンストラクタが呼び出され、mTopLevelDestinations
が空になります。
public Builder(@NonNull int... topLevelDestinationIds) {
for (int destinationId : topLevelDestinationIds) {
mTopLevelDestinations.add(destinationId);
}
}
以上です