1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        牛逼!像使用Activity一樣使用Fragment

        共 35442字,需瀏覽 71分鐘

         ·

        2021-05-18 23:06

         微信改了推動(dòng)機(jī)制,真愛請(qǐng)星標(biāo)本公號(hào)
        公眾號(hào)回復(fù)加入BATcoder技術(shù)群BAT

        作者:fundroid_方卓 

        https://blog.csdn.net/vitaviva

        前言

        " An app only needs an Activity, you can use Fragments, just don’t use the backstack with fragments "
        – Jake Wharton @Droidcon NYC 2017

        近年來,SPA,即單Activity架構(gòu)逐漸開始受到歡迎,隨之而生了很多優(yōu)秀的三方庫(kù),大部分是基于Fragment作為實(shí)現(xiàn)方案,其中最有代表性的就是Fragmentation了,后來Jetpack Navigation的誕生也標(biāo)志著Google從官方立場(chǎng)對(duì)SPA架構(gòu)的肯定。

        Navigation的出現(xiàn)并沒有加速Fragment對(duì)Activity的全面取代,一個(gè)重要原因是因?yàn)槠溥^與依賴配置(NavGraph),喪失了Activity的靈活性。這一點(diǎn)上Fragmentation做的不錯(cuò),有接近Activity的使用體驗(yàn),可惜其不支持Kotlin,且早已停止維護(hù),無(wú)法使用近年來在AndroidX中引入的各種新特性。

        是否有一個(gè)工具,既具備Fragmentation那樣靈活性,又能像Navigation那樣兼容AndroidX中的新功能呢?Fragivity正是在這個(gè)背景下誕生的。

        Fragivity的項(xiàng)目地址:
        https://github.com/vitaviva/fragivity

        Fragmentation的項(xiàng)目地址:
        https://github.com/YoKeyword/Fragmentation

        Use Fragment Like Activity

        顧名思義,F(xiàn)ragivity希望讓Fragment具備Activity一樣的使用體驗(yàn),從而在各種場(chǎng)景中能真正取而代之:

        • 生命周期與Activity行為一致
        • 支持多種LaunchMode
        • 支持OnBackPressed事件處理、支持SwipeBack
        • 支持Transition、SharedElement等轉(zhuǎn)場(chǎng)動(dòng)畫
        • 支持以Dialog樣式顯示
        • 支持Deep Links

        Fragivity底層基于Navigation實(shí)現(xiàn),同時(shí)兼具Fragmentation的靈活性,無(wú)需配置NavGraph即可實(shí)現(xiàn)畫面跳轉(zhuǎn)。簡(jiǎn)單對(duì)比一下三者的區(qū)別:


        Fragmentation
        Navigation
        Fragivity
        自由跳轉(zhuǎn)
        yes
        no (依賴NavGraph)
        yes
        Launch Mode
        3種
        2種
        3種
        支持Deep Links
        no
        yes(依賴NavGraph)
        yes(使用注解)
        kotlin友好
        no
        yes
        yes
        生命周期
        與Activity不一致(add方式)
        與Activity不一致(replace方式)
        與Activity一致
        Fragment間通信
        startFragmentForResult
        viewmodel
        viewmodel、callback、resultapi等多種方式
        過場(chǎng)動(dòng)畫
        View Animation
        Transition Animation
        Transition Animation
        Swipe Back
        yes(依賴基類)
        no
        yes (無(wú)需基類)
        支持Dialog顯示
        no
        yes
        yes
        OnBackPressed攔截
        yes (依賴基類)
        yes(無(wú)需基類)
        yes(無(wú)需基類)


        通過對(duì)比可以發(fā)現(xiàn),比起前兩者Fragivity在多個(gè)維度上與Activity的行為更加一致。

        基本使用

        Fragivity的接入成本很低。

        gradle依賴


        implementation 'com.github.fragivity:core:$latest_version'


        聲明NavHostFragment


        像Navigation一樣,F(xiàn)ragivity需要NavHostFragment作為Parent,然后在ChildFragment之間實(shí)現(xiàn)頁(yè)面跳轉(zhuǎn)。我們?cè)趚ml中聲明NavHostFragment:

        <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
            android:id="@+id/container"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            android:fitsSystemWindows="true">


            <androidx.fragment.app.FragmentContainerView
                android:id="@+id/nav_host"
                android:name="androidx.navigation.fragment.NavHostFragment"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:defaultNavHost="true" />

        </FrameLayout>

        加載首頁(yè)

        通常我們需要定義一個(gè)MainActivity作為入口,同樣,這里通過loadRoot加載一個(gè)初始的Fragment:

        class MainActivity : AppCompatActivity() {

            override fun onCreate(savedInstanceState: Bundle?) {
                super.onCreate(savedInstanceState)
                setContentView(R.layout.activity_main)

                val navHostFragment = supportFragmentManager
                    .findFragmentById(R.id.nav_host) as NavHostFragment

                navHostFragment.loadRoot(HomeFragment::class)

            }
        }


        頁(yè)面跳轉(zhuǎn)


        接下來便可以在Fragment之間進(jìn)行跳轉(zhuǎn)了:

        //跳轉(zhuǎn)到目標(biāo)Fragment
        navigator.push(DestinationFragment::class)

        //攜帶參數(shù)跳轉(zhuǎn)
        val bundle = bundleOf(KEY_ARGUMENT to "some args")
        navigator.push(DestinationFragment::classbundle)

         

        頁(yè)面返回


        通過pop方法可以返回上一頁(yè)面。

        //返回上一頁(yè)面
        navigator.pop()

        //返回到指定頁(yè)面
        navigator.popTo(HomeFramgent::class)

        轉(zhuǎn)場(chǎng)動(dòng)畫


        基于Navigation的能力,在畫面跳轉(zhuǎn)時(shí)可以設(shè)置Transition動(dòng)畫。

        navigator.push(UserProfile::classbundle) { //this:NavOptions
            //配置動(dòng)畫
            enterAnim = R.anim.enter_anim
            exitAnim = R.anim.exit_anim
            popEnterAnim = R.anim.enter_anim
            popExitAnim = R.anim.exit_anim
        }

        借助FragmentNavigatorExtras還可以設(shè)置SharedElement,實(shí)現(xiàn)更優(yōu)雅地動(dòng)畫效果。

        //跳轉(zhuǎn)時(shí),對(duì)imageView設(shè)置SharedElement
        navigator.push(UserProfile::classbundle,
                       FragmentNavigatorExtras
        (imageView to "imageView")) { //this:NavOptions

            enterAnim = R.anim.enter_anim
            exitAnim = R.anim.exit_anim
            popEnterAnim = R.anim.enter_anim
            popExitAnim = R.anim.exit_anim

        }

        class UserProfile : Fragment() {

            override fun onCreate(savedInstanceState: Bundle?) {
                super.onCreate(savedInstanceState)
                //目標(biāo)Fragment中設(shè)置共享元素動(dòng)畫
                sharedElementEnterTransition = TransitionInflater.from(context).inflateTransition(android.R.transition.move)
            }

        }

        無(wú)需配置實(shí)現(xiàn)頁(yè)面跳轉(zhuǎn)

        Navigation需要配置NavGraph才能實(shí)現(xiàn)頁(yè)面間跳轉(zhuǎn),例如:

        <navigation
            xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            xmlns:tools="http://schemas.android.com/tools"
            app:startDestination="@+id/first">


            <fragment
                android:id="@+id/fragment_first"
                android:name=".FirstFagment"
                android:label="@string/tag_first">

                <action
                    android:id="@+id/action_to_second"
                    app:destination="@id/fragment_second"/>

            </fragment>
            <fragment
                android:id="@+id/fragment_second"
                android:name=".SecondFragment"
                android:label="@string/tag_second"/>

        </navigation>

        每個(gè)<navigation/>對(duì)應(yīng)一個(gè)NavGraph對(duì)象,<fragment/>會(huì)對(duì)應(yīng)到NavGraph中的各個(gè)Destination,NavController持有NavGraph通過控制Destination之間的跳轉(zhuǎn)。

        依賴配置的頁(yè)面跳轉(zhuǎn),無(wú)法做到像Activity那樣靈活。Fragivity通過動(dòng)態(tài)構(gòu)建NavGraph,無(wú)需配置即可實(shí)現(xiàn)跳轉(zhuǎn):

        動(dòng)態(tài)創(chuàng)建Graph


        加載首頁(yè)時(shí),動(dòng)態(tài)創(chuàng)建Graph。

        fun NavHostFragment.loadRoot(root: KClass<out Fragment>) {

            navController.apply {
                //添加Navigator
                navigatorProvider.addNavigator(
                    FragivityNavigator(
                        context,
                        childFragmentManager,
                        id
                    )
                )

                //創(chuàng)建Graph
                graph = createGraph(startDestination = startDestId) {
                    val startDestId = root.hashCode()
                    //添加startDestination
                    destination(
                        FragmentNavigatorDestinationBuilder(
                            provider[FragivityNavigator::class],
                            startDestId,
                            root

                        ))
                }
            }
        }

        FragivityNavigator負(fù)責(zé)處理頁(yè)面跳轉(zhuǎn)的邏輯,后文會(huì)單獨(dú)介紹。Graph創(chuàng)建后添加startDestination用來加載首頁(yè)。

        動(dòng)態(tài)添加Destination


        除startDestination以外,每當(dāng)跳轉(zhuǎn)到新頁(yè)面,都需要為Graph動(dòng)態(tài)添加此Destination:

        fun NavHost.push(
            clazz: KClass<out Fragment>,
            args: Bundle? = null,
            extras: Navigator.Extras? = null,
            optionsBuilder: NavOptions.()
         -> Unit = {}
        ) = with(navController) {
            // 動(dòng)態(tài)創(chuàng)建Destination
            val node = putFragment(clazz)
            // 調(diào)用NavController的navigate方法進(jìn)行跳轉(zhuǎn)
            navigate(
                node.id, args,
                convertNavOptions(clazz, NavOptions().apply(optionsBuilder)),
                extras
            )
        }

        // 創(chuàng)建并添加Destination
        private fun NavController.putFragment(clazz: KClass<out Fragment>): FragmentNavigator.Destination {
            val destId = clazz.hashCode()
            lateinit var destination: FragmentNavigator.Destination
            if (graph.findNode(destId) == null) {
                destination = (FragmentNavigatorDestinationBuilder(
                    navigatorProvider[FragivityNavigator::class],
                    destId,
                    clazz

                )).build()
                graph.plusAssign(destination)// 添加進(jìn)Graph
            } else {
                destination = graph.findNode(destId) as FragmentNavigator.Destination
            }
            return destination
        }

        創(chuàng)建Destination后,通過NavController的navigate方法跳轉(zhuǎn)到此Destination。

        BackStack及生命周期

        如J神所說,F(xiàn)ragment無(wú)法很好替代Activity的原因之一是在BackStack管理上的差異,這會(huì)影響到生命周期的不同。

        設(shè)想以下場(chǎng)景:A頁(yè)面 > (啟動(dòng))> B頁(yè)面 >(返回)> A頁(yè)面


        我們知道添加Fragment一般有兩種方式:add 、 replace。無(wú)論哪種方式其在畫面跳轉(zhuǎn)時(shí)的生命周期與Activity都不相同:

        頁(yè)面B的啟動(dòng)方式
        從B返回時(shí)的生命周期
        Activity
        ActivityB:onPasue -> onStop -> onDestroy
        ActivityA:onStart -> onResume
        Fragment(add )
        FragmentB : onPause -> onStop -> onDestroy
        FragmentA : no change
        Fragment(replace)
        FragmentB: onPause -> onStop -> onDestroy
        FragmentA: onCreateView -> onStart -> onResume

        如果希望在畫面跳轉(zhuǎn)時(shí)Fragment的生命周期與Activity行為一致,則至少需要達(dá)成以下三個(gè)目標(biāo):

        • 目標(biāo)1:回退時(shí),F(xiàn)ragmentB不重新onCreateView (add方式滿足)
        • 目標(biāo)2:回退時(shí),F(xiàn)ragmentB會(huì)觸發(fā)onStart -> onResume (replace方式滿足)
        • 目標(biāo)3:后臺(tái)的Fragment不跟隨父生命周期發(fā)生變化 (replace方式滿足)


        無(wú)論Navigation還是Fragmentation都不能同時(shí)滿足上面三條。


        重寫FragmentNavigator


        NavController通過FragmentNavigator實(shí)現(xiàn)具體的跳轉(zhuǎn)邏輯,F(xiàn)ragmentNavigator是Navigator的派生類,專門負(fù)責(zé)FragmentNavigator.Destination類型的跳轉(zhuǎn)。

        navigate()實(shí)現(xiàn)了Fragment跳轉(zhuǎn)的具體邏輯,其核心代碼如下:

        @Navigator.Name("fragment")
        public class FragmentNavigator extends Navigator<FragmentNavigator.Destination{

            @Override
            public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,
                    @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {

                String className = destination.getClassName();

                //實(shí)例化Fragment
                final Fragment frag = instantiateFragment(mContext, mFragmentManager,
                className, args);
                frag.setArguments(args);

                final FragmentTransaction ft = mFragmentManager.beginTransaction();
                ft.replace(mContainerId, frag); // replace方式添加Fragment
                ft.setPrimaryNavigationFragment(frag);

                //事務(wù)壓棧
                ft.addToBackStack(generateBackStackName(mBackStack.size() + 1, destId));

                ft.setReorderingAllowed(true);
                ft.commit();

            }

        }

        FragmentNavigator通過replace進(jìn)行Fragment跳轉(zhuǎn),前面分析我們知道這在回退時(shí)會(huì)重新onCreateView,不符合預(yù)期。我們實(shí)現(xiàn)子類FragivityNavigator,重寫navigate()方法,將replace改為add,避免重新onCreateView,達(dá)成“目標(biāo)1”。

        public class FragivityNavigator extends FragmentNavigator {
            @Override
            public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,
                    @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {

                final Fragment frag = instantiateFragment(mContext, mFragmentManager,
                className, args);
                //ft.replace(mContainerId, frag); // replace改為add
                ft.add(mContainerId, frag, generateBackStackName(mBackStack.size(), destination.getId()));

            }
        }

        添加OnBackStackChangedListener


        在合適的時(shí)機(jī)為FragmentManger添加OnBackStackChangedListener,當(dāng)監(jiān)聽到backstack變化時(shí),手動(dòng)觸發(fā)生命周期回調(diào),達(dá)成“目標(biāo)2”。

        private final FragmentManager.OnBackStackChangedListener mOnBackStackChangedListener =
                new FragmentManager.OnBackStackChangedListener() {

                    @Override
                    public void onBackStackChanged() {
                        if (mIsPendingAddToBackStackOperation) {
                            mIsPendingAddToBackStackOperation = !isBackStackEqual();

                            if (mFragmentManager.getFragments().size() > 1) {
                                // 切到后臺(tái)時(shí)的生命周期
                                Fragment fragment = mFragmentManager.getFragments().get(mFragmentManager.getFragments().size() - 2);
                                if (fragment instanceof ReportFragment) {
                                    fragment.performPause();
                                    fragment.performStop();
                                    ((ReportFragment) fragment).setShow(false);
                                }
                            }
                        } else if (mIsPendingPopBackStackOperation) {
                            mIsPendingPopBackStackOperation = !isBackStackEqual();
                            // 回到前臺(tái)時(shí)的生命周期
                            Fragment fragment = mFragmentManager.getPrimaryNavigationFragment();
                            if (fragment instanceof ReportFragment) {
                                ((ReportFragment) fragment).setShow(true);
                                fragment.performStart();
                                fragment.performResume();
                            }
                        }
                    }
                };

        ReportFragment代理


        為了達(dá)成“目標(biāo)3”, 在實(shí)例化Fragment時(shí),為其創(chuàng)建ReportFragment作為代理。所謂代理其實(shí)是通過ParentFragment對(duì)內(nèi)進(jìn)行生命周期的分發(fā)和控制。

        //ReportFragment
        internal class ReportFragment : Fragment() {

            internal lateinit var className: String
            private val _real: Class<out Fragment> by lazy {
                Class.forName(className) as Class<out Fragment>
            }
            private val _realFragment by lazy {  _real.newInstance() }

            override fun onAttach(context: Context) {
                super.onAttach(context)
                //將目標(biāo)Framgent作為child進(jìn)行管理
                mChildFragmentManager.beginTransaction().apply {
                    _realFragment.arguments = arguments
                    add(R.id.container, _realFragment)
                    commitNow()
                }
            }

        }

        //ReportFragmentManager
        internal class ReportFragmentManager : FragmentManager() {
            //isShow:在后臺(tái)時(shí),不響應(yīng)生命周期分發(fā)
            internal var isShow = true
            public override fun dispatchResume() {
                if (isShow) super.dispatchResume()
            }

            //...
        }

        支持Launch Mode

        Fragivity支持三種LaunchMode:Standard、SingleTop、SingleTask。

        啟動(dòng)方式非常簡(jiǎn)單:

        navigator.push(LaunchModeFragment::classbundle) { //this: NavOptions
            launchMode = LaunchMode.STANDARD // 默認(rèn)可省略
            //launchMode = LaunchMode.SINGLE_TOP
            //launchMode = LaunchMode.SINGLE_TASK
        }

        這里著重介紹一下SingleTop的實(shí)現(xiàn)。Navigation也支持SingleTop,但是在Navigator中完成的,由于我們重寫了Navigator(replace改為add),因此對(duì)SingleTop的實(shí)現(xiàn)也要做相應(yīng)調(diào)整:

        @Override
        public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,
                    @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {

            final Fragment preFrag = mFragmentManager.getPrimaryNavigationFragment();

            //當(dāng)以singleTop啟動(dòng)時(shí)
            if (isSingleTopReplacement) {
                    if (mBackStack.size() > 1) {
                        ft.remove(preFrag);// 刪除舊實(shí)例

                        //更新FragmentTransaction中的實(shí)例信息
                        frag.mTag = generateBackStackName(mBackStack.size() - 1, destination.getId());
                        if (mFragmentManager.mBackStack.size() > 0) {
                            List<FragmentTransaction.Op> ops =
                                    mFragmentManager.mBackStack.get(mFragmentManager.mBackStack.size() - 1).mOps;
                            for (FragmentTransaction.Op op : ops) {
                                if (op.mCmd == OP_ADD && op.mFragment == preFrag) {
                                    op.mFragment = frag;
                                }
                            }
                        }
                    }
                } 
        }

        SingleTop要求當(dāng)棧頂類型和目標(biāo)類型相同時(shí)只能存在一個(gè)實(shí)例,所以需要?jiǎng)h除舊實(shí)例避免重復(fù)添加。同時(shí)為了保證BackStack回退時(shí)的事務(wù)行為正常,需要將添加舊實(shí)例的事務(wù)中的相關(guān)信息更新為新實(shí)例。

        Fragment通信

        Fragivity支持androidx.fragment的所有通信方式,例如使用ViewModel,或者使用ResultApi(Fragment 版本高于1.3.0-beta02)等。除此之外,F(xiàn)ragivity提供了更簡(jiǎn)單的基于Callback的通信方式:
        //SourceFragment
        val cb = { it : Boolean -> 
            //...
        }
        navigator.push {
            DestinationFragment(cb)
        }

        //Destination
        class DestinationFragment(val cb:(Boolean) -> Unit) {...}

        以前Fragment如果必須使用無(wú)參的構(gòu)造函數(shù),否則打包時(shí)會(huì)出錯(cuò)。感謝AndroidX帶來的進(jìn)步,目前已經(jīng)取消了此限制,允許自定義帶參數(shù)的構(gòu)造函數(shù)。因此我們可以通過lambda動(dòng)態(tài)創(chuàng)建Fragment并將callback作為構(gòu)造參數(shù)傳入。
        inline fun <reified T : Fragment> NavHost.push(
            noinline optionsBuilder: NavOptions.()
         -> Unit = {},
            noinline block: () -> T
        ) {
            //...
            push(T::classoptionsBuilder)
        }

        如上,其內(nèi)部仍然是使用Fragment的Class作為參數(shù)進(jìn)行跳轉(zhuǎn),只是借助kotlin的reified特性,獲取了泛型的Class信息而已。

        支持DeepLinks

        Activity可以通過URI隱式啟動(dòng),為了覆蓋此類使用場(chǎng)景,需要為Fragment提供Deep Links支持。Navigation在NavGraph中為Destination配置URI信息;Fragivity雖然沒有NavGraph,但可以通過注解配置URI?;舅枷腩愃朴贏Router的路由原理:

        • 在編譯期通過kapt解析注解,獲取URI信息,并與Fragment相關(guān)聯(lián)
        • 在Activity的入口處攔截Intent,解析URI并跳轉(zhuǎn)到相關(guān)聯(lián)的Fragment


        添加kapt依賴


        kapt 'com.github.fragivity:processor:$latest_version'

        配置URI


        定義Fragment時(shí),使用@DeepLink配置URI。
        const val URI = "myapp://fragitiy.github.com/"

        @DeepLink(uri = URI)
        class DeepLinkFragment : AbsBaseFragment() {

            override fun onCreateView(
                inflater: LayoutInflater,
                container: ViewGroup?,
                savedInstanceState: Bundle?
            )
        : View? {
                return inflater.inflate(R.layout.fragment_deep_link, container, false)
            }
        }

        處理Intent


        在MainActivity入口處,處理Intent中的URI。
        //MainActivity#onCreate
        override fun onCreate(savedInstanceState: Bundle?) {
             super.onCreate(savedInstanceState)
             setContentView(R.layout.activity_main)

             val navHostFragment = supportFragmentManager
                    .findFragmentById(R.id.nav_host) as NavHostFragment

             navHostFragment.handleDeepLink(intent)

        }

        handleDeepLink內(nèi)部最終會(huì)調(diào)用NavController的相關(guān)方法對(duì)URI進(jìn)行解析:

        //NavController
        public void navigate(@NonNull Uri deepLink) {
            navigate(new NavDeepLinkRequest(deepLink, nullnull));
        }
        之后,我們就可以從APP外部通過URI的方式跳轉(zhuǎn)到目標(biāo)Fragment了:
        val intent = Intent(Intent.ACTION_VIEW, Uri.parse("myapp://fragitiy.github.com/"))
        startActivity(intent)

        OnBackPressed事件攔截


        Fragment沒有Activity的OnBackPressed方法,F(xiàn)ragmentation通過繼承的方式增加了onBackPressedSupport方法,但這會(huì)引入新的基類,對(duì)業(yè)務(wù)代碼的侵入性較高。

        Fragivity基于androidx.activity的OnBackPressedDispatcher,以更加無(wú)侵的方式攔截back鍵事件。OnBackPressedDispatcher通過責(zé)任鏈模式保證了back事件消費(fèi)的順序,同時(shí)感知Lifecycle,在適當(dāng)?shù)臅r(shí)機(jī)自動(dòng)注銷,避免泄露。

        參考:
        https://developer.android.com/guide/navigation/navigation-custom-back

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            requireActivity().onBackPressedDispatcher.addCallback( this,
                object : OnBackPressedCallback(true) {
                    override fun handleOnBackPressed() {
                        // 攔截back鍵事件
                    }
                })
        }

        back鍵返回與pop()返回


        Fragivity提供pop方法,通過代碼實(shí)現(xiàn)返回,其內(nèi)部最終會(huì)調(diào)用Navigator#popBackStack。為了保證回退邏輯統(tǒng)一,我們希望back鍵回退時(shí)也由popBackStack統(tǒng)一處理。Navigation通過NavHostFragment進(jìn)行了實(shí)現(xiàn):

        //NavHostFragment#onCreate
        public void onCreate(@Nullable Bundle savedInstanceState) {
                //...
             mNavController = new NavHostController(context);
                mNavController.setOnBackPressedDispatcher(requireActivity().getOnBackPressedDispatcher());
                //...

        }

        //NavController#setOnBackPressedDispatcher
        void setOnBackPressedDispatcher(@NonNull OnBackPressedDispatcher dispatcher) {
            if (mLifecycleOwner == null) {
                throw new IllegalStateException("You must call setLifecycleOwner() before calling "
                        + "setOnBackPressedDispatcher()");
            }
            // Remove the callback from any previous dispatcher
            mOnBackPressedCallback.remove();
            // Then add it to the new dispatcher
            dispatcher.addCallback(mLifecycleOwner, mOnBackPressedCallback);
        }

        //NavController#mOnBackPressedCallback
        private final OnBackPressedCallback mOnBackPressedCallback =
                new OnBackPressedCallback(false) {
            @Override
            public void handleOnBackPressed() {
                popBackStack(); // 最終回調(diào)Navigator#popBackStack
            }
        };

        SwipeBack

        Navigation沒有提供滑動(dòng)返回的能力,我們從Fragmentation中找到解決方案:onCreateView的時(shí)候,將SwipeLayout作為Container容器。使用方式非常簡(jiǎn)單:

        class SwipeBackFragment : Fragment() {

            override fun onCreateView(
                inflater: LayoutInflater,
                container: ViewGroup?,
                savedInstanceState: Bundle?
            )
        : View? {
                return inflater.inflate(R.layout.fragment_swipe_back, container, false)
            }

            override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
                super.onViewCreated(view, savedInstanceState)
                swipeBackLayout.setEnableGesture(true//一句話開啟SwipeBack
            }

        }

        借助ReportFragment代理,避免了額外基類的引入。swipeBackLayout是擴(kuò)展屬性,實(shí)際獲取的是parentFragment(ReportFragment)的實(shí)例。

        val Fragment.swipeBackLayout
            get() = (parentFragment as ReportFragment).swipeBackLayout

        ReportFragment中的處理非常簡(jiǎn)單,將SwipeLayout作為Container即可。

        internal class ReportFragment : Fragment() {

            override fun onCreateView(
                inflater: LayoutInflater,
                container: ViewGroup?,
                savedInstanceState: Bundle?
            )
        : View? {
                swipeBackLayout =
                    SwipeBackLayout(requireContext()).apply {
                        attachToFragment(
                            this@ReportFragment,
                            inflater.inflate(R.layout.report_layout, container, false)
                                .apply { appendBackground() } // add a default background color to make it opaque

                        )
                        setEnableGesture(false//default false
                    }
                return swipeBackLayout
            }

        為了避免滑動(dòng)過程中的背景穿透,調(diào)用applyBackgroud()為Fragment添加與當(dāng)前主題相同的默認(rèn)背景色。

        private fun View.appendBackground() {
            val a: TypedArray =
                requireActivity().theme.obtainStyledAttributes(intArrayOf(android.R.attr.windowBackground))
            val background = a.getResourceId(00)
            a.recycle()
            setBackgroundResource(background)
        }

        ShowDialog

        Activity通過設(shè)置Theme可以以Dialog樣式啟動(dòng),使用DialogFragment同樣可以實(shí)現(xiàn)Dialog樣式的Fragment。Navigation對(duì)DialogFragment已經(jīng)做了支持,F(xiàn)ragivity只要調(diào)用相關(guān)方法即可:

        定義DialogFragment


        class DialogFragment : DialogFragment() {

            override fun onCreateView(
                inflater: LayoutInflater,
                container: ViewGroup?,
                savedInstanceState: Bundle?
            )
        : View? {
                return inflater.inflate(R.layout.fragment_dialog, container, false)
            }
        }

        顯示Dialog


        navigator.showDialog(DialogFragment::class)

        DialogFramgent也需要在Graph上動(dòng)態(tài)添加Destination,只是與普通的Fragment有所區(qū)別,其配套的Navigator類型是DialogFragmentNavigator:

        //創(chuàng)建Destination
        val destination = DialogFragmentNavigatorDestinationBuilder(
               navigatorProvider[DialogFragmentNavigator::class],
               destIdclazz ).apply {

                    label = clazz.qualifiedName
               }.build()

        //添加到Graph      
        graph.plusAssign(destination)

        最后

        Fragivity在核心邏輯上力求最大程度復(fù)用Navigation的能力,并保持與最新版本同步,這有利于保證框架的先進(jìn)性和穩(wěn)定性。同時(shí)Fragivity致力于打造與Activity相近的使用體驗(yàn),以幫助開發(fā)者更低成本地轉(zhuǎn)向單Activity架構(gòu)。

        工程源碼中有本文介紹的各種API的demo,歡迎大家下載體驗(yàn),提issue,覺得好用別忘了start~


        Fragivity地址如下所示:
        https://github.com/vitaviva/fragivity


        ·················END·················

        推薦閱讀

        ? 耗時(shí)2年,Android進(jìn)階三部曲第三部《Android進(jìn)階指北》出版!

        ? 『BATcoder』做了多年安卓還沒編譯過源碼?一個(gè)視頻帶你玩轉(zhuǎn)!

        ? 引入Jetpack架構(gòu)后,你的App會(huì)發(fā)生哪些變化?(建議收藏)

        ? 重生!進(jìn)階三部曲第一部《Android進(jìn)階之光》第2版 出版!

        BATcoder技術(shù)群,讓一部分人先進(jìn)大廠

        大家,我是劉望舒,騰訊云最具價(jià)值專家TVP,著有暢銷書《Android進(jìn)階之光》《Android進(jìn)階解密》《Android進(jìn)階指北》,蟬聯(lián)四屆電子工業(yè)出版社年度優(yōu)秀作者,谷歌開發(fā)者社區(qū)特邀講師,百度百科收錄的技術(shù)專家。

        前華為面試官,現(xiàn)大廠技術(shù)負(fù)責(zé)人。


        想要加入 BATcoder技術(shù)群,公號(hào)回復(fù)BAT 即可。

        為了防止失聯(lián),歡迎關(guān)注我的小號(hào)


          微信改了推送機(jī)制,真愛請(qǐng)星標(biāo)本公號(hào)??
        瀏覽 86
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            国产探花在线精品一区二区 | 国产情侣呻吟对白高潮 | 激情床戏电影视频,大尺度 | 欧美一级特黄A片免费 | 大操逼通逼逼 | 午夜福利美女视频 | 欧美人与性口牲恔配上海 | 日逼视频www | 妲己无圣光无遮挡 | igao在线观看免费完整版 |