はじめに
JetPackのNavigation、みなさん使ってますか?
まだα版ですが可能性を非常に感じますね。
さて、Navigationをいざプロジェクトに載せようと思った時、「この設定、どうすりゃいいんだ?」ってことなかったですか?例えば↓のような起動オプションの設定。
val intent = Intent(context, SomeActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK
context.startActivity(intent)
大丈夫、(多分)NavOptionsを使えばできます。
今回はケースごとにTips形式でどんどん上げていこうと思います。
なお、これを書いている時点でalpha2であること、いくつか非推奨メソッドが存在していること、ざっくりとしか確認してないので、期待動作と異なる可能性や、今後実装が大きく変わる可能性も十分あるので注意
起動フラグを設定する
FLAG_ACTIVITY_SINGLE_TOPで起動したい
メソッド呼び出し確認のみ
val intent = Intent(context, SomeActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
context.startActivity(intent)
view?.findViewById<Button>(R.id.button_to_activity)?.setOnClickListener {
val options = NavOptions.Builder().setLaunchSingleTop(true).build()
Navigation.findNavController(it).navigate(R.id.action_mainFragment_to_main2Activity, null, options)
}
実装ポイント
-
setLaunchSingleTop(true)
を設定し、navigateの引数に渡す
該当する処理
@SuppressWarnings("deprecation")
@Override
public void navigate(@NonNull Destination destination, @Nullable Bundle args,
@Nullable NavOptions navOptions) {
/* 略 */
if (navOptions != null && navOptions.shouldLaunchSingleTop()) {
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
}
/* 略 */
}
FLAG_ACTIVITY_NEW_DOCUMENTで起動したい
動作確認してないので注意
val intent = Intent(context, SomeActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_DOCUMENT
context.startActivity(intent)
view?.findViewById<Button>(R.id.button_to_activity)?.setOnClickListener {
val options = NavOptions.Builder().setLaunchDocument(true).build()
Navigation.findNavController(it).navigate(R.id.action_mainFragment_to_main2Activity, null, options)
}
上記のSingleTopと同じような指定方法で良さげと思いきや、setLaunchDocument()が非推奨指定されてる。んで、setLaunchDocumentのJavadoc見ると以下のような記載が。
/**
* 略
* @param launchDocument true to launch a new document task
* @deprecated As per the {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT}
* documentation, it is recommended to use {@link android.R.attr#documentLaunchMode} on an
* Activity you wish to launch as a new document.
*/
@Deprecated
@NonNull
public Builder setLaunchDocument(boolean launchDocument) {/* 略 */}
documentLaunchModeで指定してね、とのこと。なので↓の方法がよさげ。
<activity android:name=".Main2Activity"
android:documentLaunchMode="always">
</activity>
実装ポイント
AndroidManifest.xmlで指定するだけ
該当する処理
alpha2版ではまだ残ってるけど、使われているメソッドがすでに非推奨になっているため、参考程度に。
@SuppressWarnings("deprecation")
@Override
public void navigate(@NonNull Destination destination, @Nullable Bundle args,
@Nullable NavOptions navOptions) {
/* 略 */
if (navOptions != null && navOptions.shouldLaunchDocument()
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
} else if (!(mContext instanceof Activity)) {
// If we're not launching from an Activity context we have to launch in a new task.
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
/* 略 */
}
LAUNCH_CLEAR_TASKで起動したい
メソッド呼び出し確認のみ
val intent = Intent(context, SomeActivity::class.java)
intent.flags = Intent.LAUNCH_CLEAR_TASK
context.startActivity(intent)
view?.findViewById<Button>(R.id.button_to_activity)?.setOnClickListener {
val options = NavOptions.Builder().setClearTask(true).build()
Navigation.findNavController(it).navigate(R.id.action_mainFragment_to_main2Activity, null, options)
}
setLaunchDocument同様、こちらもすでに非推奨。同じようにJavadocを読んで見る。
/**
* Clear the entire task before launching this target. If you are launching as a
* {@link #setLaunchDocument(boolean) document}, this will clear the document task.
* Otherwise it will clear the current task.
*
* @param clearTask
* @return
* @deprecated Use {@link #setPopUpTo(int, boolean)} with the
* {@link NavDestination#getId() id} of the
* {@link androidx.navigation.NavController#getGraph() NavController's graph}
* and set inclusive to true.
*/
@Deprecated
@NonNull
public Builder setClearTask(boolean clearTask) {
setPopUpTo(int, boolean)
使ってね。id
にはNavController
のId
を、inclusive
にはtrue
を指定してね、とのこと。念のためsetPopUpTo
のJavadocも確認。
/**
* Pop up to a given destination before navigating. This pops all non-matching destinations
* from the back stack until this destination is found.
*
* @param destinationId The destination to pop up to, clearing all intervening destinations.
* @param inclusive true to also pop the given destination from the back stack.
* @return this Builder
* @see NavOptions#getPopUpTo
* @see NavOptions#isPopUpToInclusive
*/
@NonNull
public Builder setPopUpTo(@IdRes int destinationId, boolean inclusive) {
destinationId
に指定したIdを持つ画面までをクリアする?っぽい?(自信ない)
なので↓のように指定すれば良さげ。
view?.findViewById<Button>(R.id.button_to_activity)?.setOnClickListener {
val options = NavOptions.Builder().setPopUpTo(R.id.main2Activity, true).build()
Navigation.findNavController(it).navigate(R.id.action_mainFragment_to_main2Activity, null, options)
}
実装ポイント
setPopUpTo(destinationId, inclusive)
を使う
該当する処理
public void navigate(@IdRes int resId, @Nullable Bundle args, @Nullable NavOptions navOptions) {
/* 略 */
if (destId == 0 && navOptions != null && navOptions.getPopUpTo() != 0) {
popBackStack(navOptions.getPopUpTo(), navOptions.isPopUpToInclusive());
return;
}
/* 略 */
}
/**
* Attempts to pop the controller's back stack back to a specific destination.
*
* @param destinationId The topmost destination to retain
* @param inclusive Whether the given destination should also be popped.
*
* @return true if the stack was popped at least once, false otherwise
*/
public boolean popBackStack(@IdRes int destinationId, boolean inclusive) {
if (mBackStack.isEmpty()) {
throw new IllegalArgumentException("NavController back stack is empty");
}
ArrayList<NavDestination> destinationsToRemove = new ArrayList<>();
Iterator<NavDestination> iterator = mBackStack.descendingIterator();
while (iterator.hasNext()) {
NavDestination destination = iterator.next();
if (inclusive || destination.getId() != destinationId) {
destinationsToRemove.add(destination);
}
if (destination.getId() == destinationId) {
break;
}
}
boolean popped = false;
iterator = destinationsToRemove.iterator();
while (iterator.hasNext()) {
NavDestination destination = iterator.next();
// Skip destinations already removed by a previous popBackStack operation
while (!mBackStack.isEmpty() && mBackStack.peekLast().getId() != destination.getId()) {
if (iterator.hasNext()) {
destination = iterator.next();
} else {
destination = null;
break;
}
}
if (destination != null) {
popped = destination.getNavigator().popBackStack() || popped;
}
}
return popped;
}
起動アニメーションを設定する
画面切り替え時
動作確認OK!
beforeの状態は省略(書くのがメンドクサクナッタ)
view?.findViewById<Button>(R.id.button_to_activity)?.setOnClickListener {
val options = NavOptions.Builder()
.setEnterAnim(R.anim.abc_fade_in)
.setExitAnim(R.anim.abc_fade_out)
.build()
Navigation.findNavController(it).navigate(R.id.action_mainFragment_to_main2Activity, null, options)
}
実装ポイント
-
setEnterAnim/setExitAnim
を使う
該当する処理
@SuppressWarnings("deprecation")
@Override
public void navigate(@NonNull Destination destination, @Nullable Bundle args,
@Nullable NavOptions navOptions) {
/* 略 */
if (navOptions != null && mHostActivity != null) {
int enterAnim = navOptions.getEnterAnim();
int exitAnim = navOptions.getExitAnim();
if (enterAnim != -1 || exitAnim != -1) {
enterAnim = enterAnim != -1 ? enterAnim : 0;
exitAnim = exitAnim != -1 ? exitAnim : 0;
mHostActivity.overridePendingTransition(enterAnim, exitAnim);
}
}
}
BackStackからの復帰時
動作確認してないので注意
beforeの状態は(ry
view?.findViewById<Button>(R.id.button_to_activity)?.setOnClickListener {
val options = NavOptions.Builder()
.setPopEnterAnim(R.anim.abc_fade_in)
.setPopExitAnim(R.anim.abc_fade_out)
.build()
Navigation.findNavController(it).navigate(R.id.action_mainFragment_to_main2Activity, null, options)
}
実装のポイント
-
setPopEnterAnim/setPopExitAnim
を使う
該当する処理
@SuppressWarnings("deprecation")
@Override
public void navigate(@NonNull Destination destination, @Nullable Bundle args,
@Nullable NavOptions navOptions) {
/* 略 */
NavOptions.addPopAnimationsToIntent(intent, navOptions);
/* 略 */
}
/**
* Add the {@link #getPopEnterAnim() pop enter} and {@link #getPopExitAnim() pop exit}
* animation to an Intent for later usage with
* {@link #applyPopAnimationsToPendingTransition(Activity)}.
* <p>
* This is automatically called for you by {@link ActivityNavigator}.
* </p>
*
* @param intent Intent being started with the given NavOptions
* @param navOptions NavOptions containing the pop animations.
* @see #applyPopAnimationsToPendingTransition(Activity)
* @see #getPopEnterAnim()
* @see #getPopExitAnim()
*/
public static void addPopAnimationsToIntent(@NonNull Intent intent,
@Nullable NavOptions navOptions) {
if (navOptions != null) {
intent.putExtra(KEY_NAV_OPTIONS, navOptions.toBundle());
}
}
/**
* Apply any pop animations in the Intent of the given Activity to a pending transition.
* This should be used in place of {@link Activity#overridePendingTransition(int, int)}
* to get the appropriate pop animations.
* @param activity An activity started from the {@link ActivityNavigator}.
* @see #addPopAnimationsToIntent(Intent, NavOptions)
* @see #getPopEnterAnim()
* @see #getPopExitAnim()
*/
public static void applyPopAnimationsToPendingTransition(@NonNull Activity activity) {
Intent intent = activity.getIntent();
if (intent == null) {
return;
}
Bundle bundle = intent.getBundleExtra(KEY_NAV_OPTIONS);
if (bundle != null) {
NavOptions navOptions = NavOptions.fromBundle(bundle);
int popEnterAnim = navOptions.getPopEnterAnim();
int popExitAnim = navOptions.getPopExitAnim();
if (popEnterAnim != -1 || popExitAnim != -1) {
popEnterAnim = popEnterAnim != -1 ? popEnterAnim : 0;
popExitAnim = popExitAnim != -1 ? popExitAnim : 0;
activity.overridePendingTransition(popEnterAnim, popExitAnim);
}
}
}
終わりに
「はじめに」にも書いたけど、この辺りは今後大きく変わっていく可能性が大きそう。
なので、覚えたことが無駄になるのはチョットナ・・・という人は、alphaが取れたことにもう一度見ると良いかも。