【Android】业务逻辑分离用UserCase,UI逻辑分离用什么?来自定义UI碎片吧

观大佬文章有感,自定义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,这一期就此完结。

参考:

Android 业务逻辑应该如何写

Shatter

相关推荐
雨白1 小时前
Jetpack系列(二):Lifecycle与LiveData结合,打造响应式UI
android·android jetpack
kk爱闹3 小时前
【挑战14天学完python和pytorch】- day01
android·pytorch·python
每次的天空4 小时前
Android-自定义View的实战学习总结
android·学习·kotlin·音视频
恋猫de小郭5 小时前
Flutter Widget Preview 功能已合并到 master,提前在体验毛坯的预览支持
android·flutter·ios
断剑重铸之日6 小时前
Android自定义相机开发(类似OCR扫描相机)
android
随心最为安6 小时前
Android Library Maven 发布完整流程指南
android
岁月玲珑6 小时前
【使用Android Studio调试手机app时候手机老掉线问题】
android·ide·android studio
还鮟10 小时前
CTF Web的数组巧用
android
小蜜蜂嗡嗡11 小时前
Android Studio flutter项目运行、打包时间太长
android·flutter·android studio
aqi0012 小时前
FFmpeg开发笔记(七十一)使用国产的QPlayer2实现双播放器观看视频
android·ffmpeg·音视频·流媒体