Use lazy vertical staggered grid in feed to maximize space utilization

Change-Id: I66fff57bd0f11f5f73e7e5c9b5c51b0fa6a76926
pull/947/head
dahunsi 1 year ago
parent e40fb7925d
commit 03d2455f41

@ -21,6 +21,8 @@ import androidx.compose.foundation.lazy.LazyListItemInfo
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.grid.LazyGridItemInfo
import androidx.compose.foundation.lazy.grid.LazyGridState
import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridItemInfo
import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState
import androidx.compose.runtime.Composable
/**
@ -102,3 +104,40 @@ fun LazyGridState.scrollbarState(
},
reverseLayout = { layoutInfo.reverseLayout },
)
/**
* Remembers a [ScrollbarState] driven by the changes in a [LazyStaggeredGridState]
*
* @param itemsAvailable the total amount of items available to scroll in the staggered grid.
* @param itemIndex a lookup function for index of an item in the staggered grid relative
* to [itemsAvailable].
*/
@Composable
fun LazyStaggeredGridState.scrollbarState(
itemsAvailable: Int,
itemIndex: (LazyStaggeredGridItemInfo) -> Int = LazyStaggeredGridItemInfo::index,
): ScrollbarState =
scrollbarState(
itemsAvailable = itemsAvailable,
visibleItems = { layoutInfo.visibleItemsInfo },
firstVisibleItemIndex = { visibleItems ->
interpolateFirstItemIndex(
visibleItems = visibleItems,
itemSize = { layoutInfo.orientation.valueOf(it.size) },
offset = { layoutInfo.orientation.valueOf(it.offset) },
nextItemOnMainAxis = { first ->
visibleItems.find { it != first && it.lane == first.lane }
},
itemIndex = itemIndex,
)
},
itemPercentVisible = itemPercentVisible@{ itemInfo ->
itemVisibilityPercentage(
itemSize = layoutInfo.orientation.valueOf(itemInfo.size),
itemStartOffset = layoutInfo.orientation.valueOf(itemInfo.offset),
viewportStartOffset = layoutInfo.viewportStartOffset,
viewportEndOffset = layoutInfo.viewportEndOffset,
)
},
reverseLayout = { false },
)

