观大佬文章有感,自定义UI碎片Shatter
前言
故事得从雪人大佬的文章 Android 业务逻辑应该如何写 开始说起,对于 UI 的组件,View 的组织形式,如何更方便的搭建UI,我看了大佬的文章是深有感触。
难道把所有的布局全部堆积在一个 XML 文件里不行吗?当然可以!
但是不利于调试,不利于抽取,不利于"搭积木"等等弊端。稍微复杂一些的页面,比如我们 App 中的用户信息页面,不同的用户状态展示不同的布局,不同的选择条件还会触发隐藏的布局,类似的稍微复杂一些的UI效果还不算弹窗和下拉选等逻辑随便都是2000行起。
如果还带上布局中的一些弹窗,下拉选,PickerView等操作,这下Activity光写这些UI逻辑都要1000行代码。
这里需要@一下Compose,也难怪谷歌下场强推 Compose 构建布局,从这个角度来说 Comoose 的确更方便。
那 XML/View 的主流体系怎么办呢?也不是没有解决的办法,大佬的文章不就提出了一种解决方案,我们完全可以按照雪人大佬的方案,搞一个UI的碎片来分量这些UI与UI逻辑
不过大佬的 LightFragment 并没有开源,我在之前的开源框架 Shatter 上做出修改,适配 Kotlin协程+Flow+Lifecyclede的新版本,开源出来供大家交流学习。
一、方案选型
首先我们带着问题来看到拆分这个问题。
能不能拆分一些复杂的UI呢?以哪种方案拆分呢?又怎么拆分呢?各种方案有哪些利弊?又有哪些需要解决的问题呢?
XML 全部堆叠在一起连调试起来都不方便,找到 View 都很困难,更改样式之后想查看效果都费劲,一般的做法也就是分解 XML ,把布局分解为一个个的小块,然后 include 进来。或者使用 ViewStub 等手段去分块。
但是这只是 XML 级别的,对应 View 的操作还是在 Activity / Fragment 中,难免会导致 Activity 的臃肿,之前我们是可以通过 UserCase Repository 等手段拆分 ViewModel 的业务逻辑,让其不要太臃肿,为什么很少会提到 UI 的臃肿,难道大家都不关心 UI 的分离吗?还是大家都用 Compose 了。
有同学会说,我把 XML 拆分的布局用一个个 View 对象来承载不就行了吗? 其实确实是可以的,可以"搭积木"了,但是有一个很大弊端是本身无法感知生命周期、没有统一的管理、无法相互通信。
上文的大佬提到的一种方案是自定义了一种 LightFragment 的方案,把一个个子布局用自定义的 LightFragment 包装,LightFragment 拥有和原生Fragment几乎一样的能力,通过 LightFragmentManager 来统一管理调度和发送事件实现通信能力,通过 LightFragmentManager 来分发生命周期。
首先这个 LightFragment 并没有开源,我无法得知实现逻辑,不知道是不是基于 Fragment 实现的,个人觉得 Fragment 还是太重,根据大佬介绍的 Shatter 库来看他的实现倒是比较符合我们的使用场景。
Shatter 的粒度比 Fragment 要小,是承载 XML 布局的碎片,可以用在 Activity / Fragment 中,可以适配单A多F架构,可以自行感知 Activity / Fragment 的生命周期,甚至还可以做无布局的功能型碎片。通过 Flow 实现 Shatter 内部的通信。
只是可惜的是老版本 Shatter 已经是 6 - 8 年前的项目了,停止维护了,还是使用切面 aspect 来实现一些事件的监听,有点古老。
我就在此基础上重写一版,支持预加载,同步加载与异步加载、占位加载等配置,下面就先实现简单的基础功能。
一、定义Shatter支持生命周期感知
首先我们先定义 Shatter 并实现生命周期感知。
kotlin
abstract class Shatter : LifecycleOwner {
var shatterManager: ShatterManager? = null
var containView: View? = null
private var lifecycleOwner: LifecycleOwner? = null
private var preloadedView: View? = null
private var isPreloading: Boolean = false
private var isPreloadCompleted: Boolean = false
private var onViewReadyListener: OnViewReadyListener? = null
interface OnViewReadyListener {
fun onViewReady(view: View)
}
val lifecycleScope: LifecycleCoroutineScope?
get() = lifecycleOwner?.lifecycle?.coroutineScope
// 子碎片列表
internal val childShatters = mutableListOf<Shatter>()
// 添加子碎片
fun addChildShatters(shatter: Shatter) = apply {
childShatters.add(shatter)
}
// 添加带有包含视图的子碎片
fun addChildShatters(@IdRes containViewId: Int, shatter: Shatter) = apply {
val view: View? = when (lifecycleOwner) {
is FragmentActivity -> (lifecycleOwner as FragmentActivity).findViewById(containViewId)
is Fragment -> (lifecycleOwner as Fragment).requireView().findViewById(containViewId)
else -> null
}
shatter.containView = view
childShatters.add(shatter)
}
@PublishedApi
internal var viewBinding: ViewBinding? = null
// 获取ViewBinding实例
inline fun <reified B : ViewBinding> getBinding(): B {
return if (viewBinding == null) {
val method = B::class.java.getMethod("bind", View::class.java)
val viewBinding = method.invoke(null, containView) as B
this.viewBinding = viewBinding
viewBinding
} else {
viewBinding as B
}
}
// 将子碎片附加到父碎片
fun attachChildShatter(asyncLoad: Boolean) {
childShatters.forEachIndexed { index, shatter ->
shatter.shatterManager = shatterManager
shatter.attachToLifecycleOwner(lifecycleOwner, asyncLoad, index)
shatterManager?.cache?.putShatter(shatter)
}
}
// 通用的加载视图方法
private fun loadView(lifecycleOwner: LifecycleOwner?, parentGroup: ViewGroup, asyncLoad: Boolean, completion: (View) -> Unit) {
if (getLayoutResId() != 0) {
if (asyncLoad) {
// 异步加载
val asyncLayoutInflater = AsyncLayoutInflater(ShatterManager.applicationContext)
asyncLayoutInflater.inflate(getLayoutResId(), parentGroup) { view, _, _ ->
completion(view) // 使用回调返回加载完成的视图
}
} else {
// 同步加载
lifecycleOwner?.run {
val view = LayoutInflater.from(ShatterManager.applicationContext).inflate(getLayoutResId(), parentGroup, false)
completion(view) // 使用回调返回加载完成的视图
}
}
}
}
// 预加载
fun preload(lifecycleOwner: LifecycleOwner?, asyncLoad: Boolean) {
this.lifecycleOwner = lifecycleOwner
if (!isPreloading) {
isPreloading = true
loadView(lifecycleOwner, RelativeLayout(ShatterManager.applicationContext), asyncLoad) { view ->
// 预加载完成
preloadedView = view
isPreloadCompleted = true
isPreloading = false
onViewReadyListener?.onViewReady(view)
Log.w("Shatter", "预加载完成啦")
}
}
}
//绑定Lifecycle,加载布局,添加到容器
fun attachToLifecycleOwner(lifecycleOwner: LifecycleOwner?, asyncLoad: Boolean, index: Int) {
this.lifecycleOwner = lifecycleOwner
if (getLayoutResId() != 0) {
if (containView != null && containView is ViewGroup) {
if (preloadedView != null && isPreloadCompleted) {
// 如果预加载已完成,则添加到containView
addView2Group(preloadedView, index)
containView = preloadedView
preloadedView = null // 清除引用
// 初始化Shatter调用onCreate等方法
onShatterCreate(lifecycleOwner)
} else {
// 设置布局加载的监听
onViewReadyListener = object : OnViewReadyListener {
override fun onViewReady(view: View) {
addView2Group(view, index)
containView = view
// 在添加视图后清除回调,防止内存泄漏
onViewReadyListener = null
// 初始化Shatter调用onCreate等方法
onShatterCreate(lifecycleOwner)
}
}
// 如果当前没有进行预加载,则启动加载过程
if (!isPreloading) {
loadView(lifecycleOwner, containView as ViewGroup, asyncLoad) { view ->
onViewReadyListener?.onViewReady(view)
}
}
}
}
} else {
// 直接初始化无布局的Shatter
onShatterCreate(lifecycleOwner)
}
}
private fun addView2Group(view: View?, index: Int) {
val groupView = containView as ViewGroup
val childCount = groupView.childCount
if (view == null) return
// 如果索引在当前子视图数量的范围内
if (index < childCount) {
groupView.removeViewAt(index)
groupView.addView(view, index)
} else {
// 如果索引等于或大于当前子视图数量,添加空视图作为占位符,直到达到指定的索引,然后添加目标视图
for (i in childCount until index) {
groupView.addView(View(ShatterManager.applicationContext).apply {
visibility = View.INVISIBLE
})
}
groupView.addView(view, index)
}
}
// 当碎片被创建时调用
private fun onShatterCreate(lifecycleOwner: LifecycleOwner?) {
if (lifecycleOwner is FragmentActivity) {
val activity = lifecycleOwner
onCreate(activity.intent)
initView(containView, activity.intent)
initData(activity.intent)
} else if (lifecycleOwner is Fragment) {
onCreate(null)
initView(containView, null)
initData(null)
}
}
// 获取布局资源ID
@LayoutRes
abstract fun getLayoutResId(): Int
// 获取碎片标签
open fun getTag(): String = this::class.java.simpleName
// 查找指定类型的碎片
open fun <T : Shatter> findShatter(clazz: Class<T>): T? {
val tag = clazz.simpleName
val shatter = shatterManager?.cache?.getShatter(tag)
if (shatter != null) {
return shatter as T
}
return null
}
open fun onCreate(intent: Intent?) {}
open fun initView(view: View?, intent: Intent?) {}
// 初始化数据
open fun initData(intent: Intent?) {}
}
内部的主要实现是布局的加载方式,这里支持异步加载和预加载和占位加载。
然后我们可以通过 ShatterManager 的方式来监听生命周期并驱动子 Shatter 的生命周期。
kotlin
class ShatterManager(private val lifecycleOwner: LifecycleOwner) : LifecycleEventObserver {
private val shatters = mutableListOf<Shatter>()
internal val cache = ShatterCache()
companion object {
//管理全局上下文
lateinit var applicationContext: Context
//初始化框架,接收上下文
fun init(application: Application) {
applicationContext = application.applicationContext
}
}
init {
lifecycleOwner.lifecycle.removeObserver(this)
//添加了生命周期的监听
lifecycleOwner.lifecycle.addObserver(this)
}
//这里只对Destroy生命周期回调做了处理,自动处理页面对应的Shatter销毁逻辑
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
shatters.forEach { it.onStateChanged(source, event) }
if (event == Lifecycle.Event.ON_START) {
shatters.forEach { it.onStart() }
} else if (event == Lifecycle.Event.ON_RESUME) {
shatters.forEach { it.onResume() }
} else if (event == Lifecycle.Event.ON_PAUSE) {
shatters.forEach { it.onPause() }
} else if (event == Lifecycle.Event.ON_STOP) {
shatters.forEach { it.onStop() }
} else if (event == Lifecycle.Event.ON_DESTROY) {
shatters.forEach { it.onDestroy() }
destroy()
source.lifecycle.removeObserver(this)
}
}
/**
* 入口1 添加Shatter到指定ID的容器中,
*
* @param containViewId 指定添加容器的布局Id
* @param shatter 待添加的子碎片对象
* @param isAsync 是否以异步加载的方式加载布局
* @param index 如果有多个布局添加,指定添加到容器中的索引位置
*
*/
fun addShatter(@IdRes containViewId: Int, shatter: Shatter, isAsync: Boolean = false, index: Int = -1) = apply {
shatter.shatterManager = this
val view: View? = when (lifecycleOwner) {
is FragmentActivity -> lifecycleOwner.findViewById(containViewId)
is Fragment -> lifecycleOwner.requireView().findViewById(containViewId)
else -> null
}
addShatter(view, shatter, isAsync, index)
}
/**
* 入口2 添加Shatter到指定的容器中,
*
* @param containView 指定添加容器的ViewGroup对象
* @param shatter 待添加的子碎片对象
* @param isAsync 是否以异步加载的方式加载布局
* @param index 如果有多个布局添加,指定添加到容器中的索引位置
*
*/
fun addShatter(containView: View?, shatter: Shatter, isAsync: Boolean = false, index: Int = -1) = apply {
shatter.shatterManager = this
shatter.containView = containView
shatter.attachToLifecycleOwner(lifecycleOwner, isAsync, index)
shatters.add(shatter)
cache.putShatter(shatter)
shatter.attachChildShatter(isAsync)
}
/**
*入口3 添加非布局类型的Shatter
*
* 不需要设置布局的加载方式和索引
*/
fun addShatter(shatter: Shatter) = apply {
shatter.shatterManager = this
shatter.attachToLifecycleOwner(lifecycleOwner, false, -1)
shatters.add(shatter)
cache.putShatter(shatter)
shatter.attachChildShatter(false)
}
/**
* 入口4 预加载Shatter对象和布局
*
*/
fun preloadShatter(shatter: Shatter, isAsync: Boolean = false) {
shatter.shatterManager = this
shatter.preload(lifecycleOwner, isAsync)
}
//移除指定的Shatter
fun remove(shatter: Shatter) {
shatters.find { it.getTag() == shatter.getTag() }?.childShatters?.forEach {
cache.removeShatter(it.getTag())
}
cache.removeShatter(shatter.getTag())
shatters.remove(shatter)
}
//找到指定的Shatter
fun <T : Shatter> findShatter(clazz: Class<T>): T? {
val tag = clazz.simpleName
val shatter = shatters.find { it.getTag() == tag } ?: return null
return shatter as T
}
//销毁全部的Shatter,清空全部缓存
private fun destroy() {
cache.clear()
shatters.forEach { it.childShatters.clear() }
shatters.clear()
}
}
这里就支持了 Shatter 的核心布局加载和基础的生命周期监听啦。
二、支持Activity与Fragment的生命周期
由于 LifecycleEventObserver 仅能支持简单的生命周期回调,对于一些特殊的生命周期回调还是需要我们单独的处理。
例如我们可以定义一个接口来定义一些常用的回调:
kotlin
interface ShatterLifecycleListener {
// 通用的生命周期
fun onStart()
fun onResume()
fun onPause()
fun onStop()
fun onDestroy()
fun onRestart()
fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?)
//Activity 的特有生命周期
fun onNewIntent(intent: Intent?)
fun onSaveInstanceState(outState: Bundle?)
fun onRestoreInstanceState(savedInstanceState: Bundle?)
fun enableOnBackPressed(): Boolean
//Fragment 的特有回调
fun onHiddenChanged(isHidden: Boolean)
}
那么我们在 Shatter 和 ShatterManager 中就需要处理这些生命周期:
kotlin
abstract class Shatter : ShatterLifecycleListener, LifecycleOwner {
...
// 处理新的Intent
override fun onNewIntent(intent: Intent?) {
childShatters.forEach { it.onNewIntent(intent) }
}
// 保存实例状态
override fun onSaveInstanceState(outState: Bundle?) {
childShatters.forEach { it.onSaveInstanceState(outState) }
}
// 恢复实例状态
override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
childShatters.forEach { it.onRestoreInstanceState(savedInstanceState) }
}
// 开始
override fun onStart() {
childShatters.forEach { it.onStart() }
}
//重新启动
override fun onRestart() {
childShatters.forEach { it.onRestart() }
}
// 恢复
override fun onResume() {
childShatters.forEach { it.onResume() }
}
// 暂停
override fun onPause() {
childShatters.forEach { it.onPause() }
}
// 停止
override fun onStop() {
childShatters.forEach { it.onStop() }
}
// 销毁
override fun onDestroy() {
childShatters.forEach { it.onDestroy() }
}
// 处理ActivityResult
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
childShatters.forEach { it.onActivityResult(requestCode, resultCode, data) }
}
// 是否允许返回键
override fun enableOnBackPressed(): Boolean {
return childShatters.find { !it.enableOnBackPressed() } == null
}
//Fragment 是否可见
override fun onHiddenChanged(isHidden: Boolean) {
childShatters.forEach { it.onHiddenChanged(isHidden) }
}
//给ShatterManager遍历调用
fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
childShatters.forEach { it.onStateChanged(source, event) }
}
override val lifecycle: Lifecycle
get() = lifecycleOwner!!.lifecycle
}
当然一些特殊的生命周期还是都需要从 ShatterManager 开始分发:
kotlin
//这里只对Destroy生命周期回调做了处理,自动处理页面对应的Shatter销毁逻辑
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
shatters.forEach { it.onStateChanged(source, event) }
if (event == Lifecycle.Event.ON_START) {
shatters.forEach { it.onStart() }
} else if (event == Lifecycle.Event.ON_RESUME) {
shatters.forEach { it.onResume() }
} else if (event == Lifecycle.Event.ON_PAUSE) {
shatters.forEach { it.onPause() }
} else if (event == Lifecycle.Event.ON_STOP) {
shatters.forEach { it.onStop() }
} else if (event == Lifecycle.Event.ON_DESTROY) {
shatters.forEach { it.onDestroy() }
destroy()
source.lifecycle.removeObserver(this)
}
}
fun onNewIntent(intent: Intent?) {
if (lifecycleOwner is FragmentActivity)
shatters.forEach { it.onNewIntent(intent) }
}
fun onRestart() {
if (lifecycleOwner is FragmentActivity)
shatters.forEach { it.onRestart() }
}
fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
shatters.forEach { it.onActivityResult(requestCode, resultCode, data) }
}
fun enableOnBackPressed(): Boolean {
return if (lifecycleOwner is FragmentActivity) {
shatters.find { !it.enableOnBackPressed() } == null
} else {
true
}
}
fun onHiddenChanged(isHidden: Boolean) {
if (lifecycleOwner is Fragment)
shatters.forEach { it.onHiddenChanged(isHidden) }
}
其实我们可以在基类的 Activity/Fragment 中进行分发
Activity:
kotlin
// Shatter 的逻辑
protected var mShatterManager: ShatterManager? = null
//此页面是否启动Shatter
protected open fun isShatterEnable(): Boolean {
return false
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
mShatterManager?.onNewIntent(intent)
}
override fun onRestart() {
super.onRestart()
mShatterManager?.onRestart()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
mShatterManager?.onActivityResult(requestCode, resultCode, data)
}
override fun onBackPressed() {
if (mShatterManager != null) {
if (mShatterManager!!.enableOnBackPressed()) {
super.onBackPressed()
}
} else {
super.onBackPressed()
}
}
Fragment:
kotlin
// Shatter 的逻辑
protected var mShatterManager: ShatterManager? = null
//此页面是否启动Shatter
protected open fun isShatterEnable(): Boolean {
return false
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
mShatterManager?.onActivityResult(requestCode, resultCode, data)
}
override fun onHiddenChanged(hidden: Boolean) {
super.onHiddenChanged(hidden)
mShatterManager?.onHiddenChanged(hidden)
}
当然你可以用Kotlin的扩展或委托来实现,说到这里无比想念 Dart 的混入概念,太强了。
三、Flow通知管理
那么很关键的一个碎片中的逻辑如何影响到另一个碎片呢?
如果直接通过 Activity 直接拿到 Shatter 对象去调用方法,也不是不行,但是会更容易导致空指针,因为这个Shatter可能不存可能没加载,使用消息通知的方式更优雅。
大佬们选择的是 EvevtBus 或 RxJava,这... 不是RxJava用不起而是Flow更有性价比啊,现在项目都是Kotlin了根本没导入那些库了好吗?
我们在 ShatterManager 中实现 Flow 的发送和接收逻辑,由于我们的一个 ShatterManager 本身就是跟一个 Activity 或 Fragment 绑定的,所以我们直接拿到 LifecycleOwner 的 lifecycleScope 即可完成 Flow 的操作了。
kotlin
class ShatterManager(private val lifecycleOwner: LifecycleOwner) : LifecycleEventObserver {
private val newMsgFlow: MutableSharedFlow<ShatterEvent<*>> by lazy {
MutableSharedFlow()
}
init {
//协程接收Flow
lifecycleOwner.lifecycleScope.launch {
newMsgFlow.collectLatest { event ->
shatters.forEach { it.onShatterEvent(event.key, event.data) }
}
}
}
//通过Flow发送事件
fun sendShatterEvent(key: String, data: Any? = null) {
lifecycleOwner.lifecycleScope.launch {
newMsgFlow.emit(ShatterEvent(key, data))
}
}
}
Shatter 中定义各自的接收和发送方法:
kotlin
abstract class Shatter : ShatterLifecycleListener, LifecycleOwner {
// 发送碎片事件
open fun sendShatterEvent(key: String, data: Any? = null) {
shatterManager?.sendShatterEvent(key, data)
}
// 接收处理碎片事件
open fun onShatterEvent(key: String, data: Any?) {
childShatters.forEach {
it.onShatterEvent(key, data)
}
}
}
四、使用示例
初始化:
kotlin
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
ShatterManager.init(this)
}
}
Activity:
kotlin
class DemoActivity : AppCompatActivity() {
lateinit var buttonShatter: ShatterButton
protected var mShatterManager: ShatterManager? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_demo)
//预加载Shatter
mShatterManager = ShatterManager(this)
buttonShatter = ShatterButton()
mShatterManager?.preloadShatter(buttonShatter, isAsync = false)
findViewById<Button>(R.id.btn_load_shatter).setOnClickListener {
loadShatter()
}
}
private fun loadShatter() {
mShatterManager
?.addShatter(R.id.fl_content, ShatterText(), isAsync = true, index = 0)
?.addShatter(R.id.fl_content, buttonShatter, index = 2)
?.addShatter(R.id.fl_content, ShatterText2(), isAsync = false, index = 1)
?.sendShatterEvent("Message1", "来自Activity,加载Shatter按钮的消息")
}
inner class ShatterButton : Shatter() {
override fun getLayoutResId(): Int = R.layout.shatter_register_button
override fun onCreate(intent: Intent?) {
super.onCreate(intent)
Log.w("MainActivity", "ShatterButton -> onCreate")
}
override fun initView(view: View?, intent: Intent?) {
super.initView(view, intent)
Log.w("MainActivity", "ShatterButton -> initView")
val binding = getBinding<ShatterRegisterButtonBinding>()
binding.btnRegister1.setOnClickListener {
Toast.makeText(applicationContext, "发送了消息", Toast.LENGTH_SHORT).show()
sendShatterEvent("Message1", "我是来自ButtonShatter的信息")
}
}
override fun initData(intent: Intent?) {
super.initData(intent)
Log.w("MainActivity", "ShatterButton -> initData")
}
override fun onResume() {
super.onResume()
Log.w("MainActivity", "ShatterButton -> onResume")
}
override fun onPause() {
super.onPause()
Log.w("MainActivity", "ShatterButton -> onPause")
}
override fun onRestart() {
super.onRestart()
Log.w("MainActivity", "ShatterButton -> onRestart")
}
override fun onStart() {
super.onStart()
Log.w("MainActivity", "ShatterButton -> onStart")
}
override fun onStop() {
super.onStop()
Log.w("MainActivity", "ShatterButton -> onStop")
}
override fun onDestroy() {
super.onDestroy()
Log.w("MainActivity", "ShatterButton -> onDestroy")
}
}
inner class ShatterText : Shatter() {
override fun getLayoutResId(): Int = R.layout.shatter_register_text
override fun initView(view: View?, intent: Intent?) {
super.initView(view, intent)
val binding = getBinding<ShatterRegisterTextBinding>()
binding.tvReLogin.setOnClickListener {
Toast.makeText(applicationContext, "点击重新登录", Toast.LENGTH_SHORT).show()
}
}
}
inner class ShatterText2 : Shatter() {
private lateinit var binding: ShatterRegisterText2Binding
override fun getLayoutResId(): Int = R.layout.shatter_register_text2
override fun initView(view: View?, intent: Intent?) {
super.initView(view, intent)
binding = getBinding<ShatterRegisterText2Binding>()
}
override fun onShatterEvent(key: String, data: Any?) {
super.onShatterEvent(key, data)
when (key) {
"Message1" -> {
binding.tvMessage.text = data?.toString()
}
}
}
}
}
注意看异步加载、索引占位的效果:
生命周期回调:
跳转页面返回页面的生命周期也是一致的,大家可以自行测试。
后记
这样我们就实现了一个基于XML体系拆分到自定义 View 的 Shatter 碎片了,通过 ShatterManager 管理生命周期分发和消息通知,支持异步加载,支持索引占位,支持预加载,更轻量级的UI拆分方案。
比如我目前在用的项目是基于单A多F架构,通过 Navigation 实现 Fragment 跳转,部分复杂的 Fragment 页面内部选择使用 Shatter 拆分UI,充分利用 Android 多线程优势,使用异步占位加载布局提升流畅度。
相比include ViewStub AsyncViewStub等方案,还是在Activity中处理逻辑,此方案可以把UI逻辑也分离出去,避免Activity的逻辑臃肿。
例如我们的一个 Shatter 碎片,用于上传用户的证件,布局就是一个线性布局,一个帧布局,内部根据用户的状态,审核状态,展示证件图片,默认图片,删除图标,拒绝原因,等等操作。逻辑不复杂但是也不是很简单,我们还需要处理图库选择,相机拍照,压缩,裁剪,上传,等一系列的UI逻辑,使用Shatter之后,我们就能分离这一部分UI逻辑,一是逻辑结构清晰,二是便于复用。
类似的实现还有很多,比如标题栏,带历史的搜索框,九宫格展图片示或选取等等。
需要注意的是对 Java 项目不友好,用到了 Kotlin 的特性,当然如果要支持 Java 项目其实可是可以的,包括重复加载的释放问题,对基类的侵占问题,无用初始化问题,后期看大家的反馈情况看看是否继续更新。
通过这种方案就可以把2000行的XML布局拆分到一个个子碎片,方便同事协调开发,个人负责个人的碎片通过Flow进行通信,最大限度的减少冲突问题,达到高效"搭积木"的效果。
本文源码奉上,恳请各位大佬高工指点 【传送门】 。
最后惯例,如果有其他的更多的更好的实现方式,也希望大家能评论区交流一起学习进步。如果我的文章有错别字,不通顺的,或者代码/注释有错漏的地方,同学们都可以指出修正。
如果感觉本文对你有一点的启发和帮助,还望你能点赞
支持一下,你的支持对我真的很重要。
Ok,这一期就此完结。
参考: