译文----- JetpackCompose List列表(下)

Reacting to scroll position

Many apps need to react and listen to scroll position and item layout changes. The lazy components support this use-case by hoisting the LazyListState:

很多app需要根据item的滑动做出相应的变化. 可以通过懒加载组件(LazyColumnLazyRow)的LazyListState来实现该功能

 1 @Composable
 2 fun MessageList(messages: List<Message>) {
 3     // Remember our own LazyListState
 4     val listState = rememberLazyListState()
 5 
 6     // Provide it to LazyColumn
 7     LazyColumn(state = listState) {
 8         // ...
 9     }
10 }

 

For simple use-cases, apps commonly only need to know information about the first visible item. For this LazyListState provides the firstVisibleItemIndex and firstVisibleItemScrollOffset properties.

常见的应用场景, app仅仅需要知道关于第一个可见的item的信息. 这些信息可以通过LazyListStatefirstVisibleItemIndexfirstVisibleItemScrollOffset获取到

If we use the example of a showing and hiding a button based on if the user has scrolled past the first item:

以下示例, 展示了如何一个button根据用户的滑动操作来动态的显示和隐藏的效果实现

 1 @OptIn(ExperimentalAnimationApi::class) // AnimatedVisibility 注意这里, 动画效果的api为experimental, 将来api有可能修改甚至删除(慎用)
 2 @Composable
 3 fun MessageList(messages: List<Message>) {
 4     Box {
 5         val listState = rememberLazyListState()
 6 
 7         LazyColumn(state = listState) {
 8             // ...
 9         }
10 
11         // Show the button if the first visible item is past
12         // the first item. We use a remembered derived state to
13         // minimize unnecessary compositions
14         val showButton by remember {
15             derivedStateOf {
16                 listState.firstVisibleItemIndex > 0
17             }
18         }
19 
20         AnimatedVisibility(visible = showButton) {
21             ScrollToTopButton()
22         }
23     }
24 }

 

Note: The example above uses derivedStateOf() to minimize unnecessary compositions. For more information, see the Side Effects documentation.

上面的例子通过 derivedStateOf() 来优化计算量, 更多信息请阅读 副作用文档

(百度翻译为-->注意:上面的例子使用derivedStateOf()来最小化不必要的合成。有关更多信息,请参阅副作用文档, 读着不顺, 看了下derivedStateOf()的文档大概可理解为(还没看源码实现,所以可能理解不准确), 这个函数会保存一个状态值在缓存中, 当状态不改变时, 不会导致重复的计算状态值)

Reading the state directly in composition is useful when you need to update other UI composables, but there are also scenarios where the event does not need to be handled in the same composition. A common example of this is sending an analytics event once the user has scrolled past a certain point. To handle this efficiently, we can use a snapshotFlow():

当你想要更新UI组件时, 直接在组件中读取状态会更有效(useful原意有用的, 但感觉翻译称"有效"会更好). 但也有一些不需要在相同的组件中处理事件, 一个常见的例子是: 一旦用户滚动过某个点, 就发送一个事件. 为了有效地处理这个问题,我们可以使用snapshotFlow()

 1 val listState = rememberLazyListState()
 2 
 3 LazyColumn(state = listState) {
 4     // ...
 5 }
 6 
 7 LaunchedEffect(listState) {
 8     snapshotFlow { listState.firstVisibleItemIndex }
 9         .map { index -> index > 0 }
10         .distinctUntilChanged()
11         .filter { it == true }
12         .collect {
13             MyAnalyticsService.sendScrolledPastFirstItemEvent()
14         }
15 }

 

LazyListState also provides information about all of the items currently being displayed and their bounds on screen, via the layoutInfo property. See the LazyListLayoutInfo class for more information. LazyListState还通过layoutInfo属性提供有关当前显示的所有item及其在屏幕上的界限的信息. 有关详细信息,请阅读LazyListLayoutInfo类.

Controlling the scroll position

As well as reacting to scroll position, it’s also useful for apps to be able to control the scroll position too. LazyListState supports this via the scrollToItem() function, which ‘immediately’ snaps the scroll position, and animateScrollToItem() which scrolls using an animation (also known as a smooth scroll):

对于一个app来说, 控制滚动位置和获取滑动事件一样的有用. 可以通过LazyListStatescrollToItem()函数来控制item立即滚动到某个索引. animateScrollToItem()则会通过动画效果来平滑过渡

Note: Both scrollToItem() and animateScrollToItem() are suspending functions, which means that we need to invoke them in a coroutine. See our coroutines documentation for more information on how to do that in Compose.

