QMUI實戰(三)——你是如何啓動你的第一個 Fragment 的?

上一篇文章講了一些關於 ActivityFragment 的一些零碎的知識點,只有深刻的瞭解了它們,咱們才能合理的運用它們。UI相比於數據流,更靈活也更混亂,合理運用不一樣組件,可使得條例更清晰,代碼量更少。java

合理運用ActivityFragment

雖然咱們常常在說單 ActivityFragment 的架構,但官方推薦的架構並非單 ActivityFragment 的架構,若是咱們去看他的文檔或示例代碼,咱們能夠獲得官方一個推薦的職責劃分:android

Activity 用於模塊,而 Fragment 用於流程數組

例如官方一個用戶註冊模塊,一個 RegisterActivity 表示註冊,而後有 RegisterUserNameFragmentRegisterAvatarFragment 等來表示註冊的各個步驟,它們都公用同一個數據對象,那麼咱們就能夠把數據放在 RegisterActivityViewModel 裏。而註冊流程結束後,咱們釋放 RegisterActivity 時,同時也釋放了註冊相關的數據。這是一個比較優雅的方式:咱們即實現了數據的跨頁面使用,又在流程結束後將數據及時釋放。做爲最佳實踐,若是咱們的多個界面(Fragment)須要用到同一批數據,那麼咱們就能夠用一個 Activity 來包裹這些 Fragmentbash

舉一個反面例子,有些同窗完全貫徹單 ActivityFragment 的架構來實現多 Fragment 的登陸,當登錄完成進入主頁後,那就須要銷燬登陸的各個 Fragment,其作法就是遞歸的銷燬已經存在的各個 Fragment, 耗時又耗力,並且銷燬 Fragment 還可能出翔(上文有提)。可是若是咱們採用一個 LoginActivity 來包裹這些 Fragment, 那就在進入主界面後,直接 finish 掉 LoginActivity,這樣不是更簡單嗎?微信

再以微信讀書講書來舉個例子,微信讀書講書點擊進去是一個講書界面,而後講書界面有一個目錄,能夠拿到講書人全部的講書,當點擊目錄的 item 時,刷新當前講書界面。 這是一個比較常見的類型,獲得、微課等都有這種界面,那麼這個界面你會如何設計呢?我來給下兩種實現:數據結構

  1. 用一個 Fragment 承載全部的東西,當切換目錄 item 時, 拉取新的講書詳細信息,而後刷新各個 View。
  2. 用一個 Activity,目錄數據放在 Activity 的 ViewModel 裏,目錄 UI 直接掛載在 Activity 上,而後用 Fragment 來承載當前講書,切換目錄 item 時銷燬當前講書 Fragment, 而後建一個新的 Fragment

我想不少人可能會直接選擇方案一吧,看上去簡單,可是隨着業務的增加,顯示的邏輯就愈來愈複雜了,例如正常講書、TTS、公衆號講書,切換目錄或推薦時均可能會切換到任意的一種類型,這個時候刷新就是要各類判斷,各類差別化處理,痛苦死了,對,這就是微信讀書的現狀,痛苦得不要不要的。架構

而另一種,列表數據放在了 Activity 層級,從而達到公用,Fragment 只負責特定的講書,那麼這個時候根據不一樣的講書類型實例化出不一樣的 Fragment, 數據結構不一致、各類差別化處理都不是問題了。每次切換銷燬毀舊而且建立新的 Fragment,僅僅用微乎其微的性能消耗(除非你的 View 巨複雜)就能夠換來靈活性、可擴展性、可維護性,從一開始就杜絕了各類 if else 的判斷和一些 bug 的產生。app

立刻都 2020 年了,ViewModel 也應該走進各個 App 了,所以 Activity 通常不須要持有數據了,因此有時候咱們並不須要根據模塊來新建 Activity 了,咱們能夠用一個空殼的 Activity,不一樣的業務模塊都用實例化這個空殼 Activity, 而後用 Fragment 來區分和開始處理不一樣的業務類型。ide

假設咱們使用一個 CommonHolderActivity, QMUI 提供了以下的使用方式,讓你能夠快速的啓動不一樣的業務:性能

// 模塊 A,以 ModuleAFirstFragment 做爲第一個 Fragment
QMUIFragmentActivity.intentOf(context, CommonHolderActivity::class.java, ModuleAFirstFragment::class.java)

// 模塊 B,以 ModuleBFirstFragment 做爲第一個 Fragment
QMUIFragmentActivity.intentOf(context, CommonHolderActivity::class.java, ModuleBFirstFragment::class.java)

複製代碼

接下來我來說講 QMUIFragmentActivity.intentOf 是如何工做的,以及 @FirstFragments 的用處

First Fragment

First Fragment,是 Activity 裏的第一個 Fragment,也是流程的起始。 當咱們已近有了第一個 Fragment 後,接下來的流程主要是經過 QMUIFragment.startFragment() 來啓動一個又一個新的 Fragment, 若是流程走完了, 那咱們就是 經過 Activity.finish() 結束整個 Activity。 那麼問題來了。 咱們如何爲 Activity 添加 First Fragment 呢?

添加 First Fragment 的主體代碼以下:

val firstFragment = ...
supportFragmentManager
    .beginTransaction()
    .add(contextViewId, firstFragment, firstFragment.javaClass.getSimpleName())
    .addToBackStack(firstFragment.javaClass.getSimpleName())
    .commit()
