使用 Jetpack Compose 轻松构建长列表!
Jetpack Compose 在 7.1 号发布了 Release Candidate(候选发布版),距离正式版也越来越近。那么【声明式UI】框架来袭,对比Android传统开发模式有何不同呢?今天我们以长列表的构建为例来展示声明式UI带来的效率提升。
RecyclerView
基本
我们在使用RecyclerView时需要编写RecyclerView.Adapter
,这是一个重复性很高的工作:
创建 Adapter
创建 ViewHolder
如果有多样式还需要定义 ViewType
class CustomAdapter(private val dataSet: Array<String>) :
RecyclerView.Adapter<CustomAdapter.ViewHolder>() {
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val textView: TextView
init {
textView = view.findViewById(R.id.textView)
}
}
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(viewGroup.context)
.inflate(R.layout.text_row_item, viewGroup, false)
return ViewHolder(view)
}
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
viewHolder.textView.text = dataSet[position]
}
override fun getItemCount() = dataSet.size
}
改进
模板代码都可使用封装来简化使用,于是出现了各种Adapter
封装库,我们以drakeet
的MultiType
库为例,看看是如何简化构建RecyclerView
的。
定义 ViewDelegate
class FooViewDelegate : ViewDelegate<Foo, FooView>() {
override fun onCreateView(context: Context): FooView {
return FooView(context).apply { layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT) }
}
override fun onBindView(view: FooView, item: Foo) {
view.imageView.setImageResource(item.imageResId)
view.textView.text = item.text
}
}
注册到 MultiTypeAdaper
class SampleActivity : AppCompatActivity() {
private val adapter = MultiTypeAdapter()
private val items = ArrayList<Any>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_list)
val recyclerView = findViewById<RecyclerView>(R.id.list)
//注册到adapter
adapter.register(FooViewDelegate())
...
}
}
可以看到我们只需要关注ViewDelegate
(类似于ViewHolder
)的构建即可,不用去编写Adapter
,不需要显式定义ViewType
(在构建ViewDelegate
传入的数据类型已经起了这个作用)。这在复杂的长列表构建场景中极大减小了工作量,使代码更易于维护。
Jetpack Compose
基本
我们先来看Jetpack Compose最基本的长列表构建:
构建自己的视图
@Composable
fun VideoSection(item: Video) {
Box(modifier = Modifier.padding(top = 5.dp)) {
NetworkImage(
url = item.cover ?: "",
modifier = Modifier
.requiredHeight(height = 220.dp)
.fillMaxWidth(),
)
Image(
painter = painterResource(id = R.mipmap.video_play),
"play icon",
modifier = Modifier.align(Alignment.Center)
)
Text(
text = item.title ?: "",
color = Color.White,
modifier = Modifier.align(Alignment.BottomStart)
)
Text(
text = "视频", color = Color.White, modifier = Modifier
.align(Alignment.TopEnd)
.padding(end = 8.dp)
)
}
}
创建长列表
@Composable
fun MainFeedContent(feedItems: List<FeedItem>) {
LazyColumn(modifier = Modifier.fillMaxWidth()) {
items(feedItems) {
when(it){
is Cover -> CoverSection(item = it)
is MultiImage -> MultiImageSection(item = it)
is Video -> VideoSection(item = it)
}
}
}
}
在Jetapck Compose里 Lazy*
对应 RecyclerView
,其中常用的有 LazyColumn
,LazyRow
,LazyGrid
。见名知意,不再一一介绍。
我们来看下LazyColumn
: 它有多种添加item
的方式,我们按需使用。
LazyColumn {
// Add a single item
item {
Text(text = "First item")
}
// Add 5 items
items(5) { index ->
Text(text = "Item: $index")
}
// Add another single item
item {
Text(text = "Last item")
}
}
@Composable
fun MessageList(messages: List<Message>) {
LazyColumn {
items(messages) { message ->
MessageRow(message)
}
}
}
使用Jetpack Compose 的 List
还有个好处:轻松构建粘性item
:
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun ListWithHeader(items: List<Item>) {
LazyColumn {
stickyHeader {
Header()
}
items(items) { item ->
ItemRow(item)
}
}
}
更多列表用法请参考官方文档
可以看到Jetpack Compose构建长列表是不是特别、特别简单,只需要关注自己的视图逻辑即可,没了复杂的构建步骤,这就是【声明式UI】的优势。
那还能再简化一点吗?我的LazyColumn
里要写好多判断条件啊,有100个类型岂不是when
里有100个判断!当然可以简化!不过判断类型肯定是要做的,就是放在那里的问题,代码挪挪位置,框架就出来了...来释放LazyColumn
吧.
改进
这个方案和MultiType
一样,把数据类型注册进Map
。不过原来我们都是放ViewHolder
/Delegate
啥的,那是具体的类,而@Composable
是函数,这个怎么存到Map
呢?当然也是定义接口,进行实例化啦 😹 !
定义抽象
interface IComposableService<T> {
val content: @Composable (item: T) -> Unit
@Suppress("UNCHECKED_CAST")
@Composable
fun ComposableItem(item: Any) {
(item as? T)?.let {
content(item)
}
}
}
业务实现接口
class CoverImpl : IComposableService<Cover> {
override val content: @Composable (item: Cover) -> Unit ={ item->
//具体视图
CoverSection(item = item)
}
}
构建Manager
object ComposableManager {
private val composableMap = HashMap<String, IComposableService<*>>()
fun register(key: String, composable: IComposableService<*>) {
composableMap[key] = composable
}
fun getComposable(key: String): IComposableService<*>? {
return composableMap[key]
}
}
注册
//注册
ComposableManager.register(Cover::class.java.name,CoverImpl())
使用
@Composable
fun MainFeedContent(feedItems: List<FeedItem>) {
LazyColumn(modifier = Modifier.fillMaxWidth()) {
items(feedItems) {
ComposableManager.getComposable(it::class.java.name)?.ComposableItem(it)
}
}
}
最终效果如下
如果连注册都不想手动写可以使用服务发现去收集....
代码已上传至GitHub : 传送门
总结
可以看到声明式给我们构建UI带来极大便利,让我们可以更多关注自己要做的那部分工作。不过RecyclerView
那近1w行的代码可不是瞎写的,目前从性能体验上看,复杂长列表还是原生性能好些。不过问题总会解决的,提前储备知识吧。
- FIN -
使用Jetpack Compose完成你的自定义Layout
Compose 博物馆网站:https://compose.net.cn/
添加微信进入 Compose 技术交流群