注意:scrollToItem() 和 animateScrollToItem() 都是挂起函数,这意味着我们需要在协同例程中调用它们。有关如何在Compose中实现这一点的更多信息,请参阅我们的coroutines文档。

 1 @Composable
 2 fun MessageList(messages: List<Message>) {
 3     val listState = rememberLazyListState()
 4     // Remember a CoroutineScope to be able to launch
 5     val coroutineScope = rememberCoroutineScope()
 6 
 7     LazyColumn(state = listState) {
 8         // ...
 9     }
10 
11     ScrollToTopButton(
12         onClick = {
13             coroutineScope.launch {
14                 // Animate scroll to the first item
15                 listState.animateScrollToItem(index = 0)
16             }
17         }
18     )
19 }

 

Large data-sets (paging)

The Paging library enables apps to support large lists of items, loading and displaying small chunks of the list as necessary. Paging 3.0 and later provides Compose support through the androidx.paging:paging-compose library

Paging library组件能够支持使用小量的开销, 来加载和展示大量数据list列表, Compose需要使用Paging3.0 及更高版本的androidx.paging:paging-compose

Note: Compose support is provided only for Paging 3.0 and later. If you're using an earlier version of the Paging library, you need to migrate to 3.0 first.

注意: Compose需要使用Paging3.0或更高版本, 如果你使用的Paging库的版本比较低(version < 3.0 )则需要先迁移到3.0(或以上)

To display a list of paged content, we can use the collectAsLazyPagingItems() extension function, and then pass in the returned LazyPagingItems to items() in our LazyColumn. Similar to Paging support in views, you can display placeholders while data loads by checking if the item is null:

要分页显示内容列表, 可以使用collectAsLazyPagingItems()函数,然后将返回的LazyPagingItems传入LazyColumn中的items(). 与View(应该指的是原生view组件) 中的分页类似,通过检查项是否为空,可以在加载数据时显示placeholders(占位符):

 1 import androidx.paging.compose.collectAsLazyPagingItems
 2 import androidx.paging.compose.items
 3 
 4 @Composable
 5 fun MessageList(pager: Pager<Int, Message>) {
 6     val lazyPagingItems = pager.flow.collectAsLazyPagingItems()
 7 
 8     LazyColumn {
 9         items(lazyPagingItems) { message ->
10             if (message != null) {
11                 MessageRow(message)
12             } else {
13                 MessagePlaceholder()
14             }
15         }
16     }
17 }

 

警告:如果使用RemoteMediator从网络服务获取数据,请确保提供实际大小的placeholder(占位符)。如果您使用RemoteMediator, 它将被反复调用以获取新数据, 直到屏幕上充满内容为止。如果提供了小的placeholder(占位符)(或者根本没有占位符), 屏幕可能永远不会被填满, 应用程序将获取许多页的数据.

Item keys

By default, each item's state is keyed against the position of the item in the list. However, this can cause issues if the data set changes, since items which change position effectively lose any remembered state. If you imagine the scenario of LazyRow within a LazyColumn, if the row changes item position, the user would then lose their scroll position within the row.

默认情况下, 每个item的状态都是根据item在list中的位置设置的. 然而, 如果数据集发生了改变就会导致一系列问题, 因为item 会因为更改位置而丢失所记录的状态. 设想一下LazyRowLazyColumn中使用的场景, 如果row改变了item的位置, 那么用户将丢失其在行中的滚动位置(这部分没完全理解, 所以暂时用的百度翻译)

(个人注释: 关于这一段我是这么理解的, 比如说一个纵向滑动(LazyColumn)嵌套了一个横向滑动(LazyRow), 然后横向滑动到了任意一位置上, 假设为第三个, 此时通过remember记录相应状态, 那么如果此时再进行纵向滑动, 那么就会失去横向滑动的索引坐标, ,不过还没测试是否正确)

Note: For more information on how Compose remembers state, see the State documentation

注意:有关Compose如何记住状态的更多信息,请参阅State文档

To combat this, you can provide a stable and unique key for each item, providing a block to the key parameter. Providing a stable key enables item state to be consistent across data-set changes:

如果要解决上述问题, 你可以为每个item绑定一个唯一的key, 提供一个稳定的key可以使状态在数据集中更改中保持一直

@Composable
fun MessageList(messages: List<Message>) {
    LazyColumn {
        items(
            items = messages,
            key = { message ->
                // Return a stable + unique key for the item
                message.id
            }
        ) { message ->
            MessageRow(message)
        }
    }
}

 

Note: Any key provided must be able to be stored within a Bundle. See that class for information on what types can be stored.

注意: 任何一个被设置的key必须能够存储到Bundle中. 有关可以存储哪些类型的信息,请参见该类.(Android中Bundle能存的也就是实现序列化(SerializableParcelable)的类和基本数据类型)

posted @ 2021-03-08 00:52  予有荣焉  阅读(748)  评论(0编辑  收藏  举报