Skip to content

声明式UI布局

声明式布局,到底是什么?

  • 一种在构建MVVM业务逻辑阶段,就需要把所有情况定义好的UI布局,通过数据去推动页面的更新状态形式。(这是一种绝对的数据状态驱动UI展示逻辑,而不再是获取到数据之后,再去根据数据手动更新UI)
  • 目前不仅是Jetpack Compose使用了这样的一个状态,iOS的SwiftUI,Google的Flutter Platform,还有Facebook搞的那个React Native,也都是这样的一种开发模式。

需要改变的思维

  • 这与传统的把页面写好,然后通过最新数据去update不一样。Jetpack Compose存在一个概念,Android官方称之为重组。
  • 我理解的重组:有点像我们构建好了一切的可能性,然后根据最新的数据状态去展示相应的UI。
  • 重组范围:这个点也比较核心重要

A Sample

以一个滚动列表的布局为Sample,XML布局下面它的写法可能如下:

xml
<FrameLayout>
    <RecyclerView
        android:id="@+id/rv_list"/>

    <ErrorLayout
        android:id="@+id/error_layout"/>

    <EmptyLayout
        android:id="@+id/empty_layout"/>
</FrameLayout>
kotlin
class CommendFragment: Fragment() {
    // ...... 省略ViewDataBinding、RecyclerView Adapter和ViewModel等等这些资源的实例化等等内容

    override fun onViewCreate() {
        viewModel.uiData.observe(this.viewLifecycleOwner) {
            when (it) {
                is Success -> {
                    binding.rvList.visible = View.VISIBLE
                    binding.errorLayout.visible = View.GONE
                    binding.emptyLayout.visible = View.GONE

                    rvListAdapter.setData(it.data)
                }
                is Failed -> {
                    binding.rvList.visible = View.GONE
                    binding.errorLayout.visible = View.VISIBLE
                    binding.emptyLayout.visible = View.GONE
                }
                is Empty -> {
                    binding.rvList.visible = View.GONE
                    binding.errorLayout.visible = View.GONE
                    binding.emptyLayout.visible = View.VISIBLE
                }
            }
        }
    }
}

而如果采用Jetpack Compose去构建这个页面,则页面如下展示

kotlin
@Composable
fun SuccessUI() {
    // ...... SuccessUI
    val viewModel = viewModels()
    val data = viewModel.uiDateState.value as? Success ?: return

    // Build the UI By data
}

@Composable
fun FailedUI() {
    // ...... FailedUI
    // Build the Failed UI
}

@Composable
fun EmptyUI() {
    // ...... EmptyUI
    // Build the Empty UI
}

@Composable
fun HostUI() {
    val viewModel = viewModels()
    val uiDataState = viewModel.uiDataState.value

    when (uiDataState) {
        is Success -> {
            SuccessUI()
        }
        is Failed -> {
            FailedUI()
        }
        is Empty -> {
            EmptyUI()
        }
    }
}
  • 而且,Jetpack Compose还有一个最重要的核心点,它的SuccessUI、FailedUI、EmptyUI是完全独立开的,如果是团队协作的过程中,完全可以由架构师、或者Feature Owner去构建好HostUI所决定的整个结构的接口或者空实现等内容,然后交由团队内部的其他成员去落实具体每一个UI页面的实现代码。
  • 这一点,如果是使用XML去构建页面,过多的代码耦合在同一个文件,如果采用多人协作不同状态的UI,在发起PR的时候,极大可能会出现Merge Confirm的情况。而采用Jetpack Compose,可以将不同的UI放在不同的文件下,避免Merge Confirm的出现,在很大的程度上,是可以提高团队开发协作效率的。

目前Jetpack Compose存在的问题

  • 控件不熟悉

    对于传统的横纵向排放元素,直觉上使用LinearLayout配合weight权重或者ConstraintLayout的约束关系,可以完美解决这个问题,然而在Jetpack Compose,可能会一下子想不到使用什么样的控件去实现这个效果比较合适。

    这个问题的本质,还是开发者对于一些控件能够实现的效果熟悉程度不足,多用就行。(Row和Column可以完美解决这个问题)

  • 性能卡顿问题

    我个人尝试性使用Jetpack Compose的LazyHorzontialColumn + Coil(一个适用于Jetpack Compose的图片加载库)去实现一个瀑布流的相册,然而实际对比传统的RecyclerView + Glide,出现图片加载速度慢、图片数量级上来之后出现掉帧等情况。

随便写写的,喜欢就好。 使用VitePress构建