android 获取jetpack组合LazyColumn中的最后一个可见项目索引

wixjitnu  于 2022-12-16  发布在  Android
关注(0)|答案(7)|浏览(186)

我想检查列表是否滚动到列表的末尾,但是lazyListState没有提供这个属性
为什么我需要这个?我想显示一个FAB的“滚动到年底”的列表,并隐藏它,如果最后一项已经可见
(Note:是的,但它是internal

/**
   * Non-observable way of getting the last visible item index.
   */
  internal var lastVisibleItemIndexNonObservable: DataIndex = DataIndex(0)

不知道为什么)

val state = rememberLazyListState()
LazyColumn(
    state = state,
    modifier = modifier.fillMaxSize()
) {
    // if(state.lastVisibleItem == logs.length - 1) ...
    items(logs) { log ->
        if (log.level in viewModel.getShownLogs()) {
            LogItemScreen(log = log)
        }
    }
}

那么,如何检查LazyColumn是否滚动到数据集的末尾?

suzh9iv8

suzh9iv81#

下面是您实现它的方法:

检查是否滚动到末尾的扩展功能:

fun LazyListState.isScrolledToTheEnd() = layoutInfo.visibleItemsInfo.lastOrNull()?.index == layoutInfo.totalItemsCount - 1

示例用法:

val listState = rememberLazyListState()
val listItems = (0..25).map { "Item$it" }

LazyColumn(state = listState, modifier = Modifier.fillMaxSize()) {
    items(listItems) { item ->
        Text(text = item, modifier = Modifier.padding(16.dp))
    }
}

Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.BottomEnd) {
    if (!listState.isScrolledToTheEnd()) {
        ExtendedFloatingActionButton(
            modifier = Modifier.padding(16.dp),
            text = { Text(text = "Go to Bottom") },
            onClick = { /* Scroll to the end */}
        )
    }
}

结果:
https://im.ezgif.com/tmp/ezgif-1-b725f96cedd2.gif

ogq8wdun

ogq8wdun2#

我分享我的解决方案,以防它帮助任何人。
它提供了实现问题用例所需的信息,还通过遵循www.example.com的建议避免了无限的重新组合https://developer.android.com/jetpack/compose/lists#control-scroll-position。
1.创建这些扩展函数来计算列表状态所需的信息:

val LazyListState.isLastItemVisible: Boolean
        get() = layoutInfo.visibleItemsInfo.lastOrNull()?.index == layoutInfo.totalItemsCount - 1
    
val LazyListState.isFirstItemVisible: Boolean
        get() = firstVisibleItemIndex == 0

1.创建一个简单的数据类来保存要收集的信息:

data class ScrollContext(
    val isTop: Boolean,
    val isBottom: Boolean,
)

1.创建此记忆组合返回上一个数据类。

@Composable
fun rememberScrollContext(listState: LazyListState): ScrollContext {
    val scrollContext by remember {
        derivedStateOf {
            ScrollContext(
                isTop = listState.isFirstItemVisible,
                isBottom = listState.isLastItemVisible
            )
        }
    }
    return scrollContext
}

注意,派生状态用于避免重组和提高性能。函数需要列表状态来在派生状态中进行计算。请阅读我上面分享的链接。
1.将所有内容粘贴到您的可组合:

@Composable
fun CharactersList(
    state: CharactersState,
    loadNewPage: (offset: Int) -> Unit
) {
    // Important to remember the state, we need it
    val listState = rememberLazyListState()
    Box {
        LazyColumn(
            state = listState,
        ) {
            items(state.characters) { item ->
                CharacterItem(item)
            }
        }

        // We use our remember composable to get the scroll context
        val scrollContext = rememberScrollContext(listState)

        // We can do what we need, such as loading more items...
        if (scrollContext.isBottom) {
            loadNewPage(state.characters.size)
        }

        // ...or showing other elements like a text
        AnimatedVisibility(scrollContext.isBottom) {
            Text("You are in the bottom of the list")
        }

        // ...or a button to scroll up
        AnimatedVisibility(!scrollContext.isTop) {
            val coroutineScope = rememberCoroutineScope()
            Button(
                onClick = {
                    coroutineScope.launch {
                        // Animate scroll to the first item
                        listState.animateScrollToItem(index = 0)
                    }
                },
            ) {
                Icon(Icons.Rounded.ArrowUpward, contentDescription = "Go to top")
            }
        }
    }
}

干杯!

tzxcd3kk

tzxcd3kk3#

现在已经太晚了,但也许这对其他人会有帮助。看到上面的答案,layoutInfo.visibleItemsInfo.lastIndex会导致重组很多次,因为它是由状态组成的。所以我建议在item(key = "lastIndexKey")中对derivedStateitemKey使用下面的语句。

