1
0

More than 1 year has passed since last update.

Navigation ComponentのstartDestinationでもHomeAsUpボタンを表示したい

Posted at

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の処理を追ってみましょう

NavController.kt
fun AppCompatActivity.setupActionBarWithNavController(
    navController: NavController,
    configuration: AppBarConfiguration = AppBarConfiguration(navController.graph)
) {
    NavigationUI.setupActionBarWithNavController(this, navController, configuration)
}

NavigationUI.setupActionBarWithNavControllerを呼び出しています。その内容は

NavigationUI.java
public static void setupActionBarWithNavController(@NonNull AppCompatActivity activity,
        @NonNull NavController navController,
        @NonNull AppBarConfiguration configuration) {
    navController.addOnDestinationChangedListener(
            new ActionBarOnDestinationChangedListener(activity, configuration));
}

NavController に OnDestinationChangedListener を登録してますね。ActionBarOnDestinationChangedListenerの中を見てみましょう

ActionBarOnDestinationChangedListener.java
@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を見てみます。

AbstractAppBarOnDestinationChangedListener.java
@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が何をやっているかというと、

NavigationUI.java
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;
}

はい、ようするにdestinationIdsdestinationが含まれるかを調べてますね。
ではdestinationIdsつまりmTopLevelDestinations はどこから来ているのかというと、

AbstractAppBarOnDestinationChangedListener.java
AbstractAppBarOnDestinationChangedListener(@NonNull Context context,
        @NonNull AppBarConfiguration configuration) {
    mContext = context;
    mTopLevelDestinations = configuration.getTopLevelDestinations();

はい、AppBarConfigurationですね。
今度は、AppBarConfiguration(navController.graph)が何をやっているのか調べましょう。

AppBarConfiguration.kt
inline fun AppBarConfiguration(
    navGraph: NavGraph,
    drawerLayout: Openable? = null,
    noinline fallbackOnNavigateUpListener: () -> Boolean = { false }
) = AppBarConfiguration.Builder(navGraph)
    .setOpenableLayout(drawerLayout)
    .setFallbackOnNavigateUpListener(fallbackOnNavigateUpListener)
    .build()
AppBarConfiguration.java
public Builder(@NonNull NavGraph navGraph) {
    mTopLevelDestinations.add(NavigationUI.findStartDestination(navGraph).getId());
}

ということで、NavigationGraphからstartDestinationを探し出して、そのIDがセットされています。

AppBarConfiguration.Builder()と引数なしでコールすると、以下のコンストラクタが呼び出され、mTopLevelDestinationsが空になります。

AppBarConfiguration.java
public Builder(@NonNull int... topLevelDestinationIds) {
    for (int destinationId : topLevelDestinationIds) {
        mTopLevelDestinations.add(destinationId);
    }
}

以上です

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0