上一篇文章講了一些關於 Activity
和 Fragment
的一些零碎的知識點,只有深刻的瞭解了它們,咱們才能合理的運用它們。UI相比於數據流,更靈活也更混亂,合理運用不一樣組件,可使得條例更清晰,代碼量更少。java
Activity
與 Fragment
雖然咱們常常在說單 Activity
多 Fragment
的架構,但官方推薦的架構並非單 Activity
多 Fragment
的架構,若是咱們去看他的文檔或示例代碼,咱們能夠獲得官方一個推薦的職責劃分:android
Activity
用於模塊,而 Fragment
用於流程數組
例如官方一個用戶註冊模塊,一個 RegisterActivity
表示註冊,而後有 RegisterUserNameFragment
、RegisterAvatarFragment
等來表示註冊的各個步驟,它們都公用同一個數據對象,那麼咱們就能夠把數據放在 RegisterActivity
的 ViewModel
裏。而註冊流程結束後,咱們釋放 RegisterActivity
時,同時也釋放了註冊相關的數據。這是一個比較優雅的方式:咱們即實現了數據的跨頁面使用,又在流程結束後將數據及時釋放。做爲最佳實踐,若是咱們的多個界面(Fragment
)須要用到同一批數據,那麼咱們就能夠用一個 Activity
來包裹這些 Fragment
。bash
舉一個反面例子,有些同窗完全貫徹單 Activity
多 Fragment
的架構來實現多 Fragment
的登陸,當登錄完成進入主頁後,那就須要銷燬登陸的各個 Fragment
,其作法就是遞歸的銷燬已經存在的各個 Fragment
, 耗時又耗力,並且銷燬 Fragment
還可能出翔(上文有提)。可是若是咱們採用一個 LoginActivity
來包裹這些 Fragment
, 那就在進入主界面後,直接 finish 掉 LoginActivity
,這樣不是更簡單嗎?微信
再以微信讀書講書來舉個例子,微信讀書講書點擊進去是一個講書界面,而後講書界面有一個目錄,能夠拿到講書人全部的講書,當點擊目錄的 item 時,刷新當前講書界面。 這是一個比較常見的類型,獲得、微課等都有這種界面,那麼這個界面你會如何設計呢?我來給下兩種實現:數據結構
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,是 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 換膚的實現與使用