diff --git a/catalog/java/io/material/catalog/progressindicator/ProgressIndicatorMainDemoFragment.java b/catalog/java/io/material/catalog/progressindicator/ProgressIndicatorMainDemoFragment.java index 9fbf37189..f0e13d183 100644 --- a/catalog/java/io/material/catalog/progressindicator/ProgressIndicatorMainDemoFragment.java +++ b/catalog/java/io/material/catalog/progressindicator/ProgressIndicatorMainDemoFragment.java @@ -80,7 +80,7 @@ public class ProgressIndicatorMainDemoFragment extends ProgressIndicatorDemoFrag Slider thicknessSlider = view.findViewById(R.id.thicknessSlider); thicknessSlider.addOnChangeListener( (slider, value, fromUser) -> { - int newThickness = Math.round(value * pixelsInDp); + int newThickness = (int) (value * pixelsInDp); if (linearIndicator.getTrackThickness() != newThickness) { linearIndicator.setTrackThickness(newThickness); } @@ -92,7 +92,7 @@ public class ProgressIndicatorMainDemoFragment extends ProgressIndicatorDemoFrag Slider cornerSlider = view.findViewById(R.id.cornerSlider); cornerSlider.addOnChangeListener( (slider, value, fromUser) -> { - int newCornerRadius = Math.round(value * pixelsInDp); + int newCornerRadius = (int) (value * pixelsInDp); if (linearIndicator.getTrackCornerRadius() != newCornerRadius) { linearIndicator.setTrackCornerRadius(newCornerRadius); } @@ -104,7 +104,7 @@ public class ProgressIndicatorMainDemoFragment extends ProgressIndicatorDemoFrag Slider gapSlider = view.findViewById(R.id.gapSlider); gapSlider.addOnChangeListener( (slider, value, fromUser) -> { - int newGapSize = Math.round(value * pixelsInDp); + int newGapSize = (int) (value * pixelsInDp); if (linearIndicator.getIndicatorTrackGapSize() != newGapSize) { linearIndicator.setIndicatorTrackGapSize(newGapSize); } @@ -125,7 +125,7 @@ public class ProgressIndicatorMainDemoFragment extends ProgressIndicatorDemoFrag Slider linearStopIndicatorSlider = view.findViewById(R.id.linearStopIndicatorSizeSlider); linearStopIndicatorSlider.addOnChangeListener( (slider, value, fromUser) -> { - int newStopIndicatorSize = Math.round(value * pixelsInDp); + int newStopIndicatorSize = (int) (value * pixelsInDp); if (linearIndicator.getTrackStopIndicatorSize() != newStopIndicatorSize) { linearIndicator.setTrackStopIndicatorSize(newStopIndicatorSize); } @@ -134,7 +134,7 @@ public class ProgressIndicatorMainDemoFragment extends ProgressIndicatorDemoFrag Slider circularSizeSlider = view.findViewById(R.id.circularSizeSlider); circularSizeSlider.addOnChangeListener( (slider, value, fromUser) -> { - int newCornerRadius = Math.round(value * pixelsInDp); + int newCornerRadius = (int) (value * pixelsInDp); if (circularIndicator.getIndicatorSize() != newCornerRadius) { circularIndicator.setIndicatorSize(newCornerRadius); } diff --git a/docs/components/ProgressIndicator.md b/docs/components/ProgressIndicator.md index 4760fe123..998e3ff5f 100644 --- a/docs/components/ProgressIndicator.md +++ b/docs/components/ProgressIndicator.md @@ -50,12 +50,10 @@ A determinate progress indicator can be added to a layout: ```xml - + android:layout_width="match_parent" + android:layout_height="wrap_content" /> +android:layout_width="wrap_content" android:layout_height="wrap_content" /> ``` An indeterminate progress indicator can be added: @@ -63,21 +61,19 @@ An indeterminate progress indicator can be added: ```xml - + android:layout_width="match_parent" android:layout_height="wrap_content" + android:indeterminate="true" /> +android:layout_width="wrap_content" android:layout_height="wrap_content" +android:indeterminate="true" /> ``` -![Animation of determinate and indeterminate linear and circular progress indicators: red -indicators travel along pink tracks](assets/progressindicator/linear_circular_composite.gif) +![Animation of determinate and indeterminate linear and circular progress +indicators: red indicators travel along pink +tracks](assets/progressindicator/linear_circular_composite.gif) -1. Determinate -2. Indeterminate +1. Determinate +2. Indeterminate ### Switching from indeterminate to determinate @@ -85,13 +81,13 @@ Indeterminate progress indicators can smoothly transit to determinate progress indicators by setting the `progress` programmatically: ```kt -int progress = getLoadingProgress() +int progress = getLoadingProgress () indicator.setProgressCompat(progress, true) ``` -**Note:** Once indeterminate progress indicators are switched to the -determinate mode (or initialized as determinate), they can be set back to -indeterminate mode via calling the `setIndeterminate(true)` method. +**Note:** Once indeterminate progress indicators are switched to the determinate +mode (or initialized as determinate), they can be set back to indeterminate mode +via calling the `setIndeterminate(true)` method. ### Making progress indicators accessible @@ -132,8 +128,8 @@ The modes of behaviors are: edge and hides the view by collapsing to the baseline (bottom edge); for the circular type, shows the view by expanding from the outer edge and hides the view by collapsing to the inner edge. -* `escape` - for the linear type, escapes in the progression direction; for the - circular type, no effect. +* `escape` - for the linear type, escapes in the progression direction; for + the circular type, no effect. When the hide animation behavior is not none, the visibility of the view will be changed after the animation finishes. Please use `setVisibilityAfterHide` method @@ -142,8 +138,9 @@ to set the target visibility as `Visibility.INVISIBLE` (default) or ### Rounded corners -Progress indicators can have rounded corners via `app:trackCornerRadius` or the -`setTrackCornerRadius` method. +Progress indicators can have rounded corners via `app:trackCornerRadius` (in dp +or fraction) or the `setTrackCornerRadius` and `setTrackCornerRadiusFraction` +methods. ### Types @@ -179,7 +176,8 @@ API and source code: The following example shows a determinate linear progress indicator. -Determinate linear progress indicator animation: purple indicator fills up
+<img src= @@ -187,13 +185,13 @@ In the layout: ```xml + android:layout_width="match_parent" android:layout_height="wrap_content" /> ``` The following example shows an indeterminate linear progress indicator. -Indeterminate linear progress indicator animation: purple indicator travels
+<img src= @@ -201,9 +199,8 @@ In the layout: ```xml + android:layout_width="match_parent" android:layout_height="wrap_content" + android:indeterminate="true" /> ``` #### Multi-color indeterminate animation type @@ -211,14 +208,12 @@ In the layout: For linear progress indicator, there are two indeterminate animation types: * `disjoint` - animates as repeated cycles with two disjoint segments in the - same color at a time. Disjointed indeterminate linear progress indicator animation: red indicator
-travels along track 2x then switches to
-yellow + same color at a time. + Disjointed indeterminate linear progress indicator animation: red indicator travels along track 2x then switches to yellow * `contiguous` - animates as repeated cycles with three adjacent segments in - different colors. Contiguous indeterminate linear progress indicator
-    animation: red, yellow, blue indicators move sequentially and cover
-    track + different colors. + Contiguous indeterminate linear progress indicator animation: red, yellow, blue indicators move sequentially and cover track **Note:** There is a minimum requirement of 3 indicator colors to use the **contiguous** animation. Otherwise, an IllegalArgumentException will be thrown. @@ -245,7 +240,8 @@ API and source code: The following example shows a determinate circular progress indicator. -Determinate circular progress indicator animation: purple indicator draws a
+<img src= @@ -253,13 +249,13 @@ In the layout: ```xml + android:layout_width="wrap_content" android:layout_height="wrap_content" /> ``` The following example shows an indeterminate circular progress indicator. -Indeterminate circular progress indicator animation: purple indicator follows
+<img src= @@ -267,9 +263,8 @@ In the layout: ```xml + android:layout_width="wrap_content" android:layout_height="wrap_content" + android:indeterminate="true" /> ``` ### Anatomy and key properties @@ -278,32 +273,32 @@ A progress indicator consists of a track and an indicator. ![Progress indicator anatomy composite](assets/progressindicator/anatomy.png) -1. Active indicator -2. Track -3. Stop indicator +1. Active indicator +2. Track +3. Stop indicator #### Common attributes The following attributes are shared between linear and circular progress indicators: -Element | Attribute | Related method(s) | Default value ------------------------------------------ | ---------------------------------------- | ------------------------------------------------------------ | ------------- -**Track thickness** | `app:trackThickness` | `setTrackThickness`
`getTrackThickness` | `4dp` -**Indicator color** | `app:indicatorColor` | `setIndicatorColor`
`getIndicatorColor` | `colorPrimary` -**Track color** | `app:trackColor` | `setTrackColor`
`getTrackColor` | `colorPrimaryContainer` (linear)
`@android:color/transparent` (circular) -**Track corner radius** | `app:trackCornerRadius` | `setTrackCornerRadius`
`getTrackCornerRadius` | `2dp` -**Indicator track gap size** | `app:indicatorTrackGapSize` | `setIndicatorTrackGapSize`
`getIndicatorTrackGapSize` | `4dp` -**Show animation behavior** | `app:showAnimationBehavior` | `setShowAnimationBehavior`
`getShowAnimationBehavior` | `none` -**Hide animation behavior** | `app:hideAnimationBehavior` | `setHideAnimationBehavior`
`getHideAnimationBehavior` | `none` -**Delay (in ms) to show** | `app:showDelay` | N/A | 0 -**Min delay (in ms) to hide** | `app:minHideDelay` | N/A | 0 -**Wavelength** | `app:wavelength` | `setWavelength` | 0 -**Wavelength in determinate mode** | `app:wavelengthDeterminate` | `setWavelengthDeterminate`
`getWavelenthDeterminate` | `wavelength` -**Wavelength in indeterminate mode** | `app:wavelengthIndeterminate` | `setWavelengthIndeterminate`
`getWavelengthIndeterminate` | `wavelength` -**Wave amplitude** | `app:waveAmplitude` | `setWaveAmplitude`
`getWaveAmplitude` | 0 -**Wave speed** | `app:waveSpeed` | `setWaveSpeed`
`getWaveSpeed` | 0 -**Indeterminate animator duration scale** | `app:indeterminateAnimatorDurationScale` | `setIndeterminateAnimatorDurationScale` | 1 +Element | Attribute | Related method(s) | Default value +----------------------------------------- | ---------------------------------------- | ---------------------------------------------------------------------------------- | ------------- +**Track thickness** | `app:trackThickness` | `setTrackThickness`
`getTrackThickness` | `4dp` +**Indicator color** | `app:indicatorColor` | `setIndicatorColor`
`getIndicatorColor` | `colorPrimary` +**Track color** | `app:trackColor` | `setTrackColor`
`getTrackColor` | `colorPrimaryContainer` (linear)
`@android:color/transparent` (circular) +**Track corner radius** | `app:trackCornerRadius` | `setTrackCornerRadius`
`setTrackCornerRadiusFraction`
`getTrackCornerRadius` | `50%` +**Indicator track gap size** | `app:indicatorTrackGapSize` | `setIndicatorTrackGapSize`
`getIndicatorTrackGapSize` | `4dp` +**Show animation behavior** | `app:showAnimationBehavior` | `setShowAnimationBehavior`
`getShowAnimationBehavior` | `none` +**Hide animation behavior** | `app:hideAnimationBehavior` | `setHideAnimationBehavior`
`getHideAnimationBehavior` | `none` +**Delay (in ms) to show** | `app:showDelay` | N/A | 0 +**Min delay (in ms) to hide** | `app:minHideDelay` | N/A | 0 +**Wavelength** | `app:wavelength` | `setWavelength` | 0 +**Wavelength in determinate mode** | `app:wavelengthDeterminate` | `setWavelengthDeterminate`
`getWavelenthDeterminate` | `wavelength` +**Wavelength in indeterminate mode** | `app:wavelengthIndeterminate` | `setWavelengthIndeterminate`
`getWavelengthIndeterminate` | `wavelength` +**Wave amplitude** | `app:waveAmplitude` | `setWaveAmplitude`
`getWaveAmplitude` | 0 +**Wave speed** | `app:waveSpeed` | `setWaveSpeed`
`getWaveSpeed` | 0 +**Indeterminate animator duration scale** | `app:indeterminateAnimatorDurationScale` | `setIndeterminateAnimatorDurationScale` | 1 #### Linear type specific attributes @@ -314,7 +309,7 @@ Element | Attribute | Related me **Indeterminate animation type** | `app:indeterminateAnimationType` | `setIndeterminateAnimationType`
`getIndeterminateAnimationType` | `disjoint` **Indicator direction** | `app:indicatorDirectionLinear` | `setIndicatorDirection`
`getIndicatorDirection` | `leftToRight` **Track stop indicator size** | `app:trackStopIndicatorSize` | `setTrackStopIndicatorSize`
`getTrackStopIndicatorSize` | `4dp` -**Track inner corner radius** | `app:trackInnerCornerRadius` | `setTrackInnerCornerRadius`
`setTrackInnerCornerRadiusFraction`
`getTrackInnerCornerRadius` | `none` +**Track inner corner radius** | `app:trackInnerCornerRadius` | `setTrackInnerCornerRadius`
`setTrackInnerCornerRadiusFraction`
`getTrackInnerCornerRadius` | `none` (use `trackCornerRadius`) #### Circular type specific attributes @@ -352,16 +347,16 @@ In order to comply with the latest accessibility requirements, the `LinearProgressIndicator` and `CircularProgressIndicator` have been updated with additional attributes: -- `app:indicatorTrackGapSize`: size of the gap between the indicator and the - track, 4dp by default. -- `app:trackStopIndicatorSize`: size of the stop at the end of the track, 4dp by - default. Only applies to the linear determinate configuration. +- `app:indicatorTrackGapSize`: size of the gap between the indicator and the + track, 4dp by default. +- `app:trackStopIndicatorSize`: size of the stop at the end of the track, 4dp + by default. Only applies to the linear determinate configuration. `*.Legacy` styles have been added to revert to the previous behavior (**not recommended**): -- `Widget.Material3.LinearProgressIndicator.Legacy` -- `Widget.Material3.CircularProgressIndicator.Legacy` +- `Widget.Material3.LinearProgressIndicator.Legacy` +- `Widget.Material3.CircularProgressIndicator.Legacy` ## Theming @@ -391,8 +386,8 @@ circular progress indicators and affects other components: ```xml ``` @@ -401,17 +396,22 @@ all circular progress indicators but does not affect other components: ```xml - ``` @@ -419,7 +419,5 @@ Use the style in the layout, which affects only this specific circular progress indicator: ```xml - + ``` diff --git a/lib/java/com/google/android/material/progressindicator/BaseProgressIndicator.java b/lib/java/com/google/android/material/progressindicator/BaseProgressIndicator.java index b20974d29..8cf6c4c89 100644 --- a/lib/java/com/google/android/material/progressindicator/BaseProgressIndicator.java +++ b/lib/java/com/google/android/material/progressindicator/BaseProgressIndicator.java @@ -647,7 +647,36 @@ public abstract class BaseProgressIndicator */ public void setTrackCornerRadius(@Px int trackCornerRadius) { if (spec.trackCornerRadius != trackCornerRadius) { - spec.trackCornerRadius = Math.round(min(trackCornerRadius, spec.trackThickness / 2f)); + spec.trackCornerRadius = min(trackCornerRadius, spec.trackThickness / 2); + spec.useRelativeTrackCornerRadius = false; + invalidate(); + } + } + + /** + * Returns the relative radius of the rounded corner for the indicator and track in pixels. + * + * @see #setTrackCornerRadiusFraction(float) + * @attr ref + * com.google.android.material.progressindicator.R.styleable#BaseProgressIndicator_trackCornerRadius + */ + public float getTrackCornerRadiusFraction() { + return spec.trackCornerRadiusFraction; + } + + /** + * Sets the radius of the rounded corner for the indicator and track in fraction of track + * thickness. + * + * @param fraction The fraction of corner radius to track thickness. + * @see #getTrackCornerRadiusFraction() + * @attr ref + * com.google.android.material.progressindicator.R.styleable#BaseProgressIndicator_trackCornerRadius + */ + public void setTrackCornerRadiusFraction(@FloatRange(from = 0f, to = 0.5f) float fraction) { + if (spec.trackCornerRadiusFraction != fraction) { + spec.trackCornerRadiusFraction = min(fraction, 0.5f); + spec.useRelativeTrackCornerRadius = true; invalidate(); } } diff --git a/lib/java/com/google/android/material/progressindicator/BaseProgressIndicatorSpec.java b/lib/java/com/google/android/material/progressindicator/BaseProgressIndicatorSpec.java index 7c17a8528..5f99166e5 100644 --- a/lib/java/com/google/android/material/progressindicator/BaseProgressIndicatorSpec.java +++ b/lib/java/com/google/android/material/progressindicator/BaseProgressIndicatorSpec.java @@ -54,6 +54,18 @@ public abstract class BaseProgressIndicatorSpec { */ @Px public int trackCornerRadius; + /** + * The fraction of the track thickness to be used as the corner radius. And the stroke ROUND cap + * is used to prevent artifacts like (b/319309456), when 0.5f is specified. + */ + public float trackCornerRadiusFraction; + + /** + * When this is true, the {#link trackCornerRadiusFraction} takes effect. Otherwise, the {@link + * trackCornerRadius} takes effect. + */ + public boolean useRelativeTrackCornerRadius; + /** * The color array used in the indicator. In determinate mode, only the first item will be used. */ @@ -112,11 +124,21 @@ public abstract class BaseProgressIndicatorSpec { trackThickness = getDimensionPixelSize( context, a, R.styleable.BaseProgressIndicator_trackThickness, defaultIndicatorSize); - trackCornerRadius = - min( - getDimensionPixelSize( - context, a, R.styleable.BaseProgressIndicator_trackCornerRadius, 0), - Math.round(trackThickness / 2f)); + TypedValue trackCornerRadiusValue = + a.peekValue(R.styleable.BaseProgressIndicator_trackCornerRadius); + if (trackCornerRadiusValue != null) { + if (trackCornerRadiusValue.type == TypedValue.TYPE_DIMENSION) { + trackCornerRadius = + min( + TypedValue.complexToDimensionPixelSize( + trackCornerRadiusValue.data, a.getResources().getDisplayMetrics()), + trackThickness / 2); + useRelativeTrackCornerRadius = false; + } else if (trackCornerRadiusValue.type == TypedValue.TYPE_FRACTION) { + trackCornerRadiusFraction = min(trackCornerRadiusValue.getFraction(1.0f, 1.0f), 0.5f); + useRelativeTrackCornerRadius = true; + } + } showAnimationBehavior = a.getInt( R.styleable.BaseProgressIndicator_showAnimationBehavior, @@ -222,6 +244,27 @@ public abstract class BaseProgressIndicatorSpec { && ((!isDeterminate && wavelengthIndeterminate > 0) || (isDeterminate && wavelengthDeterminate > 0)); } + + /** + * Returns the track corner radius in pixels. + * + *

If {@link #useRelativeTrackCornerRadius} is true, the track corner radius is calculated + * using the track thickness and the track corner radius fraction. Otherwise, the track corner + * radius is returned directly. + */ + public int getTrackCornerRadiusInPx() { + return useRelativeTrackCornerRadius + ? (int) (trackThickness * trackCornerRadiusFraction) + : trackCornerRadius; + } + + /** + * Returns true if the stroke ROUND cap should be used to prevent artifacts like (b/319309456), + * when fully rounded corners are specified. + */ + public boolean useStrokeCap() { + return useRelativeTrackCornerRadius && trackCornerRadiusFraction == 0.5f; + } @CallSuper void validateSpec() { diff --git a/lib/java/com/google/android/material/progressindicator/CircularDrawingDelegate.java b/lib/java/com/google/android/material/progressindicator/CircularDrawingDelegate.java index 737fe4e3b..6a2e16d89 100644 --- a/lib/java/com/google/android/material/progressindicator/CircularDrawingDelegate.java +++ b/lib/java/com/google/android/material/progressindicator/CircularDrawingDelegate.java @@ -58,8 +58,6 @@ final class CircularDrawingDelegate extends DrawingDelegate 0) { + if (!spec.useStrokeCap() && displayedCornerRadius > 0) { paint.setStyle(Style.FILL); drawRoundedBlock(canvas, paint, endPoints.first, blockWidth, displayedTrackThickness); drawRoundedBlock(canvas, paint, endPoints.second, blockWidth, displayedTrackThickness); diff --git a/lib/java/com/google/android/material/progressindicator/LinearDrawingDelegate.java b/lib/java/com/google/android/material/progressindicator/LinearDrawingDelegate.java index 623570488..77421aaad 100644 --- a/lib/java/com/google/android/material/progressindicator/LinearDrawingDelegate.java +++ b/lib/java/com/google/android/material/progressindicator/LinearDrawingDelegate.java @@ -55,7 +55,6 @@ final class LinearDrawingDelegate extends DrawingDelegate 0 && startCornerRadius > 0) { // Draws the start rounded block. drawRoundedBlock( diff --git a/lib/java/com/google/android/material/progressindicator/LinearProgressIndicatorSpec.java b/lib/java/com/google/android/material/progressindicator/LinearProgressIndicatorSpec.java index 93256d613..030129bd7 100644 --- a/lib/java/com/google/android/material/progressindicator/LinearProgressIndicatorSpec.java +++ b/lib/java/com/google/android/material/progressindicator/LinearProgressIndicatorSpec.java @@ -130,12 +130,17 @@ public final class LinearProgressIndicatorSpec extends BaseProgressIndicatorSpec public int getTrackInnerCornerRadiusInPx() { return !hasInnerCornerRadius - ? trackCornerRadius + ? getTrackCornerRadiusInPx() : useRelativeTrackInnerCornerRadius ? (int) (trackThickness * trackInnerCornerRadiusFraction) : trackInnerCornerRadius; } + @Override + public boolean useStrokeCap() { + return super.useStrokeCap() && getTrackInnerCornerRadiusInPx() == getTrackCornerRadiusInPx(); + } + @Override void validateSpec() { super.validateSpec(); @@ -145,7 +150,8 @@ public final class LinearProgressIndicatorSpec extends BaseProgressIndicatorSpec } if (indeterminateAnimationType == LinearProgressIndicator.INDETERMINATE_ANIMATION_TYPE_CONTIGUOUS) { - if ((trackCornerRadius > 0 || (hasInnerCornerRadius && getTrackInnerCornerRadiusInPx() > 0)) + if ((getTrackCornerRadiusInPx() > 0 + || (hasInnerCornerRadius && getTrackInnerCornerRadiusInPx() > 0)) && indicatorTrackGapSize == 0) { // Throws an exception if trying to use the cornered indicator/track with contiguous // indeterminate animation type without gap. diff --git a/lib/java/com/google/android/material/progressindicator/res/values/attrs.xml b/lib/java/com/google/android/material/progressindicator/res/values/attrs.xml index 1e4018398..ee81ccd8f 100644 --- a/lib/java/com/google/android/material/progressindicator/res/values/attrs.xml +++ b/lib/java/com/google/android/material/progressindicator/res/values/attrs.xml @@ -24,9 +24,9 @@ - +