複製代碼

那麼 firstFragment 如何獲得呢?在 QMUIDemo 最初的版本是用 if else 去判斷的:

// 一些變量來記錄啓動 First Fragment 是誰?
val DST_FRAGMENT = "dst_fragment"
val DST_HOME = 1
var DST_ARCH = 2
val intent = Intent(context, QDMainActivity::class.java)
intent.put(DST_FRAGMENT, DST_HOME)
startActivity(intent)

// QDMainActivity.java
var firstFragment: QMUIFragment? = null
var dst = intent.getIntExtra(DST_FRAGMENT, DST_HOME)
if(dst == DST_HOME){
   fragment = QDHomeFragment()
}else if(dst == DST_ARCH){
   fragment = QDArchFragment()
}else{
   //.....
}

// supportFragmentManager 添加 firstFragment
複製代碼

目前看來,代碼量也不是不少,只是簡單的幾個 if else,而且實現了不一樣業務公用同一個 Activity。 但問題是每多一個業務,我就須要加一個變量,而且加一個 else 分支, 短時間沒什麼,時間久了,就是滿屏的 if else 了,至關的不優雅。

有的同窗會採用子類提供 First Fragment 的實現,而放棄公用同一個 Activity

class ParentActivity: QMUIFragmentActivity(){
  override fun onCreate(savedInstanceState: Bundle?) {
     if(savedInstanceState == null){
         val firstFragment = getFirstFragment()
         // supportFragmentManager 添加 firstFragment
     }
  }

  abstract fun getFirstFragment(): QMUIFragemnt
}

class ModuleAActivity: ParentActivity(){
   override fun getFirstFragment() = ModuleAFirstFragment()
}

class ModuleBActivity: ParentActivity(){
   override fun getFirstFragment() = ModuleBFirstFragment()
}
複製代碼

但這種實現要寫不少 Activity, 而且要在 AndroidManifest 上註冊無數次。

爲了減小讓使用看上去簡單一些,我開發了 @FirstFragments 註解來解決這個問題。

其根本思路仍是最開始的 if else 判斷,某個變量對應某個 Fragment,但我用代碼生成來幫你生成那些變量和 if else 的判斷邏輯。 這也是 Android 開發的一個思路,若是是模板式的代碼,咱們就能夠用代碼生成來解決,使得咱們用起來足夠舒服就好。其代碼生成邏輯也不是很複雜,無非就是一個 Map,Key 爲 int, Value 爲 Class<? extend QMUIFragment。

而使用時,只須要在 Activity 上聲明就行:

@FirstFragments(
    value = [
        HomeFragment::class
    ]
)
class CommonHolderActivity : QMUIFragmentActivity() {}
複製代碼

這樣咱們就可使用 QMUIFragmentActivity.intentOf

QMUIFragmentActivity.intentOf(context, CommonHolderActivity::class.java, HomeFragment::class.java)
複製代碼

若是咱們須要像 First Fragment 傳參, 咱們能夠啓用第四個參數, 固然,這個傳參是採用 Fragment.setArguments() 實現的, Fragment 自己要求爲無參構造器,這和官方的推薦是一致的。

若是咱們沒在 Activity@FirstFragments 數組裏加上 Fragment, 那麼 QMUIFragmentActivity.intentOf 會拋錯的。咱們也可使用 @DefaultFirstFragment 來指定默認的 First Fragment,這時 new Intent(context, CommonHolderActivity::class.java) 就會啓用默認的 First Fragment。

實戰

好了,理論部分若是搞明白了,代碼寫起來就簡單了。

首先咱們新建 CommonHolderActivity

class CommonHolderActivity : QMUIFragmentActivity() {

    override fun getContextViewId(): Int {
        return R.id.app_common_holder_fragment_container
    }
}
複製代碼

這裏咱們只須要重寫 getContextViewId*( 提供 FragmentContainer 的 id, 那這裏可不能夠返回 View.generateViewId() 呢? 爲何? 若是你讀懂了上一篇文章,那麼你應該能知道答案。

同時別忘了在 AndroidManifest 裏註冊:

<activity android:name=".CommonHolderActivity"
    android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|screenLayout"/>
複製代碼

新建 HomeFragment, 併爲 CommonHolderActivity 加上註解:

class HomeFragment: QMUIFragment(){
    override fun onCreateView(): View {
        return FrameLayout(context!!).apply {
            val textView = TextView(context).apply {
                text = "第一個 Fragment"
            }
            addView(textView, FrameLayout.LayoutParams(wrapContent, wrapContent).apply {
                gravity = Gravity.CENTER
            })
        }
    }
}

@FirstFragments(
    value = [
        HomeFragment::class
    ]
)
@DefaultFirstFragment(HomeFragment::class)
class CommonHolderActivity : QMUIFragmentActivity() {
    //...
}
複製代碼

而後在 LauncherActivity 裏補上跳轉邏輯:

class LauncherActivity: QMUIActivity(){

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val intent = QMUIFragmentActivity.intentOf(this,
            CommonHolderActivity::class.java,
            HomeFragment::class.java)
        startActivity(intent)
        finish()
    }
}
複製代碼

這樣咱們就來到了主頁了。

下期博文:QMUI實戰(四)—— QMUI 換膚的實現與使用

博文地址:blog.cgsdream.org/2019/12/21/…

相關文章
相關標籤/搜索