可滚动,类似RecyclerView( Column、Row 用 Modifier 设置滚动是相当于ScrollView)。
@Composable
fun Demo(dataList: List) {
val state = rememberLazyListState()
LazyColumn(
modifier = Modifier.height(100.dp),
state = state, //管理列表的状态,可通过这个操作列表滚动
contentPadding = PaddingValues(horizontal = 5.dp), //内容内边距
reverseLayout = false, //是否反转显示
verticalArrangement = Arrangement.Top, //排列方式,使用Arrangement.spacedBy(4.dp)就是设置间距
horizontalAlignment = Alignment.Start, //对齐方式
flingBehavior = ScrollableDefaults.flingBehavior(),
userScrollEnabled = true, //是否允许滑动
) { }
}
LazyListScope DSL 提供多种函数来描述列表中的条目,item() 添加单个条目,items() 添加多个条目。
fun Demo(dataList: List) {
LazyColumn {
//添加单个条目
item { Text(text = "单个条目") }
//添加多个条目
items(5) {index -> Text(text = "条目索引:$index") }
//根据数据源创建
items(
items = dataList, //可传入、数组、集合、Paging的LazyPagingItems
key = { element ->
element.id //key设为element的唯一值
}
) { item ->
//对条目布局使用该修饰符来对列表的更改添加动画效果
Row(Modifier.animateItemPlacement()) {}
}
//带索引
itemsIndexed(dataList) { index: Int, item: String ->
Text(text = "$index:$item", modifier = Modifier.padding(1.dp).background(Color.Red))
}
}
}
如果对 LazyRow 设置 Modifier.padding() 滑动的时候左右两端会有割裂感(没有滑出屏幕边缘),使用 contentPadding 后初始状态下会显示边距,滑动后是滑出屏幕外。
LazyColumn(
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp)
) { }
如果对 Item 设置padding,会导致相邻的两个 Item 之间是 2 个间距大小,首尾两个 Item 距离容器边缘是 1 个间距大小。
LazyColumn(
//使用Arrangement.Top就是排列方式
verticalArrangement = Arrangement.spacedBy(4.dp)
) { }
对条目布局使用修饰符来对列表的更改添加动画效果。
items(dataList, key = { it.id }) {
Row(Modifier.animateItemPlacement()) { }
}
不会跟随滚动,背景默认透明滑动后会穿透显示。
@Composable
fun Dode() {
LazyColumn(Modifier.height(100.dp)) {
stickyHeader {
Text(text = "头部", modifier = Modifier.background(Color.Gray))
}
items(10) { Text(text = "条目索引:$it") }
}
}
@Composable
fun ContactsList(grouped: Map>) {
LazyColumn {
grouped.forEach { (lastName, contactsForLastName) ->
stickyHeader {
CharacterHeader(lastName)
}
items(contactsForLastName) { contact ->
ContactListItem(contact)
}
}
}
}
详见:Compose - 使用 Paging
Paging 3.0 及更高版本通过
androidx.paging:paging-compose
库提供 Compose 支持。
@Composable
fun Demo() {
val pagingItems = viewModel.dataFlow.collectAsLazyPagingItems()
LazyColumn(
modifier = Modifier.fillMaxSize(),
state = rememberLazyListState()
) {
items(
count = pagingItems.itemCount,
key = pagingItems.itemKey { it.id }
) { index ->
//...
}
}
}
ViewModel {
val dataFlow by lazy {
Pager(PagingConfig(10)) {
NewestPagingSource(repository)
}.flow.cachedIn(viewModelScope)
}
}
应避免内部嵌套一个没有固定大小的同向可滚动子组合项,会抛出 IllegalStateException。可封装在同一个 LazyColumn 中,使用 LazyListScope DSL 传入不同类型的内容,得到相同的效果。
// Throws IllegalStateException
Column(modifier = Modifier.verticalScroll(state)) {
LazyColumn {...}
}
LazyColumn {
item { Header() }
items(data) { item ->
Item(item)
}
item { Footer() }
}
通常只需要监听第一个可见条目的信息,LazyListState 提供了 firstVisibleItemIndex(第一个可见条目的索引)和 firstVisibleItemScrollOffset(第一个可见条目偏移量)属性。
@Composable
fun MessageList(messages: List) {
Box {
val listState = rememberLazyListState()
LazyColumn(state = listState) {
//...
}
val showButton by remember {
derivedStateOf { listState.firstVisibleItemIndex > 0 }
}
AnimatedVisibility(visible = showButton) {
ScrollToTopButton()
}
}
}
当您需要更新其他界面可组合项时,在组合中直接读取状态非常有效,但在某些情况下,系统无需在同一组合中处理此事件。一个常见的例子是,系统会在用户滚动经过某个点后发送分析事件。为了高效地解决此问题,我们可以使用snapshotFlow():
val listState = rememberLazyListState()
LazyColumn(state = listState) {
// ...
}
LaunchedEffect(listState) {
snapshotFlow { listState.firstVisibleItemIndex }
.map { index -> index > 0 }
.distinctUntilChanged()
.filter { it == true }
.collect {
MyAnalyticsService.sendScrolledPastFirstItemEvent()
}
}
LazyListState 通过 scrollToItem() 立即滚动和 animateScrollToItem() 使用动画滚动(平滑滚动)。
@Composable
fun MessageList(messages: List) {
val listState = rememberLazyListState()
val coroutineScope = rememberCoroutineScope()
LazyColumn(state = listState) {
// ...
}
ScrollToTopButton(
onClick = {
coroutineScope.launch {
listState.animateScrollToItem(index = 0)
}
}
)
}
一般情况下函数的位置是不会发生变化的,Compose编译器能基于调用位置来区分不同的组合项,然后判断内容是否发生变化决定要不要重组。但对于列表来说,在尾部插入还好,如果是在头部或中间,后面的条目由于位置都发生了变化会全部重组,然而现实需求是只需要更新内容发生变化的条目,因此造成了无必要的刷新。
手动设置 key 为条目的唯一值(如id)后,使得列表能够精准的定位每一个条目函数, 对于内容是否变化能自动通过对象自身的 equals() 来感知,以便跳过重组。这样当数据源发生变化时列表可以高效重组,只需要处理那些发生过变化的条目对应的组合项即可,而不是全部更新。
items(
items = dataList,
key = { it.id } //it是dataBean
) { }
v1.2版本开始,当列表由多种不同类型的条目组成时,可以为条目指定类型来确保 Compose 不会尝试在属于B类型的组合项上组合A类型的条目。
LazyColumn {
items(elements, contentType = { it.type }) {...}
}