8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

JetpackのNavigationのオプション、NavOptionsをざっくり覗いてみる

Last updated at Posted at 2018-06-19

はじめに

JetPackのNavigation、みなさん使ってますか?
まだα版ですが可能性を非常に感じますね。

さて、Navigationをいざプロジェクトに載せようと思った時、「この設定、どうすりゃいいんだ?」ってことなかったですか?例えば↓のような起動オプションの設定。

SomeActivity
  val intent = Intent(context, SomeActivity::class.java)
  intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK
  context.startActivity(intent)

大丈夫、(多分)NavOptionsを使えばできます。

今回はケースごとにTips形式でどんどん上げていこうと思います。

なお、これを書いている時点でalpha2であること、いくつか非推奨メソッドが存在していること、ざっくりとしか確認してないので、期待動作と異なる可能性や、今後実装が大きく変わる可能性も十分あるので注意

起動フラグを設定する

FLAG_ACTIVITY_SINGLE_TOPで起動したい

メソッド呼び出し確認のみ

BeforeActivity
  val intent = Intent(context, SomeActivity::class.java)
  intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
  context.startActivity(intent)
AfterActivity
  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の引数に渡す

該当する処理

ActivityNavigator.java
    @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で起動したい

動作確認してないので注意

BeforeActivity
  val intent = Intent(context, SomeActivity::class.java)
  intent.flags = Intent.FLAG_ACTIVITY_NEW_DOCUMENT
  context.startActivity(intent)
AfterActivity(非推奨な方法)
  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見ると以下のような記載が。

Activit
        /**       
         * 略                                                                                                                                                                   
         * @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で指定してね、とのこと。なので↓の方法がよさげ。

AndroidManifest.xml
        <activity android:name=".Main2Activity"
            android:documentLaunchMode="always">
        </activity>

実装ポイント

AndroidManifest.xmlで指定するだけ

該当する処理

alpha2版ではまだ残ってるけど、使われているメソッドがすでに非推奨になっているため、参考程度に。

ActivityNavigator.java
    @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で起動したい

メソッド呼び出し確認のみ

BeforeActivity
  val intent = Intent(context, SomeActivity::class.java)
  intent.flags = Intent.LAUNCH_CLEAR_TASK
  context.startActivity(intent)
AfterActivity(非推奨な方法)
  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を読んで見る。

ActivityNavigator.java
        /**                                                                                       
         * 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にはNavControllerIdを、inclusiveにはtrueを指定してね、とのこと。念のためsetPopUpToのJavadocも確認。

ActivityNavigator.java
        /**                                                                                          
         * 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を持つ画面までをクリアする?っぽい?(自信ない)
なので↓のように指定すれば良さげ。

AfterActivity
  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)を使う

該当する処理

NavController.java
    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の状態は省略(書くのがメンドクサクナッタ)

AfterActivity
  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を使う

該当する処理

ActivityNavigator.java
    @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

AfterActivity
  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を使う

該当する処理

ActivityNavigator.java
    @SuppressWarnings("deprecation")
    @Override
    public void navigate(@NonNull Destination destination, @Nullable Bundle args,
            @Nullable NavOptions navOptions) {
/* 略 */
        NavOptions.addPopAnimationsToIntent(intent, navOptions);
/* 略 */
}
NavOptions.java
    /**                                                                                              
     * 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が取れたことにもう一度見ると良いかも。

8
3
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
8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?