【Jetpack Compose】再见了,RecyclerVie
背景
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行的代码可不是瞎写的,目前从性能体验上看,复杂长列表还是原生性能好些。不过问题总会解决的,提前储备知识吧。最后打个小广告:【字节跳动 国际化电商】诚招 前端、客户端、后端。
作者:搬砖小子出现了
链接:https://juejin.cn/post/6980181100228247559