【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

相关推荐
枯骨成佛30 分钟前
Android中Crash Debug技巧
android
kim56596 小时前
android studio 更改gradle版本方法(备忘)
android·ide·gradle·android studio
咸芝麻鱼6 小时前
Android Studio | 最新版本配置要求高,JDK运行环境不适配,导致无法启动App
android·ide·android studio
无所谓จุ๊บ6 小时前
Android Studio使用c++编写
android·c++
csucoderlee6 小时前
Android Studio的新界面New UI,怎么切换回老界面
android·ui·android studio
kim56596 小时前
各版本android studio下载地址
android·ide·android studio
饮啦冰美式6 小时前
Android Studio 将项目打包成apk文件
android·ide·android studio
夜色。7 小时前
Unity6 + Android Studio 开发环境搭建【备忘】
android·unity·android studio
ROCKY_8178 小时前
AndroidStudio-滚动视图ScrollView
android
趴菜小玩家9 小时前
使用 Gradle 插件优化 Flutter Android 插件开发中的 Flutter 依赖缺失问题
android·flutter·gradle