Merge "Add segmentedCircularProgressIndicator" into androidx-main
diff --git a/wear/protolayout/protolayout-material3/api/current.txt b/wear/protolayout/protolayout-material3/api/current.txt
index 6d598d2..b45c9c7 100644
--- a/wear/protolayout/protolayout-material3/api/current.txt
+++ b/wear/protolayout/protolayout-material3/api/current.txt
@@ -116,6 +116,7 @@
public final class CircularProgressIndicatorKt {
method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement circularProgressIndicator(androidx.wear.protolayout.material3.MaterialScope, optional float staticProgress, optional androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat? dynamicProgress, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional float startAngleDegrees, optional float endAngleDegrees, optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float strokeWidth, optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float gapSize, optional androidx.wear.protolayout.material3.ProgressIndicatorColors colors, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension size);
+ method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement segmentedCircularProgressIndicator(androidx.wear.protolayout.material3.MaterialScope, @IntRange(from=1L) int segmentCount, optional float staticProgress, optional androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat? dynamicProgress, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional float startAngleDegrees, optional float endAngleDegrees, optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float strokeWidth, optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float gapSize, optional androidx.wear.protolayout.material3.ProgressIndicatorColors colors, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension size);
}
public final class ColorScheme {
diff --git a/wear/protolayout/protolayout-material3/api/restricted_current.txt b/wear/protolayout/protolayout-material3/api/restricted_current.txt
index 6d598d2..b45c9c7 100644
--- a/wear/protolayout/protolayout-material3/api/restricted_current.txt
+++ b/wear/protolayout/protolayout-material3/api/restricted_current.txt
@@ -116,6 +116,7 @@
public final class CircularProgressIndicatorKt {
method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement circularProgressIndicator(androidx.wear.protolayout.material3.MaterialScope, optional float staticProgress, optional androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat? dynamicProgress, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional float startAngleDegrees, optional float endAngleDegrees, optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float strokeWidth, optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float gapSize, optional androidx.wear.protolayout.material3.ProgressIndicatorColors colors, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension size);
+ method public static androidx.wear.protolayout.LayoutElementBuilders.LayoutElement segmentedCircularProgressIndicator(androidx.wear.protolayout.material3.MaterialScope, @IntRange(from=1L) int segmentCount, optional float staticProgress, optional androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat? dynamicProgress, optional androidx.wear.protolayout.modifiers.LayoutModifier modifier, optional float startAngleDegrees, optional float endAngleDegrees, optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float strokeWidth, optional @Dimension(unit=androidx.annotation.Dimension.Companion.DP) float gapSize, optional androidx.wear.protolayout.material3.ProgressIndicatorColors colors, optional androidx.wear.protolayout.DimensionBuilders.ContainerDimension size);
}
public final class ColorScheme {
diff --git a/wear/protolayout/protolayout-material3/samples/src/main/java/androidx/wear/protolayout/material3/samples/Material3ComponentsSample.kt b/wear/protolayout/protolayout-material3/samples/src/main/java/androidx/wear/protolayout/material3/samples/Material3ComponentsSample.kt
index 86b46af..e5d1f6a 100644
--- a/wear/protolayout/protolayout-material3/samples/src/main/java/androidx/wear/protolayout/material3/samples/Material3ComponentsSample.kt
+++ b/wear/protolayout/protolayout-material3/samples/src/main/java/androidx/wear/protolayout/material3/samples/Material3ComponentsSample.kt
@@ -33,6 +33,7 @@
import androidx.wear.protolayout.material3.CardDefaults.filledTonalCardColors
import androidx.wear.protolayout.material3.CardDefaults.filledVariantCardColors
import androidx.wear.protolayout.material3.CircularProgressIndicatorDefaults
+import androidx.wear.protolayout.material3.CircularProgressIndicatorDefaults.filledTonalProgressIndicatorColors
import androidx.wear.protolayout.material3.CircularProgressIndicatorDefaults.filledVariantProgressIndicatorColors
import androidx.wear.protolayout.material3.DataCardStyle.Companion.extraLargeDataCardStyle
import androidx.wear.protolayout.material3.DataCardStyle.Companion.largeCompactDataCardStyle
@@ -54,6 +55,7 @@
import androidx.wear.protolayout.material3.imageButton
import androidx.wear.protolayout.material3.materialScope
import androidx.wear.protolayout.material3.primaryLayout
+import androidx.wear.protolayout.material3.segmentedCircularProgressIndicator
import androidx.wear.protolayout.material3.text
import androidx.wear.protolayout.material3.textButton
import androidx.wear.protolayout.material3.textDataCard
@@ -307,8 +309,13 @@
style = largeGraphicDataCardStyle(),
title = { text("1,234".layoutString) },
content = { icon("steps") },
- // TODO: b/368272767 - Use CPI here
- graphic = { text("Run".layoutString) },
+ graphic = {
+ segmentedCircularProgressIndicator(
+ segmentCount = 5,
+ staticProgress = 0.5F,
+ colors = filledTonalProgressIndicatorColors(),
+ )
+ },
)
}
)
@@ -475,3 +482,24 @@
}
)
}
+
+@Sampled
+fun multipleSegmentsCircularProgressIndicator(
+ context: Context,
+ deviceParameters: DeviceParameters,
+): LayoutElement =
+ materialScope(context, deviceParameters) {
+ segmentedCircularProgressIndicator(
+ segmentCount = 5,
+ dynamicProgress =
+ DynamicFloat.animate(
+ 0.0F,
+ 1.1F,
+ CircularProgressIndicatorDefaults.recommendedAnimationSpec
+ ),
+ startAngleDegrees = 200F,
+ endAngleDegrees = 520F,
+ colors = filledVariantProgressIndicatorColors(),
+ size = dp(85F)
+ )
+ }
diff --git a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/CircularProgressIndicator.kt b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/CircularProgressIndicator.kt
index 4a80fc3..c8a3022 100644
--- a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/CircularProgressIndicator.kt
+++ b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/CircularProgressIndicator.kt
@@ -18,6 +18,7 @@
import androidx.annotation.Dimension
import androidx.annotation.Dimension.Companion.DP
+import androidx.annotation.IntRange
import androidx.wear.protolayout.ColorBuilders.ColorProp
import androidx.wear.protolayout.DimensionBuilders.AngularLayoutConstraint
import androidx.wear.protolayout.DimensionBuilders.ContainerDimension
@@ -34,20 +35,20 @@
import androidx.wear.protolayout.LayoutElementBuilders.DashedArcLine
import androidx.wear.protolayout.LayoutElementBuilders.DashedLinePattern
import androidx.wear.protolayout.LayoutElementBuilders.LayoutElement
-import androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationParameters
-import androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec
-import androidx.wear.protolayout.expression.AnimationParameterBuilders.Easing
+import androidx.wear.protolayout.ModifiersBuilders.Modifiers
import androidx.wear.protolayout.expression.DynamicBuilders.DynamicColor
import androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat
+import androidx.wear.protolayout.material3.CircularProgressIndicatorDefaults.INDICATOR_STROKE_WIDTH_INCREMENT_PX
import androidx.wear.protolayout.material3.CircularProgressIndicatorDefaults.LARGE_STROKE_WIDTH
import androidx.wear.protolayout.material3.CircularProgressIndicatorDefaults.METADATA_TAG
+import androidx.wear.protolayout.material3.CircularProgressIndicatorDefaults.TRIVIAL_ARC_OFFSET
import androidx.wear.protolayout.material3.CircularProgressIndicatorDefaults.calculateRecommendedGapSize
import androidx.wear.protolayout.material3.CircularProgressIndicatorDefaults.filledProgressIndicatorColors
import androidx.wear.protolayout.modifiers.LayoutModifier
import androidx.wear.protolayout.modifiers.contentDescription
+import androidx.wear.protolayout.modifiers.padding
import androidx.wear.protolayout.modifiers.tag
import androidx.wear.protolayout.modifiers.toProtoLayoutModifiers
-import androidx.wear.protolayout.types.LayoutColor
import kotlin.math.min
/**
@@ -55,8 +56,9 @@
*
* @param staticProgress The static progress of this progress indicator where 0 represent no
* progress and 1 represents completion. Progress above 1 is also allowed. If [dynamicProgress] is
- * also set, this static value will be ignored. By default it equals to 0.
- * @param dynamicProgress The static progress of this progress indicator where 0 represent no
+ * also set, this static value will only be used when the dynamic value cannot be evaluated. By
+ * default it equals to 0.
+ * @param dynamicProgress The dynamic progress of this progress indicator where 0 represent no
* progress and 1 represents completion. Progress above 1 is also allowed. If not provided, the
* [staticProgress] is used.
* @param modifier Modifiers to set to this element. It's highly recommended to set a content
@@ -94,16 +96,85 @@
colors: ProgressIndicatorColors = filledProgressIndicatorColors(),
size: ContainerDimension = expand(),
): LayoutElement {
- if (size is WrappedDimensionProp) {
- throw IllegalArgumentException("CircularProgressIndicator could not have size as wrap")
- }
+ // CircularProgressIndicator could not have size as wrap
+ verifySize(size)
val modifiers = (LayoutModifier.tag(METADATA_TAG) then modifier).toProtoLayoutModifiers()
- val validEndAngleDegrees = checkAndAdjustEndAngle(startAngleDegrees, endAngleDegrees)
return singleSegmentImpl(
startAngleDegrees = startAngleDegrees,
- endAngleDegrees = validEndAngleDegrees,
+ endAngleDegrees = checkAndAdjustEndAngle(startAngleDegrees, endAngleDegrees),
+ staticProgress = staticProgress,
+ dynamicProgress = dynamicProgress,
+ strokeWidth = strokeWidth,
+ gapSize = gapSize,
+ colors = colors
+ )
+ .setModifiers(modifiers)
+ .setWidth(size)
+ .setHeight(size)
+ .build()
+}
+
+/**
+ * Protolayout Material3 design segmented circular progress indicator.
+ *
+ * A segmented variant of [circularProgressIndicator] that is divided into equally sized segments.
+ *
+ * @param segmentCount Number of equal segments that the progress indicator should be divided into.
+ * Has to be a number greater than or equal to 1.
+ * @param staticProgress The static progress of this progress indicator where 0 represent no
+ * progress and 1 represents completion. Progress above 1 is also allowed. If [dynamicProgress] is
+ * also set, this static value will only be used when the dynamic value cannot be evaluated. By
+ * default it equals to 0.
+ * @param dynamicProgress The dynamic progress of this progress indicator where 0 represent no
+ * progress and 1 represents completion. Progress above 1 is also allowed. If not provided, the
+ * [staticProgress] is used.
+ * @param modifier Modifiers to set to this element. It's highly recommended to set a content
+ * description using [contentDescription].
+ * @param startAngleDegrees The starting position of the progress arc, measured clockwise in degrees
+ * from the 12 o'clock position.
+ * @param endAngleDegrees The ending position of the progress arc, measured clockwise in degrees
+ * from 12 o'clock position. This value must be bigger than [startAngleDegrees], otherwise an
+ * exception would be thrown. By default it equals to 'startAngleDegrees + 360'.
+ * @param strokeWidth The stroke width for the progress indicator. The recommended values are
+ * [CircularProgressIndicatorDefaults.LARGE_STROKE_WIDTH] and
+ * [CircularProgressIndicatorDefaults.SMALL_STROKE_WIDTH].
+ * @param gapSize The size in dp of the gap between the ends of the progress indicator and the
+ * track. The stroke end caps are not included in this distance.
+ * @param colors [ProgressIndicatorColors] that will be used to resolve the indicator and track
+ * color for this progress indicator.
+ * @param size The bounding box size of this progress indicator, applies to both width and height.
+ * The indicator arc and track arc are located on the largest circle that can be inscribed inside.
+ * It is highly recommended for the progress indicator in [graphicDataCard] to have its size as
+ * [expand], which is the default, to fill the available space for the best result across
+ * different screen sizes. Setting [size] with a [WrappedDimensionProp] instance will cause
+ * failure and throws an [IllegalArgumentException].
+ * @throws IllegalArgumentException When [size] is set to be [WrappedDimensionProp] instance or the
+ * provided [endAngleDegrees] is smaller than the [startAngleDegrees].
+ * @sample androidx.wear.protolayout.material3.samples.multipleSegmentsCircularProgressIndicator
+ */
+public fun MaterialScope.segmentedCircularProgressIndicator(
+ @IntRange(from = 1) segmentCount: Int,
+ staticProgress: Float = 0F,
+ dynamicProgress: DynamicFloat? = null,
+ modifier: LayoutModifier = LayoutModifier,
+ startAngleDegrees: Float = 0F,
+ endAngleDegrees: Float = startAngleDegrees + 360F,
+ @Dimension(unit = DP) strokeWidth: Float = LARGE_STROKE_WIDTH,
+ @Dimension(unit = DP) gapSize: Float = calculateRecommendedGapSize(strokeWidth),
+ colors: ProgressIndicatorColors = filledProgressIndicatorColors(),
+ size: ContainerDimension = expand(),
+): LayoutElement {
+ // CircularProgressIndicator could not have size as wrap
+ verifySize(size)
+
+ val modifiers = (LayoutModifier.tag(METADATA_TAG) then modifier).toProtoLayoutModifiers()
+
+ return multipleSegmentsImpl(
+ segmentCount = segmentCount,
+ startAngleDegrees = startAngleDegrees,
+ endAngleDegrees = checkAndAdjustEndAngle(startAngleDegrees, endAngleDegrees),
staticProgress = staticProgress,
dynamicProgress = dynamicProgress,
strokeWidth = strokeWidth,
@@ -131,7 +202,12 @@
colors: ProgressIndicatorColors
): Box.Builder {
val sweepAngle = endAngleDegrees - startAngleDegrees
- val progressInDegrees = progressInDegrees(sweepAngle, staticProgress, dynamicProgress)
+ val progressInDegrees =
+ progressInDegrees(
+ sweepAngle = sweepAngle,
+ staticProgress = staticProgress,
+ dynamicProgress = dynamicProgress
+ )
val trackInDegrees = trackInDegrees(sweepAngle, progressInDegrees)
// Indicator: anchor end to startAngle, counter clockwise
@@ -179,89 +255,83 @@
)
}
-public object CircularProgressIndicatorDefaults {
- /**
- * Returns the recommended [ProgressIndicatorColors] object to be used when placing the progress
- * indicator inside a graphic card with [CardDefaults.filledCardColors].
- */
- public fun MaterialScope.filledProgressIndicatorColors(): ProgressIndicatorColors =
- ProgressIndicatorColors(
- theme.colorScheme.onPrimary,
- theme.colorScheme.onPrimary.withOpacity(0.2F),
- theme.colorScheme.onPrimary.withOpacity(0.6F)
+/**
+ * Layout the content for segmented variant of progress indicator using [DashedArcLine].
+ *
+ * Note that we require valid start and end angles for calling this method.
+ */
+private fun MaterialScope.multipleSegmentsImpl(
+ segmentCount: Int,
+ startAngleDegrees: Float,
+ endAngleDegrees: Float,
+ staticProgress: Float,
+ dynamicProgress: DynamicFloat?,
+ @Dimension(unit = DP) strokeWidth: Float,
+ @Dimension(unit = DP) gapSize: Float,
+ colors: ProgressIndicatorColors
+): Box.Builder {
+ val sweepAngle = endAngleDegrees - startAngleDegrees
+ val progressInDegrees =
+ progressInDegrees(
+ sweepAngle = sweepAngle,
+ staticProgress = staticProgress,
+ dynamicProgress = dynamicProgress
)
-
- /**
- * Returns the recommended [ProgressIndicatorColors] object to be used when placing the progress
- * indicator inside a graphic card with [CardDefaults.filledTonalCardColors].
- */
- public fun MaterialScope.filledTonalProgressIndicatorColors(): ProgressIndicatorColors =
- ProgressIndicatorColors(
- theme.colorScheme.primary,
- theme.colorScheme.primary.withOpacity(0.2F),
- theme.colorScheme.primary.withOpacity(0.6F)
- )
-
- /**
- * Returns the recommended [ProgressIndicatorColors] object to be used when placing the progress
- * indicator inside a graphic card with [CardDefaults.filledVariantCardColors].
- */
- public fun MaterialScope.filledVariantProgressIndicatorColors(): ProgressIndicatorColors =
- ProgressIndicatorColors(
- theme.colorScheme.onPrimaryContainer,
- theme.colorScheme.onPrimaryContainer.withOpacity(0.2F),
- theme.colorScheme.onPrimaryContainer.withOpacity(0.6F),
- )
-
- /**
- * The recommended animation spec for animations from current progress to a new progress value.
- */
- public val recommendedAnimationSpec: AnimationSpec =
- AnimationSpec.Builder()
- .setAnimationParameters(
- AnimationParameters.Builder()
- .setDurationMillis(450)
- .setEasing(Easing.cubicBezier(0.2f, 0f, 0f, 1f))
- .build()
- )
+ val linePattern =
+ DashedLinePattern.Builder()
+ .setGapSize(gapSize)
+ .setGapInterval(sweepAngle / segmentCount)
.build()
- /** Large stroke width for circular progress indicator. */
- @Dimension(unit = DP) public const val LARGE_STROKE_WIDTH: Float = 8F
-
- /** Small stroke width for circular progress indicator. */
- @Dimension(unit = DP) public const val SMALL_STROKE_WIDTH: Float = 4F
-
- /**
- * Returns recommended size of the gap based on [strokeWidth].
- *
- * The absolute value can be customized with `gapSize` parameter on [circularProgressIndicator].
- */
- @Dimension(unit = DP)
- public fun calculateRecommendedGapSize(@Dimension(unit = DP) strokeWidth: Float): Float =
- strokeWidth / 3F
-
- internal const val METADATA_TAG: String = "M3CPI"
+ // We need to make the indicator arc a bit wider than the indicator arc to make sure the top
+ // arc covers the bottom one completely in the overlapped area.
+ val insetPadding = INDICATOR_STROKE_WIDTH_INCREMENT_PX / deviceConfiguration.screenWidthDp / 2F
+ return Box.Builder()
+ .addContent(
+ // the track
+ createArc(
+ anchorAngle = degrees(startAngleDegrees),
+ anchorType = LayoutElementBuilders.ARC_ANCHOR_START,
+ arcLength = degrees(sweepAngle),
+ arcColor = trackColor(staticProgress, dynamicProgress, colors),
+ strokeWidth = strokeWidth,
+ linePattern = linePattern,
+ arcDirection = LayoutElementBuilders.ARC_DIRECTION_CLOCKWISE
+ )
+ .setModifiers(
+ Modifiers.Builder()
+ // Note that View#setPadding only accept integer, so this will be
+ // rounded up to one pixel during inflation.
+ .setPadding(padding(insetPadding))
+ .build()
+ )
+ .build()
+ )
+ .addContent(
+ // the indicator
+ createArc(
+ anchorAngle = degrees(startAngleDegrees),
+ anchorType = LayoutElementBuilders.ARC_ANCHOR_START,
+ arcLength = progressInDegrees,
+ arcColor = colors.indicatorColor.prop,
+ strokeWidth = strokeWidth + insetPadding * 2F,
+ linePattern = linePattern,
+ arcDirection = LayoutElementBuilders.ARC_DIRECTION_CLOCKWISE
+ )
+ .build()
+ )
}
/**
- * Represents the indicator and track colors used in progress indicator.
+ * Verify that it is not a size of wrap, otherwise throw an exception.
*
- * @param indicatorColor Color used to draw the indicator of progress indicator.
- * @param trackColor Color used to draw the track of progress indicator.
- * @param trackOverflowColor Color used to draw the track for progress overflow (>1).
+ * @throws IllegalArgumentException When [size] is set to be [WrappedDimensionProp] instance.
*/
-public class ProgressIndicatorColors(
- public val indicatorColor: LayoutColor,
- public val trackColor: LayoutColor,
- public val trackOverflowColor: LayoutColor = trackColor
-)
-
-/**
- * A small offset to make the progress arc remaining when the progress is 1, with the module
- * operation applied for handling overflow.
- */
-private const val TRIVIAL_ARC_OFFSET: Float = 0.05f
+private fun verifySize(size: ContainerDimension) {
+ if (size is WrappedDimensionProp) {
+ throw IllegalArgumentException("CircularProgressIndicator could not have size as wrap")
+ }
+}
/*
* Check the endAngle is valid with the provided startAngle, and adjust the sweep angle to be 360
diff --git a/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/CircularProgressIndicatorDefaults.kt b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/CircularProgressIndicatorDefaults.kt
new file mode 100644
index 0000000..7ca7208
--- /dev/null
+++ b/wear/protolayout/protolayout-material3/src/main/java/androidx/wear/protolayout/material3/CircularProgressIndicatorDefaults.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.protolayout.material3
+
+import androidx.annotation.Dimension
+import androidx.annotation.Dimension.Companion.DP
+import androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationParameters
+import androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec
+import androidx.wear.protolayout.expression.AnimationParameterBuilders.Easing
+import androidx.wear.protolayout.types.LayoutColor
+
+/**
+ * Represents the indicator and track colors used in progress indicator.
+ *
+ * @param indicatorColor Color used to draw the indicator of progress indicator.
+ * @param trackColor Color used to draw the track of progress indicator.
+ * @param trackOverflowColor Color used to draw the track for progress overflow (>1).
+ */
+public class ProgressIndicatorColors(
+ public val indicatorColor: LayoutColor,
+ public val trackColor: LayoutColor,
+ public val trackOverflowColor: LayoutColor = trackColor
+)
+
+public object CircularProgressIndicatorDefaults {
+ /**
+ * Returns the recommended [ProgressIndicatorColors] object to be used when placing the progress
+ * indicator inside a graphic card with [CardDefaults.filledCardColors].
+ */
+ public fun MaterialScope.filledProgressIndicatorColors(): ProgressIndicatorColors =
+ ProgressIndicatorColors(
+ theme.colorScheme.onPrimary,
+ theme.colorScheme.onPrimary.withOpacity(0.2F),
+ theme.colorScheme.onPrimary.withOpacity(0.6F)
+ )
+
+ /**
+ * Returns the recommended [ProgressIndicatorColors] object to be used when placing the progress
+ * indicator inside a graphic card with [CardDefaults.filledTonalCardColors].
+ */
+ public fun MaterialScope.filledTonalProgressIndicatorColors(): ProgressIndicatorColors =
+ ProgressIndicatorColors(
+ theme.colorScheme.primary,
+ theme.colorScheme.primary.withOpacity(0.2F),
+ theme.colorScheme.primary.withOpacity(0.6F)
+ )
+
+ /**
+ * Returns the recommended [ProgressIndicatorColors] object to be used when placing the progress
+ * indicator inside a graphic card with [CardDefaults.filledVariantCardColors].
+ */
+ public fun MaterialScope.filledVariantProgressIndicatorColors(): ProgressIndicatorColors =
+ ProgressIndicatorColors(
+ theme.colorScheme.onPrimaryContainer,
+ theme.colorScheme.onPrimaryContainer.withOpacity(0.2F),
+ theme.colorScheme.onPrimaryContainer.withOpacity(0.6F),
+ )
+
+ /**
+ * The recommended animation spec for animations from current progress to a new progress value.
+ */
+ public val recommendedAnimationSpec: AnimationSpec =
+ AnimationSpec.Builder()
+ .setAnimationParameters(
+ AnimationParameters.Builder()
+ .setDurationMillis(450)
+ .setEasing(Easing.cubicBezier(0.2f, 0f, 0f, 1f))
+ .build()
+ )
+ .build()
+
+ /** Large stroke width for circular progress indicator. */
+ @Dimension(unit = DP) public const val LARGE_STROKE_WIDTH: Float = 8F
+
+ /** Small stroke width for circular progress indicator. */
+ @Dimension(unit = DP) public const val SMALL_STROKE_WIDTH: Float = 4F
+
+ /**
+ * Returns recommended size of the gap based on [strokeWidth].
+ *
+ * The absolute value can be customized with `gapSize` parameter on [circularProgressIndicator].
+ */
+ @Dimension(unit = DP)
+ public fun calculateRecommendedGapSize(@Dimension(unit = DP) strokeWidth: Float): Float =
+ strokeWidth / 3F
+
+ internal const val METADATA_TAG: String = "M3CPI"
+ /**
+ * A small offset to make the progress arc remaining when the progress is 1, with the module
+ * operation applied for handling overflow.
+ */
+ internal const val TRIVIAL_ARC_OFFSET: Float = 0.05f
+
+ /**
+ * Extra stroke width for progress arc to prevent aliasing issue where the progress arc draws on
+ * top of the track arc where there are multiple segments.
+ */
+ internal const val INDICATOR_STROKE_WIDTH_INCREMENT_PX: Float = 1.5f
+}
diff --git a/wear/tiles/tiles-samples/src/main/java/androidx/wear/tiles/samples/tile/PlaygroundTileService.kt b/wear/tiles/tiles-samples/src/main/java/androidx/wear/tiles/samples/tile/PlaygroundTileService.kt
index 9171bf1..f3f97b9 100644
--- a/wear/tiles/tiles-samples/src/main/java/androidx/wear/tiles/samples/tile/PlaygroundTileService.kt
+++ b/wear/tiles/tiles-samples/src/main/java/androidx/wear/tiles/samples/tile/PlaygroundTileService.kt
@@ -20,17 +20,16 @@
import androidx.wear.protolayout.DimensionBuilders.expand
import androidx.wear.protolayout.DimensionBuilders.weight
import androidx.wear.protolayout.LayoutElementBuilders
-import androidx.wear.protolayout.LayoutElementBuilders.Box
-import androidx.wear.protolayout.ModifiersBuilders.Background
-import androidx.wear.protolayout.ModifiersBuilders.Modifiers
import androidx.wear.protolayout.ResourceBuilders
import androidx.wear.protolayout.ResourceBuilders.AndroidImageResourceByResId
import androidx.wear.protolayout.ResourceBuilders.ImageResource
import androidx.wear.protolayout.TimelineBuilders
+import androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat
import androidx.wear.protolayout.material3.ButtonDefaults.filledVariantButtonColors
import androidx.wear.protolayout.material3.CardColors
import androidx.wear.protolayout.material3.CardDefaults.filledTonalCardColors
import androidx.wear.protolayout.material3.CardDefaults.filledVariantCardColors
+import androidx.wear.protolayout.material3.CircularProgressIndicatorDefaults.recommendedAnimationSpec
import androidx.wear.protolayout.material3.DataCardStyle.Companion.extraLargeDataCardStyle
import androidx.wear.protolayout.material3.DataCardStyle.Companion.smallCompactDataCardStyle
import androidx.wear.protolayout.material3.MaterialScope
@@ -46,6 +45,7 @@
import androidx.wear.protolayout.material3.iconDataCard
import androidx.wear.protolayout.material3.materialScope
import androidx.wear.protolayout.material3.primaryLayout
+import androidx.wear.protolayout.material3.segmentedCircularProgressIndicator
import androidx.wear.protolayout.material3.text
import androidx.wear.protolayout.material3.textButton
import androidx.wear.protolayout.material3.textDataCard
@@ -123,7 +123,7 @@
): LayoutElementBuilders.LayoutElement =
materialScope(context = context, deviceConfiguration = requestParams.deviceConfiguration) {
primaryLayout(
- mainSlot = { oneSlotButtons() },
+ mainSlot = { graphicDataCardSample() },
bottomSlot = {
textEdgeButton(
onClick = clickable(),
@@ -241,20 +241,12 @@
)
},
graphic = {
- Box.Builder()
- .setWidth(expand())
- .setHeight(expand())
- .setModifiers(
- Modifiers.Builder()
- .setBackground(
- Background.Builder()
- .setCorner(shapes.full)
- .setColor(colorScheme.background.prop)
- .build()
- )
- .build()
- )
- .build()
+ segmentedCircularProgressIndicator(
+ segmentCount = 6,
+ startAngleDegrees = 200F,
+ endAngleDegrees = 520F,
+ dynamicProgress = DynamicFloat.animate(0.0F, 1.5F, recommendedAnimationSpec),
+ )
}
)