查看原文
其他

使用 Jetpack Compose 轻松构建长列表!

搬砖小子出现了 Jetpack Compose 博物馆 2022-07-13


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封装库,我们以drakeetMultiType库为例,看看是如何简化构建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完成自定义手势处理

使用Jetpack Compose完成你的自定义Layout


聊一聊Compose固有特性测量Intrinsic


主题配置繁琐?Compose帮你轻松搞定! 




Compose 博物馆网站:https://compose.net.cn/


添加微信进入 Compose 技术交流群



您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存