aboutsummaryrefslogtreecommitdiff
path: root/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/carousel/Keyline.kt
diff options
context:
space:
mode:
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.kt95
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(