diff options
| author | 2024-12-10 21:45:52 +0000 | |
|---|---|---|
| committer | 2024-12-10 21:45:52 +0000 | |
| commit | eb11444200d02d1039d12bf9c7682c2554777599 (patch) | |
| tree | 12b2f0ae4be66bb29632382385f17ba8400795b6 | |
| parent | 799131ca8af087a2a8fa52730e82024d7eff0729 (diff) | |
| parent | 545ff2868a7cc10e671887c560ad2a316c90ce74 (diff) | |
Merge "Update responsive grid layout + alpha animation" into main
2 files changed, 98 insertions, 11 deletions
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index a55b6d720dd6..15edd0b05bfc 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -151,6 +151,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntRect import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.LayoutDirection @@ -947,12 +948,28 @@ private fun BoxScope.CommunalHubLazyGrid( } } } else { + val itemAlpha = + if (communalResponsiveGrid()) { + val percentVisible by + remember(gridState, index) { + derivedStateOf { calculatePercentVisible(gridState, index) } + } + animateFloatAsState(percentVisible) + } else { + null + } + CommunalContent( model = item, viewModel = viewModel, size = size, selected = false, - modifier = Modifier.requiredSize(dpSize).animateItem(), + modifier = + Modifier.requiredSize(dpSize).animateItem().thenIf( + communalResponsiveGrid() + ) { + Modifier.graphicsLayer { alpha = itemAlpha?.value ?: 1f } + }, index = index, contentListState = contentListState, interactionHandler = interactionHandler, @@ -1856,6 +1873,44 @@ private fun CommunalContentModel.getSpanOrMax(maxSpan: Int?) = size.span } +private fun IntRect.percentOverlap(other: IntRect): Float { + val intersection = intersect(other) + if (intersection.width < 0 || intersection.height < 0) { + return 0f + } + val overlapArea = intersection.width * intersection.height + val area = width * height + return overlapArea.toFloat() / area.toFloat() +} + +private fun calculatePercentVisible(state: LazyGridState, index: Int): Float { + val viewportSize = state.layoutInfo.viewportSize + val visibleRect = + IntRect( + offset = + IntOffset( + state.layoutInfo.viewportStartOffset + state.layoutInfo.beforeContentPadding, + 0, + ), + size = + IntSize( + width = + viewportSize.width - + state.layoutInfo.beforeContentPadding - + state.layoutInfo.afterContentPadding, + height = viewportSize.height, + ), + ) + + val itemInfo = state.layoutInfo.visibleItemsInfo.find { it.index == index } + return if (itemInfo != null) { + val boundingBox = IntRect(itemInfo.offset, itemInfo.size) + boundingBox.percentOverlap(visibleRect) + } else { + 0f + } +} + private object Colors { val DisabledColorFilter by lazy { disabledColorMatrix() } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt index 21b34748a364..c7930549abe8 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt @@ -36,7 +36,9 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.toComposeRect import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.Dp @@ -45,6 +47,7 @@ import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.coerceAtMost import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.times +import androidx.window.layout.WindowMetricsCalculator /** * Renders a responsive [LazyHorizontalGrid] with dynamic columns and rows. Each cell will maintain @@ -71,7 +74,7 @@ fun ResponsiveLazyHorizontalGrid( "$minHorizontalArrangement and $minVerticalArrangement, respectively." } BoxWithConstraints(modifier) { - val gridSize = rememberGridSize(maxWidth = maxWidth, maxHeight = maxHeight) + val gridSize = rememberGridSize() val layoutDirection = LocalLayoutDirection.current val density = LocalDensity.current @@ -128,25 +131,43 @@ fun ResponsiveLazyHorizontalGrid( val extraWidth = maxWidth - usedWidth val extraHeight = maxHeight - usedHeight - val finalStartPadding = minStartPadding + extraWidth / 2 + // If there is a single column or single row, distribute extra space evenly across the grid. + // Otherwise, distribute it along the content padding to center the content. + val distributeHorizontalSpaceAlongGutters = gridSize.height == 1 || gridSize.width == 1 + val evenlyDistributedWidth = + if (distributeHorizontalSpaceAlongGutters) { + extraWidth / (gridSize.width + 1) + } else { + extraWidth / 2 + } + + val finalStartPadding = minStartPadding + evenlyDistributedWidth + val finalEndPadding = minEndPadding + evenlyDistributedWidth val finalTopPadding = minTopPadding + extraHeight / 2 val finalContentPadding = PaddingValues( start = finalStartPadding, - end = minEndPadding + extraWidth / 2, + end = finalEndPadding, top = finalTopPadding, bottom = minBottomPadding + extraHeight / 2, ) with(density) { setContentOffset(Offset(finalStartPadding.toPx(), finalTopPadding.toPx())) } + val horizontalArrangement = + if (distributeHorizontalSpaceAlongGutters) { + minHorizontalArrangement + evenlyDistributedWidth + } else { + minHorizontalArrangement + } + LazyHorizontalGrid( rows = GridCells.Fixed(gridSize.height), modifier = Modifier.fillMaxSize(), state = state, contentPadding = finalContentPadding, - horizontalArrangement = Arrangement.spacedBy(minHorizontalArrangement), + horizontalArrangement = Arrangement.spacedBy(horizontalArrangement), verticalArrangement = Arrangement.spacedBy(minVerticalArrangement), flingBehavior = flingBehavior, userScrollEnabled = userScrollEnabled, @@ -210,27 +231,38 @@ data class SizeInfo( } @Composable -private fun rememberGridSize(maxWidth: Dp, maxHeight: Dp): IntSize { +private fun rememberGridSize(): IntSize { val configuration = LocalConfiguration.current val orientation = configuration.orientation + val screenSize = calculateWindowSize() - return remember(orientation, maxWidth, maxHeight) { + return remember(orientation, screenSize) { if (orientation == Configuration.ORIENTATION_PORTRAIT) { IntSize( - width = calculateNumCellsWidth(maxWidth), - height = calculateNumCellsHeight(maxHeight), + width = calculateNumCellsWidth(screenSize.width), + height = calculateNumCellsHeight(screenSize.height), ) } else { // In landscape we invert the rows/columns to ensure we match the same area as portrait. // This keeps the number of elements in the grid consistent when changing orientation. IntSize( - width = calculateNumCellsHeight(maxWidth), - height = calculateNumCellsWidth(maxHeight), + width = calculateNumCellsHeight(screenSize.width), + height = calculateNumCellsWidth(screenSize.height), ) } } } +@Composable +fun calculateWindowSize(): DpSize { + // Observe view configuration changes and recalculate the size class on each change. + LocalConfiguration.current + val density = LocalDensity.current + val context = LocalContext.current + val metrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context) + return with(density) { metrics.bounds.toComposeRect().size.toDpSize() } +} + private fun calculateNumCellsWidth(width: Dp) = // See https://developer.android.com/develop/ui/views/layout/use-window-size-classes when { |