@ -18,6 +18,7 @@ package com.google.samples.apps.nowinandroid.core.designsystem.component.scrollb
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.grid.LazyGridState
import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@ -50,6 +51,19 @@ fun LazyGridState.rememberDraggableScroller(
scroll = ::scrollToItem,
)
/**
* Remembers a function to react to [Scrollbar] thumb position displacements for a
* [LazyStaggeredGridState]
* @param itemsAvailable the amount of items in the staggered grid.
*/
@Composable
fun LazyStaggeredGridState.rememberDraggableScroller(
itemsAvailable: Int,
): (Float) -> Unit = rememberDraggableScroller(
itemsAvailable = itemsAvailable,
scroll = ::scrollToItem,
)
/**
* Generic function to react to [Scrollbar] thumb displacements in a lazy layout.
* @param itemsAvailable the total amount of items available to scroll in the layout.

@ -23,10 +23,10 @@ import androidx.browser.customtabs.CustomTabColorSchemeParams
import androidx.browser.customtabs.CustomTabsIntent
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyGridScope
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope
import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
import androidx.compose.foundation.lazy.staggeredgrid.items
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@ -47,7 +47,7 @@ import com.google.samples.apps.nowinandroid.core.model.data.UserNewsResource
* An extension on [LazyListScope] defining a feed with news resources.
* Depending on the [feedState], this might emit no items.
*/
fun LazyGridScope.newsFeed(
fun LazyStaggeredGridScope.newsFeed(
feedState: NewsFeedUiState,
onNewsResourcesCheckedChanged: (String, Boolean) -> Unit,
onNewsResourceViewed: (String) -> Unit,
@ -129,7 +129,7 @@ sealed interface NewsFeedUiState {
@Composable
private fun NewsFeedLoadingPreview() {
NiaTheme {
LazyVerticalGrid(columns = GridCells.Adaptive(300.dp)) {
LazyVerticalStaggeredGrid(columns = StaggeredGridCells.Adaptive(300.dp)) {
newsFeed(
feedState = NewsFeedUiState.Loading,
onNewsResourcesCheckedChanged = { _, _ -> },
@ -148,7 +148,7 @@ private fun NewsFeedContentPreview(
userNewsResources: List<UserNewsResource>,
) {
NiaTheme {
LazyVerticalGrid(columns = GridCells.Adaptive(300.dp)) {
LazyVerticalStaggeredGrid(columns = StaggeredGridCells.Adaptive(300.dp)) {
newsFeed(
feedState = NewsFeedUiState.Success(userNewsResources),
onNewsResourcesCheckedChanged = { _, _ -> },

@ -35,10 +35,10 @@ import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.windowInsetsBottomHeight
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.lazy.grid.GridCells.Adaptive
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan
import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@ -175,17 +175,17 @@ private fun BookmarksGrid(
onTopicClick: (String) -> Unit,
modifier: Modifier = Modifier,
) {
val scrollableState = rememberLazyGridState()
val scrollableState = rememberLazyStaggeredGridState()
TrackScrollJank(scrollableState = scrollableState, stateName = "bookmarks:grid")
Box(
modifier = modifier
.fillMaxSize(),
) {
LazyVerticalGrid(
columns = Adaptive(300.dp),
LazyVerticalStaggeredGrid(
columns = StaggeredGridCells.Adaptive(300.dp),
contentPadding = PaddingValues(16.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalArrangement = Arrangement.spacedBy(24.dp),
verticalItemSpacing = 24.dp,
state = scrollableState,
modifier = Modifier
.fillMaxSize()
@ -197,7 +197,7 @@ private fun BookmarksGrid(
onNewsResourceViewed = onNewsResourceViewed,
onTopicClick = onTopicClick,
)
item(span = { GridItemSpan(maxLineSpan) }) {
item(span = StaggeredGridItemSpan.FullLine) {
Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.safeDrawing))
}
}

@ -49,13 +49,14 @@ import androidx.compose.foundation.layout.windowInsetsBottomHeight
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridCells.Adaptive
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyGridScope
import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope
import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan
import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
@ -153,7 +154,7 @@ internal fun ForYouScreen(
val itemsAvailable = feedItemsSize(feedState, onboardingUiState)
val state = rememberLazyGridState()
val state = rememberLazyStaggeredGridState()
val scrollbarState = state.scrollbarState(
itemsAvailable = itemsAvailable,
)
@ -163,11 +164,11 @@ internal fun ForYouScreen(
modifier = modifier
.fillMaxSize(),
) {
LazyVerticalGrid(
columns = Adaptive(300.dp),
LazyVerticalStaggeredGrid(
columns = StaggeredGridCells.Adaptive(300.dp),
contentPadding = PaddingValues(16.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalArrangement = Arrangement.spacedBy(24.dp),
verticalItemSpacing = 24.dp,
modifier = Modifier
.testTag("forYou:feed"),
state = state,
@ -197,7 +198,7 @@ internal fun ForYouScreen(
onTopicClick = onTopicClick,
)
item(span = { GridItemSpan(maxLineSpan) }, contentType = "bottomSpacing") {
item(span = StaggeredGridItemSpan.FullLine, contentType = "bottomSpacing") {
Column {
Spacer(modifier = Modifier.height(8.dp))
// Add space for the content to clear the "offline" snackbar.
@ -255,7 +256,7 @@ internal fun ForYouScreen(
* Depending on the [onboardingUiState], this might emit no items.
*
*/
private fun LazyGridScope.onboarding(
private fun LazyStaggeredGridScope.onboarding(
onboardingUiState: OnboardingUiState,
onTopicCheckedChanged: (String, Boolean) -> Unit,
saveFollowedTopics: () -> Unit,
@ -268,7 +269,7 @@ private fun LazyGridScope.onboarding(
-> Unit
is OnboardingUiState.Shown -> {
item(span = { GridItemSpan(maxLineSpan) }, contentType = "onboarding") {
item(span = StaggeredGridItemSpan.FullLine, contentType = "onboarding") {
Column(modifier = interestsItemModifier) {
Text(
text = stringResource(R.string.onboarding_guidance_title),

@ -35,11 +35,11 @@ import androidx.compose.foundation.layout.windowInsetsBottomHeight
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.layout.windowInsetsTopHeight
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.grid.GridCells.Adaptive
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan
import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.ClickableText
import androidx.compose.foundation.text.KeyboardActions
@ -297,16 +297,16 @@ private fun SearchResultBody(
onTopicClick: (String) -> Unit,
searchQuery: String = "",
) {
val state = rememberLazyGridState()
val state = rememberLazyStaggeredGridState()
Box(
modifier = Modifier
.fillMaxSize(),
) {
LazyVerticalGrid(
columns = Adaptive(300.dp),
LazyVerticalStaggeredGrid(
columns = StaggeredGridCells.Adaptive(300.dp),
contentPadding = PaddingValues(16.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalArrangement = Arrangement.spacedBy(24.dp),
verticalItemSpacing = 24.dp,
modifier = Modifier
.fillMaxSize()
.testTag("search:newsResources"),
@ -314,9 +314,7 @@ private fun SearchResultBody(
) {
if (topics.isNotEmpty()) {
item(
span = {
GridItemSpan(maxLineSpan)
},
span = StaggeredGridItemSpan.FullLine,
) {
Text(
text = buildAnnotatedString {
@ -331,9 +329,7 @@ private fun SearchResultBody(
val topicId = followableTopic.topic.id
item(
key = "topic-$topicId", // Append a prefix to distinguish a key for news resources
span = {
GridItemSpan(maxLineSpan)
},
span = StaggeredGridItemSpan.FullLine,
) {
InterestsItem(
name = followableTopic.topic.name,
@ -353,9 +349,7 @@ private fun SearchResultBody(
if (newsResources.isNotEmpty()) {
item(
span = {
GridItemSpan(maxLineSpan)
},
span = StaggeredGridItemSpan.FullLine,
) {
Text(
text = buildAnnotatedString {

@ -5,7 +5,7 @@ androidGradlePlugin = "8.1.1"
androidxActivity = "1.8.0-alpha06"
androidxAppCompat = "1.5.1"
androidxBrowser = "1.4.0"
androidxComposeBom = "2023.06.01"
androidxComposeBom = "2023.09.00"
androidxComposeCompiler = "1.5.0"
androidxComposeRuntimeTracing = "1.0.0-alpha03"
androidxCore = "1.9.0"

Loading…
Cancel
Save