val isFirstItemFullyVisible = remember {
        derivedStateOf {
            listState.firstVisibleItemIndex == 0 && listState.firstVisibleItemScrollOffset == 0
        }
    }

    val isLastItemFullyVisible by remember {
        derivedStateOf {
            listState.layoutInfo
                .visibleItemsInfo
                .any { it.key == lastIndexKey }.let { _isLastIndexVisible ->
                    if(_isLastIndexVisible){
                        val layoutInfo = listState.layoutInfo
                        val lastItemInfo = layoutInfo.visibleItemsInfo.lastOrNull() ?: return@let false

                        return@let lastItemInfo.size+lastItemInfo.offset == layoutInfo.viewportEndOffset
                    }else{
                        return@let false
                    }
                }
        }
    }

    if (isFirstItemFullyVisible.value || isLastItemFullyVisible) {
         // TODO
    }
juzqafwq

juzqafwq4#

1.4.0-alpha03开始,您可以使用**LazyListState#canScrollForward**检查是否可以向前滚动或是否位于列表末尾。

val state = rememberLazyListState()
   if  (state.canScrollForward){ /* ... */  }

之前,您可以使用包含有关可见项目信息的**LazyListState#layoutInfo。如果列表在底部滚动,您可以使用它检索信息。
由于您正在阅读state,因此应该使用
derivedStateOf**以避免冗余的重新组合。
比如:

val state = rememberLazyListState()

val isAtBottom by remember {
    derivedStateOf {
        val layoutInfo = state.layoutInfo
        val visibleItemsInfo = layoutInfo.visibleItemsInfo
        if (layoutInfo.totalItemsCount == 0) {
            false
        } else {
            val lastVisibleItem = visibleItemsInfo.last()
            val viewportHeight = layoutInfo.viewportEndOffset + layoutInfo.viewportStartOffset

            (lastVisibleItem.index + 1 == layoutInfo.totalItemsCount &&
                    lastVisibleItem.offset + lastVisibleItem.size <= viewportHeight)
        }
    }
}

kkih6yb8

kkih6yb85#

我找到的当前解决方案是:

LazyColumn(
    state = state,
    modifier = modifier.fillMaxSize()
) {
    if ((logs.size - 1) - state.firstVisibleItemIndex == state.layoutInfo.visibleItemsInfo.size - 1) {
        println("Last visible item is actually the last item")
        // do something
    }
    items(logs) { log ->
        if (log.level in viewModel.getShownLogs()) {
            LogItemScreen(log = log)
        }
    }
}

该声明
lastDataIndex - state.firstVisibleItemIndex == state.layoutInfo.visibleItemsInfo.size - 1
通过从第一个可见项中减去数据集的最后一个索引并检查它是否等于 * 可见项计数 * 来猜测最后一个项

wqnecbli

wqnecbli6#

只是想建立在一些其他的答案张贴在这里。
@Tuan Chau在评论中提到,这将导致无限的合成,这里是我尝试使用他的想法来避免这一点,它似乎工作正常。开放的想法如何使它更好!

@Composable
fun InfiniteLoadingList(
    modifier: Modifier,
    items: List<Any>,
    loadMore: () -> Unit,
    rowContent:  @Composable (Int, Any) -> Unit
) {
    val listState = rememberLazyListState()
    val firstVisibleIndex = remember { mutableStateOf(listState.firstVisibleItemIndex) }
    LazyColumn(state = listState, modifier = modifier) {
        itemsIndexed(items) { index, item ->
            rowContent(index, item)
        }
    }
    if (listState.shouldLoadMore(firstVisibleIndex)) {
        loadMore()
    }
}

扩展功能:

fun LazyListState.shouldLoadMore(rememberedIndex: MutableState<Int>): Boolean {
  val firstVisibleIndex = this.firstVisibleItemIndex
  if (rememberedIndex.value != firstVisibleIndex) {
      rememberedIndex.value = firstVisibleIndex
      return layoutInfo.visibleItemsInfo.lastOrNull()?.index == layoutInfo.totalItemsCount - 1
  }
  return false
}

用法:

InfiniteLoadingList(
        modifier = modifier,
        items = listOfYourModel,
        loadMore = { viewModel.populateMoreItems() },
    ) { index, item ->
    val item = item as YourModel
    // decorate your row
}
x33g5p2x

x33g5p2x7#

试试这个:

val lazyColumnState = rememberLazyListState()
val lastVisibleItemIndex = state.layoutInfo.visibleItemsInfo.lastIndex + state.firstVisibleItemIndex

相关问题