Lazy layouts in Compose recap(GoogleIO 2022)
There are LazyColumn that supports the vertical scroll, LazyRow that supports the horizontal scroll, and LazyGrid that supports the vertical/horizontal scroll in Compose 1.2.
In Compose, you can implement the same functionalities with shorter codes compared to RecyclerView. When you need adapter, view holder, xml, etc in RecyclerView, Compose needs only simple code like below.
You can construct list with items() like above, but you can do that one by one with item() like below. You can use index with itemsIndex().
Next, let’s decorate UI. You can put start/end padding with Modifier.padding() like below, but items are cropped when you scroll them.
In this case, you can use contentPadding to prevent items to be cropped.
There are no margin between items basically. You can adjust margin between items with horizontalArrangement like below.
Let’s see how to control scroll. You can access to scroll information with rememberLazyListState() like below. If you need to share it to other compositions, you can hoist and pass it to parameter.
Which information does that state have? There are index and offset of the first visible item in (1) below.
(2) is the example that shows a button according to the first visible item. Because properties of LazyListState is changed frequently, recomposition can occur frequently too. You can use derivedStateOf to make occur recomposition only when value that you want is changed.
(3) is the example that accesses to the current visible item and total item count with layoutInfo object. You can use them when you want to play only one video that is visible completely in screen.
Next is about ScrollToTopButton. You can use scrollToItem() and move to the specific position simply like (a) below. You can use animateScrollToItem() when you need animation like (b).
However you need to make coroutine scope like (c) since these functions are suspend function.
LazyGrid
LazyGrid became the stable release in Compose 1.2. It consists of LazyVerticalGrid, LazyHorizontalGrid according to the scroll direction.
LazyGrid has the same way to put the vertical/horizontal margin and items with LazyColumn and the additional column count like below.
LazyVerticalGrid also uses LazyGridState and provides the same properties and functions like LazyListState. LazyVerticalGrid has also contentPadding even though I omitted it.
However if I set the column count, grid will not look pretty in the large screen like tablet, or landscape mode. You can use GridCells.Adaptive() like below, and put as many columns as possible in the specified size.
Next is the more complicated case. Below example has two columns: the first column’s width is twice as wide as the second’s. You can implement GridCells and override calculateCrossAxisCellSizes(). You can see the code returns list after calculating widths of each item with the available size and spacing.
Next is the span, which is used in grid frequently. You can make the full width header in one row with span parameter.
However where did maxLineSpan come from? the lambda function which is passed to span provides LazyGridItemSpanScope in below left. You can know the total span count from maxLineSpan. Also maxCurrentLineSpan means how many spans the current item can occupy. In below right image, there are three columns and the second can have the maximum two spans, which means that column has maxCurrentLineSpan=2.
LazyLayout
Compose has the experimental LazyLayout, which can implement the layout manager. You can implement staggerer grid with it.
Item animations
Compose1.1 added the experimental animation in LazyList when changing the order of items. Compose1.2 supports the animation also in LazyGrid. You just need to put animateItemPlacement() like below, and the extra specification like tween().
In this case it’s very important to define key. Not every data types but data types for bundle are possible for that key(primitive type, enum, Parcelable, etc).
They say insert/remove animations is in progress.
Tip #1: Don’t use 0-pixel sized items
This is the principle for both LazyList and LazyGrid. When you want to show images asynchronously in LazyColumn, there are no images in the beginning, and every items have 0-pixel height, then LazyColumn creates every items. After loading an image, recomposition occurs, and only some images can be visible in the screen, then all the other items are removed. These are unnecessarily actions.
You need to set the default size like modifier = Modifier.size(30.dp) for this problem. In the same way the placeholder helps keep the scroll position.
Tip #2: Avoid nesting components scrollable in the same direction
You know you should not put RecyclerView inside ScrollView in the traditional view system. This the same principle for Compose. However Compose even throws an exception in that case.
You can mix the different types of items, not nesting components.
Of course there are exceptional cases.
- Available for different directions: parent Row, child LazyColumn
- Available for same directions when the child size is fixed: parent Column, child LazyColumn(modifier = Modifier.height(200.dp)
Tip #3: Beware of putting multiple elements in one item
What happens if I put multiple elements in one item like below? There are any problems in UI, but:
- Each item can not be processed separately. In above right image if item1 is visible due to scroll, item2 is created together. Extremely speaking, if You put all the elements in one item LazyLayout is useless.
- Scroll is in trouble. scrollToItem(2) makes the element3 go to the top.
However, a divider can be included in one item because it is a part of the previous item, it does not matter scroll, and it is a small size.
Tip #4: Consider using custom arrangements
This section is about the arrangement or alignment of components. Below image wants to align footer in bottom when the screen is not full of items. You need to set verticalArrangement.
You can align the last item in the bottom of the screen using the total height: totalSize, heights of each item: sizes, and processed positions: outPositions.
Performance
They say first turn on R8 to measure performance.
Optimization #1: Reusing
Composition reusing is the similar concept with RecyclerView. An item is not removed when it is not visible in the screen and it can be reused for the newly visible item later. However composition reusing is not possible for the different UI and properties so Compose1.2 added contentType to distinguish them(I thought Compose has already the feature but it has now).
Optimization #2: Prefetching
One frame needs to end in 16ms to prevent junk like below. If a new item comes in like Frame2, Compose/Measure steps are added and the frame can be over 16ms.
In this case, UI thread is idle after Render. Compose/Measure can be moved to this kind of idle time, which is Prefetching.
They didn’t say how developers implements it at all. It seems that Compose takes care of it.