<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          牛逼!像使用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)秀的三方庫,大部分是基于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ù),無法使用近年來在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的靈活性,無需配置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 (無需基類)
          支持Dialog顯示
          no
          yes
          yes
          OnBackPressed攔截
          yes (依賴基類)
          yes(無需基類)
          yes(無需基類)


          通過對(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)頁面跳轉(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>

          加載首頁

          通常我們需要定義一個(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)

              }
          }


          頁面跳轉(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)

           

          頁面返回


          通過pop方法可以返回上一頁面。

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

          //返回到指定頁面
          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)
              }

          }

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

          Navigation需要配置NavGraph才能實(shí)現(xiàn)頁面間跳轉(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)。

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

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


          加載首頁時(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é)處理頁面跳轉(zhuǎn)的邏輯,后文會(huì)單獨(dú)介紹。Graph創(chuàng)建后添加startDestination用來加載首頁。

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


          除startDestination以外,每當(dāng)跳轉(zhuǎn)到新頁面,都需要為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無法很好替代Activity的原因之一是在BackStack管理上的差異,這會(huì)影響到生命周期的不同。

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


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

          頁面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方式滿足)


          無論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如果必須使用無參的構(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。基本思想類似于ARouter的路由原理:

          • 在編譯期通過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,以更加無侵的方式攔截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)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  簧片在线免费观看 | 亚洲无码一区二区电影 | 逼特逼在线视频 | 日本A∨再现 | 天天天天天天天天干 |