Compose实战-实现一个带下拉加载更多功能的LazyColumn

前言:

LazyColumn类似于android原生中的listview,用来加载列表中的数据。

本文主要记录如何使用compose去实现一个带下拉加载更多功能的列表。

一.使用LazyColumn实现基本列表功能

首先构建ComposeListActivity,用来装载界面。

主要包含以下几块:

list:mutableStateListOf类型,可被观测状态的数据源。

ListGreeting:LazyColumn绘制的方法,展示list中的数据。

ListMessageCard:类似于ItemView,用来显示每一行的数据

loadMore:模拟加载更多数据的方法。

代码如下:

/**
 * @author lxl
 * compose带下拉加载更多功能的lazyColumn
 */
class ComposeListActivity : ComponentActivity() {
    private val list = mutableStateListOf();
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        loadMore()
        setContent {
            DemoClientTheme {
                // A surface container using the 'background' color from the theme
                ListGreeting()
            }
        }
    }


    @SuppressLint("UnrememberedMutableState")
    @Composable
    fun ListGreeting() {
        Log.i("lxltest","ListGreeting")
        val state = rememberLazyListState()
        Row(
            Modifier
                .padding(5.dp)
                .fillMaxWidth(),
            horizontalArrangement = Arrangement.Center
        ) {
            LazyColumn(Modifier.padding(5.dp), state = state) {
                items(list) { name ->
                    ListMessageCard(name)
                }
            }
            LoadMoreListHandler(listState = state) {
                Toast.makeText(this@ComposeListActivity, "加载中,请稍后", Toast.LENGTH_SHORT).show()
                loadMore()
            }
        }
    }

    @Composable
    fun ListMessageCard(name: String) {
        Row(modifier = Modifier.padding(all = 12.dp)) {
            Spacer(modifier = Modifier.width(10.dp))
            Column {
                Text(
                    text = name,
                    color = MaterialTheme.colors.secondaryVariant,
                    style = MaterialTheme.typography.subtitle2,
                    fontSize = 16.sp
                )
                Spacer(modifier = Modifier.width(12.dp))
            }
        }
    }

    private fun loadMore() {
        val start = list.size
        Log.i("lxltest", "加载start:${start}到${start + 30}")
        list.apply {
            for (i in 0 until 30) {
                add("text${start + i}")
            }
        }
    }
}

二.使用remember记录状态

compose区别于传统的安卓布局,compose是并不是使用回调的概念,而使用的一种观察者的模式。并且默认是无状态的,所以我们要使用remember来记录状态并设置到LazyColumn中去。

首先生成一个loadMore的续体,如果loadMore的状态发生了变化,则会进行回调。而状态是否发生变化,则是我们上面的remember中的判断。

我们这里判断如果距离底部还有1个时触发状态的变化,返回true。

val loadMore = remember {
        derivedStateOf {
            val layoutInfo = listState.layoutInfo
            val totalItemsCount = layoutInfo.totalItemsCount
            val lastVisibleItemIndex = (layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0) + 1
            val b = lastVisibleItemIndex > (totalItemsCount - buffer)
            Log.i(
                "lxltest",
                "lastVisibleItemIndex:${lastVisibleItemIndex},totalItemsCount:${totalItemsCount},buffer:${buffer},result:${b}"
            )
            b
        }
    }

然后把loadMore的续体传入LaunchedEffect中,这时候如果loadMore.value的状态发生变化,则会调用传进来的onLoaderMore进行通知

 LaunchedEffect(loadMore)
    {
        snapshotFlow { loadMore.value }
            .distinctUntilChanged()
            .collect {
                    onLoaderMore()
            }
    }

最完这些工作后,发现果然是可以了,拉拉到30,60临界点时,会自动加载新的数据。

但是也发现一个问题,就是每次加载数据,不是加载30条,而是60条。这是为何?

三.排查解决问题

在collect中加入日志辅助排查,终于找到了问题。原来loadMore状态变化通知时,会通知两次,第一次是状态发生变化的通知,此时it=true。有一次it=false,我怀疑有可能是修改完成的一个回调。具体没有去追踪,所以这里简单做一下it判断就可以了。完整代码如下:

@Composable
fun LoadMoreListHandler(listState: LazyListState, buffer: Int = 1, onLoaderMore: () -> Unit) {

    val loadMore = remember {
        derivedStateOf {
            val layoutInfo = listState.layoutInfo
            val totalItemsCount = layoutInfo.totalItemsCount
            val lastVisibleItemIndex = (layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0) + 1
            val b = lastVisibleItemIndex > (totalItemsCount - buffer)
            Log.i(
                "lxltest",
                "lastVisibleItemIndex:${lastVisibleItemIndex},totalItemsCount:${totalItemsCount},buffer:${buffer},result:${b}"
            )
            b
        }
    }

    LaunchedEffect(loadMore)
    {
        snapshotFlow { loadMore.value }
            .distinctUntilChanged()
            .collect {
                if (it) {
                    onLoaderMore()
                }
            }
    }
}

四.项目地址

完整代码地址:

GitHub - aa5279aa/android_all_demo: 一直觉得研究各种技术,一个个demo的下载运行太费劲了,为什么不能有一个所有新技术的融合体demo呢?项目为此而生

你可能感兴趣的:(android-compose,android,kotlin,compose)