diff options
Diffstat (limited to 'compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Keyline.kt')
-rw-r--r-- | compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Keyline.kt | 95 |
1 files changed, 94 insertions, 1 deletions
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Keyline.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Keyline.kt index eeda9e6375c..c4451375005 100644 --- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Keyline.kt +++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Keyline.kt @@ -16,9 +16,101 @@ package androidx.compose.material3.carousel +import androidx.compose.ui.unit.Density import androidx.compose.ui.util.fastFirstOrNull import androidx.compose.ui.util.fastMapIndexed import kotlin.math.abs +import kotlin.math.ceil +import kotlin.math.floor +import kotlin.math.max +import kotlin.math.min + +/** + * Creates a list of keylines that arranges items into a multi-browse configuration. + * + * Note that this function may adjust the size of large items. In order to ensure large, medium, + * and small items fit perfectly into the available space and are numbered/arranged in a + * visually pleasing and opinionated way, this strategy finds the nearest number of large items that + * will fit into an approved arrangement that requires the least amount of size adjustment + * necessary. + * + * For more information, see <a href="https://material.io/components/carousel/overview">design + * guidelines</a>. + * + * @param density The [Density] object that provides pixel density information of the device + * @param carouselMainAxisSize the size of the carousel container, in pixels, in the main + * scrolling axis + * @param preferredItemSize the desired size of large items, in pixels, in the main scrolling axis + * @param itemSpacing the spacing between items in pixels + * @param minSmallSize the minimum allowable size of small items in pixels + * @param maxSmallSize the maximum allowable size of small items in pixels + */ +internal fun multiBrowseKeylineList( + density: Density, + carouselMainAxisSize: Float, + preferredItemSize: Float, + itemSpacing: Float, + minSmallSize: Float = with(density) { StrategyDefaults.minSmallSize.toPx() }, + maxSmallSize: Float = with(density) { StrategyDefaults.maxSmallSize.toPx() }, +): KeylineList? { + if (carouselMainAxisSize == 0f || preferredItemSize == 0f) { + return null + } + + var smallCounts: IntArray = intArrayOf(1) + val mediumCounts: IntArray = intArrayOf(1, 0) + + val targetLargeSize: Float = min(preferredItemSize + itemSpacing, carouselMainAxisSize) + // Ideally we would like to create a balanced arrangement where a small item is 1/3 the size + // of the large item and medium items are sized between large and small items. Clamp the + // small target size within our min-max range and as close to 1/3 of the target large item + // size as possible. + val targetSmallSize: Float = (targetLargeSize / 3f + itemSpacing).coerceIn( + minSmallSize + itemSpacing, + maxSmallSize + itemSpacing + ) + val targetMediumSize = (targetLargeSize + targetSmallSize) / 2f + + if (carouselMainAxisSize < minSmallSize * 2) { + // If the available space is too small to fit a large item and small item (where a large + // item is bigger than a small item), allow arrangements with + // no small items. + smallCounts = intArrayOf(0) + } + + // Find the minimum space left for large items after filling the carousel with the most + // permissible medium and small items to determine a plausible minimum large count. + val minAvailableLargeSpace = carouselMainAxisSize - targetMediumSize * mediumCounts.max() - + maxSmallSize * smallCounts.max() + val minLargeCount = max( + 1, + floor(minAvailableLargeSpace / targetLargeSize).toInt()) + val maxLargeCount = ceil(carouselMainAxisSize / targetLargeSize).toInt() + + val largeCounts = IntArray(maxLargeCount - minLargeCount + 1) { maxLargeCount - it } + val anchorSize = with(density) { StrategyDefaults.anchorSize.toPx() } + val arrangement = Arrangement.findLowestCostArrangement( + availableSpace = carouselMainAxisSize, + targetSmallSize = targetSmallSize, + minSmallSize = minSmallSize, + maxSmallSize = maxSmallSize, + smallCounts = smallCounts, + targetMediumSize = targetMediumSize, + mediumCounts = mediumCounts, + targetLargeSize = targetLargeSize, + largeCounts = largeCounts, + ) ?: return null + + return keylineListOf(carouselMainAxisSize, CarouselAlignment.Start) { + add(anchorSize, isAnchor = true) + + repeat(arrangement.largeCount) { add(arrangement.largeSize) } + repeat(arrangement.mediumCount) { add(arrangement.mediumSize) } + repeat(arrangement.smallCount) { add(arrangement.smallSize) } + + add(anchorSize, isAnchor = true) + } +} /** * A structure that is fixed at a specific [offset] along a scrolling axis and @@ -294,11 +386,12 @@ private class KeylineListScopeImpl : KeylineListScope { pivotIndex = firstFocalIndex pivotOffset = when (carouselAlignment) { - CarouselAlignment.Start -> focalItemSize / 2 CarouselAlignment.Center -> { (carouselMainAxisSize / 2) - ((focalItemSize / 2) * focalItemCount) } CarouselAlignment.End -> carouselMainAxisSize - (focalItemSize / 2) + // Else covers and defaults to CarouselAlignment.Start + else -> focalItemSize / 2 } val keylines = createKeylinesWithPivot( |