上一篇文章讲了一些关于 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 换肤的实现与使用