Skip to content

Commit bc59873

Browse files
paulfthomasdsn5ft
authored andcommitted
[NTC][ProgressIndicator] Internal changes
PiperOrigin-RevId: 581401427
1 parent 5e5eee0 commit bc59873

File tree

16 files changed

+275
-54
lines changed

16 files changed

+275
-54
lines changed

docs/components/ProgressIndicator.md

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -289,25 +289,27 @@ A progress indicator consists of a track and an indicator.
289289
The following attributes are shared between linear and circular progress
290290
indicators:
291291

292-
Element | Attribute | Related method(s) | Default value
293-
----------------------------- | --------------------------- | --------------------------------------------------------- | -------------
294-
**Track thickness** | `app:trackThickness` | `setTrackThickness`</br>`getTrackThickness` | `4dp`
295-
**Indicator color** | `app:indicatorColor` | `setIndicatorColor`</br>`getIndicatorColor` | `colorPrimary`
296-
**Track color** | `app:trackColor` | `setTrackColor`</br>`getTrackColor` | `colorSurfaceContainerHighest` (linear)</br>`@android:color/transparent` (circular)
297-
**Track corner radius** | `app:trackCornerRadius` | `setTrackCornerRadius`</br>`getTrackCornerRadius` | `0dp`
298-
**Show animation behavior** | `app:showAnimationBehavior` | `setShowAnimationBehavior`</br>`getShowAnimationBehavior` | `none`
299-
**Hide animation behavior** | `app:hideAnimationBehavior` | `setHideAnimationBehavior`</br>`getHideAnimationBehavior` | `none`
300-
**Delay (in ms) to show** | `app:showDelay` | N/A | 0
301-
**Min delay (in ms) to hide** | `app:minHideDelay` | N/A | 0
292+
| Element | Attribute | Related method(s) | Default value |
293+
|-------------------------------|-----------------------------|-----------------------------------------------------------|-------------------------------------------------------------------------------------|
294+
| **Track thickness** | `app:trackThickness` | `setTrackThickness`</br>`getTrackThickness` | `4dp` |
295+
| **Indicator color** | `app:indicatorColor` | `setIndicatorColor`</br>`getIndicatorColor` | `colorPrimary` |
296+
| **Track color** | `app:trackColor` | `setTrackColor`</br>`getTrackColor` | `colorSurfaceContainerHighest` (linear)</br>`@android:color/transparent` (circular) |
297+
| **Track corner radius** | `app:trackCornerRadius` | `setTrackCornerRadius`</br>`getTrackCornerRadius` | `0dp` |
298+
| **Indicator track gap size** | `app:indicatorTrackGapSize` | `setIndicatorTrackGapSize`</br>`getIndicatorTrackGapSize` | `4dp` |
299+
| **Show animation behavior** | `app:showAnimationBehavior` | `setShowAnimationBehavior`</br>`getShowAnimationBehavior` | `none` |
300+
| **Hide animation behavior** | `app:hideAnimationBehavior` | `setHideAnimationBehavior`</br>`getHideAnimationBehavior` | `none` |
301+
| **Delay (in ms) to show** | `app:showDelay` | N/A | 0 |
302+
| **Min delay (in ms) to hide** | `app:minHideDelay` | N/A | 0 |
302303

303304
#### Linear type specific attributes
304305

305306
Linear type progress indicators also have the following attributes:
306307

307-
Element | Attribute | Related method(s) | Default value
308-
-------------------------------- | -------------------------------- | ------------------------------------------------------------------- | -------------
309-
**Indeterminate animation type** | `app:indeterminateAnimationType` | `setIndeterminateAnimationType`</br>`getIndeterminateAnimationType` | `disjoint`
310-
**Indicator direction** | `app:indicatorDirectionLinear` | `setIndicatorDirection`</br>`getIndicatorDirection` | `leftToRight`
308+
| Element | Attribute | Related method(s) | Default value |
309+
|----------------------------------|----------------------------------|---------------------------------------------------------------------|---------------|
310+
| **Indeterminate animation type** | `app:indeterminateAnimationType` | `setIndeterminateAnimationType`</br>`getIndeterminateAnimationType` | `disjoint` |
311+
| **Indicator direction** | `app:indicatorDirectionLinear` | `setIndicatorDirection`</br>`getIndicatorDirection` | `leftToRight` |
312+
| **Track stop indicator size** | `app:trackStopIndicatorSize` | `setTrackStopIndicatorSize`</br>`getTrackStopIndicatorSize` | `4dp` |
311313

312314
#### Circular type specific attributes
313315

lib/java/com/google/android/material/progressindicator/BaseProgressIndicator.java

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ public abstract class BaseProgressIndicator<S extends BaseProgressIndicatorSpec>
7676
public static final int HIDE_NONE = 0;
7777
public static final int HIDE_OUTWARD = 1;
7878
public static final int HIDE_INWARD = 2;
79+
public static final int HIDE_ESCAPE = 3;
7980

8081
static final int DEF_STYLE_RES = R.style.Widget_MaterialComponents_ProgressIndicator;
8182

@@ -636,6 +637,34 @@ public void setTrackCornerRadius(@Px int trackCornerRadius) {
636637
}
637638
}
638639

640+
/**
641+
* Returns the size of the gap between the indicator and track in pixels.
642+
*
643+
* @see #setIndicatorTrackGapSize(int)
644+
* @attr ref
645+
* com.google.android.material.progressindicator.R.styleable#BaseProgressIndicator_indicatorTrackGapSize
646+
*/
647+
@Px
648+
public int getIndicatorTrackGapSize() {
649+
return spec.indicatorTrackGapSize;
650+
}
651+
652+
/**
653+
* Sets the size of the gap between the indicator and track in pixels.
654+
*
655+
* @param indicatorTrackGapSize The new gap size in pixels.
656+
* @see #getIndicatorTrackGapSize()
657+
* @attr ref
658+
* com.google.android.material.progressindicator.R.styleable#BaseProgressIndicator_indicatorTrackGapSize
659+
*/
660+
public void setIndicatorTrackGapSize(@Px int indicatorTrackGapSize) {
661+
if (spec.indicatorTrackGapSize != indicatorTrackGapSize) {
662+
spec.indicatorTrackGapSize = indicatorTrackGapSize;
663+
spec.validateSpec();
664+
invalidate();
665+
}
666+
}
667+
639668
/**
640669
* Returns the show animation behavior used in this progress indicator.
641670
*
@@ -843,7 +872,7 @@ public void onAnimationEnd(Drawable drawable) {
843872

844873
/** @hide */
845874
@RestrictTo(Scope.LIBRARY_GROUP)
846-
@IntDef({HIDE_NONE, HIDE_OUTWARD, HIDE_INWARD})
875+
@IntDef({HIDE_NONE, HIDE_OUTWARD, HIDE_INWARD, HIDE_ESCAPE})
847876
@Retention(RetentionPolicy.SOURCE)
848877
public @interface HideAnimationBehavior {}
849878
}

lib/java/com/google/android/material/progressindicator/BaseProgressIndicatorSpec.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import android.util.AttributeSet;
2727
import android.util.TypedValue;
2828
import androidx.annotation.AttrRes;
29+
import androidx.annotation.CallSuper;
2930
import androidx.annotation.ColorInt;
3031
import androidx.annotation.NonNull;
3132
import androidx.annotation.Nullable;
@@ -57,8 +58,8 @@ public abstract class BaseProgressIndicatorSpec {
5758
@NonNull public int[] indicatorColors = new int[0];
5859

5960
/**
60-
* The color used in the track. If not defined, it will be set to the indicatorColors and
61-
* apply the first disable alpha value from the theme.
61+
* The color used in the track. If not defined, it will be set to the indicatorColors and apply
62+
* the first disable alpha value from the theme.
6263
*/
6364
@ColorInt public int trackColor;
6465

@@ -68,6 +69,9 @@ public abstract class BaseProgressIndicatorSpec {
6869
/** The animation behavior to hide the indicator and track. */
6970
@HideAnimationBehavior public int hideAnimationBehavior;
7071

72+
/** The size of the gap between the indicator and the rest of the track. */
73+
@Px public int indicatorTrackGapSize;
74+
7175
/**
7276
* Instantiates BaseProgressIndicatorSpec.
7377
*
@@ -103,6 +107,7 @@ protected BaseProgressIndicatorSpec(
103107
a.getInt(
104108
R.styleable.BaseProgressIndicator_hideAnimationBehavior,
105109
BaseProgressIndicator.HIDE_NONE);
110+
indicatorTrackGapSize = a.getDimensionPixelSize(R.styleable.BaseProgressIndicator_indicatorTrackGapSize, 0);
106111

107112
loadIndicatorColors(context, a);
108113
loadTrackColor(context, a);
@@ -178,5 +183,11 @@ public boolean isHideAnimationEnabled() {
178183
return hideAnimationBehavior != BaseProgressIndicator.HIDE_NONE;
179184
}
180185

181-
abstract void validateSpec();
186+
@CallSuper
187+
void validateSpec() {
188+
if (indicatorTrackGapSize < 0) {
189+
// Throws an exception if trying to use a negative gap size.
190+
throw new IllegalArgumentException("indicatorTrackGapSize must be >= 0.");
191+
}
192+
}
182193
}

lib/java/com/google/android/material/progressindicator/CircularDrawingDelegate.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
package com.google.android.material.progressindicator;
1717

18+
import static java.lang.Math.min;
19+
1820
import android.graphics.Canvas;
1921
import android.graphics.Paint;
2022
import android.graphics.Paint.Cap;
@@ -150,12 +152,24 @@ void fillIndicator(
150152
? (endFraction - startFraction) * 360 * arcDirectionFactor
151153
: (1 + endFraction - startFraction) * 360 * arcDirectionFactor;
152154

155+
// Draws the gaps if needed.
156+
if (spec.indicatorTrackGapSize > 0) {
157+
float gapSize =
158+
min(spec.getIndicatorTrackGapSizeDegree(), Math.abs(startDegree)) * arcDirectionFactor;
159+
// No need to draw if the indicator is shorter than gap.
160+
if (Math.abs(arcDegree) <= Math.abs(gapSize) * 2) {
161+
return;
162+
}
163+
startDegree += gapSize;
164+
arcDegree -= gapSize * 2;
165+
}
166+
153167
// Draws the indicator arc without rounded corners.
154168
RectF arcBound = new RectF(-adjustedRadius, -adjustedRadius, adjustedRadius, adjustedRadius);
155169
canvas.drawArc(arcBound, startDegree, arcDegree, false, paint);
156170

157171
// Draws rounded corners if needed.
158-
if (displayedCornerRadius > 0 && Math.abs(arcDegree) < 360) {
172+
if (displayedCornerRadius > 0 && Math.abs(arcDegree) > 0 && Math.abs(arcDegree) < 360) {
159173
paint.setStyle(Style.FILL);
160174
drawRoundedEnd(canvas, paint, displayedTrackThickness, displayedCornerRadius, startDegree);
161175
drawRoundedEnd(

lib/java/com/google/android/material/progressindicator/CircularProgressIndicatorSpec.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,13 @@ public CircularProgressIndicatorSpec(
105105
validateSpec();
106106
}
107107

108-
@Override
109-
void validateSpec() {}
108+
/** The size of the gap between the indicator and the rest of the track in degrees. */
109+
int getIndicatorTrackGapSizeDegree() {
110+
if (indicatorTrackGapSize == 0) {
111+
return 0;
112+
}
113+
int diameter = indicatorSize - (indicatorInset * 2) - trackThickness;
114+
double perimeter = Math.PI * diameter;
115+
return (int) Math.round(360 / (perimeter / (indicatorTrackGapSize + trackCornerRadius)));
116+
}
110117
}

lib/java/com/google/android/material/progressindicator/DeterminateDrawable.java

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818

1919
import android.content.Context;
2020
import android.graphics.Canvas;
21+
import android.graphics.Color;
2122
import android.graphics.Rect;
23+
import android.widget.ProgressBar;
2224
import androidx.annotation.NonNull;
2325
import androidx.dynamicanimation.animation.DynamicAnimation;
2426
import androidx.dynamicanimation.animation.FloatPropertyCompat;
@@ -74,7 +76,8 @@ public final class DeterminateDrawable<S extends BaseProgressIndicatorSpec>
7476
@NonNull
7577
public static DeterminateDrawable<LinearProgressIndicatorSpec> createLinearDrawable(
7678
@NonNull Context context, @NonNull LinearProgressIndicatorSpec spec) {
77-
return new DeterminateDrawable<>(context, /*baseSpec=*/ spec, new LinearDrawingDelegate(spec));
79+
return new DeterminateDrawable<>(
80+
context, /* baseSpec= */ spec, new LinearDrawingDelegate(spec));
7881
}
7982

8083
/**
@@ -88,7 +91,7 @@ public static DeterminateDrawable<LinearProgressIndicatorSpec> createLinearDrawa
8891
public static DeterminateDrawable<CircularProgressIndicatorSpec> createCircularDrawable(
8992
@NonNull Context context, @NonNull CircularProgressIndicatorSpec spec) {
9093
return new DeterminateDrawable<>(
91-
context, /*baseSpec=*/ spec, new CircularDrawingDelegate(spec));
94+
context, /* baseSpec= */ spec, new CircularDrawingDelegate(spec));
9295
}
9396

9497
public void addSpringAnimationEndListener(
@@ -197,12 +200,27 @@ public void draw(@NonNull Canvas canvas) {
197200
canvas.save();
198201
drawingDelegate.validateSpecAndAdjustCanvas(canvas, getBounds(), getGrowFraction());
199202

200-
// Draws the track.
201-
drawingDelegate.fillTrack(canvas, paint);
202-
// Draws the indicator.
203203
int indicatorColor =
204204
MaterialColors.compositeARGBWithAlpha(baseSpec.indicatorColors[0], getAlpha());
205-
drawingDelegate.fillIndicator(canvas, paint, 0f, getIndicatorFraction(), indicatorColor);
205+
if (baseSpec.indicatorTrackGapSize > 0) {
206+
int trackColor = MaterialColors.compositeARGBWithAlpha(baseSpec.trackColor, getAlpha());
207+
// Draws the full transparent track.
208+
baseSpec.trackColor = Color.TRANSPARENT;
209+
drawingDelegate.fillTrack(canvas, paint);
210+
baseSpec.trackColor = trackColor;
211+
// Draws the indicator and track.
212+
int gapSize = baseSpec.indicatorTrackGapSize;
213+
// TODO: workaround to maintain pixel-perfect compatibility with drawing logic
214+
// not using indicatorTrackGapSize.
215+
// See https://github.com/material-components/material-components-android/commit/0ce6ae4.
216+
baseSpec.indicatorTrackGapSize = 0;
217+
drawingDelegate.fillIndicator(canvas, paint, 0f, getIndicatorFraction(), indicatorColor);
218+
baseSpec.indicatorTrackGapSize = gapSize;
219+
drawingDelegate.fillIndicator(canvas, paint, getIndicatorFraction(), 1f, trackColor);
220+
} else {
221+
drawingDelegate.fillTrack(canvas, paint);
222+
drawingDelegate.fillIndicator(canvas, paint, 0f, getIndicatorFraction(), indicatorColor);
223+
}
206224
canvas.restore();
207225
}
208226

lib/java/com/google/android/material/progressindicator/IndeterminateDrawable.java

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,17 @@
1515
*/
1616
package com.google.android.material.progressindicator;
1717

18+
import static com.google.android.material.progressindicator.LinearProgressIndicator.INDETERMINATE_ANIMATION_TYPE_CONTIGUOUS;
19+
1820
import android.animation.ObjectAnimator;
1921
import android.content.Context;
2022
import android.graphics.Canvas;
23+
import android.graphics.Color;
2124
import android.graphics.Rect;
2225
import android.os.Build.VERSION;
2326
import android.os.Build.VERSION_CODES;
2427
import androidx.annotation.NonNull;
28+
import com.google.android.material.color.MaterialColors;
2529

2630
/** This class draws the graphics for indeterminate mode. */
2731
public final class IndeterminateDrawable<S extends BaseProgressIndicatorSpec>
@@ -55,10 +59,9 @@ public static IndeterminateDrawable<LinearProgressIndicatorSpec> createLinearDra
5559
@NonNull Context context, @NonNull LinearProgressIndicatorSpec spec) {
5660
return new IndeterminateDrawable<>(
5761
context,
58-
/*baseSpec=*/ spec,
62+
/* baseSpec= */ spec,
5963
new LinearDrawingDelegate(spec),
60-
spec.indeterminateAnimationType
61-
== LinearProgressIndicator.INDETERMINATE_ANIMATION_TYPE_CONTIGUOUS
64+
spec.indeterminateAnimationType == INDETERMINATE_ANIMATION_TYPE_CONTIGUOUS
6265
? new LinearIndeterminateContiguousAnimatorDelegate(spec)
6366
: new LinearIndeterminateDisjointAnimatorDelegate(context, spec));
6467
}
@@ -75,7 +78,7 @@ public static IndeterminateDrawable<CircularProgressIndicatorSpec> createCircula
7578
@NonNull Context context, @NonNull CircularProgressIndicatorSpec spec) {
7679
return new IndeterminateDrawable<>(
7780
context,
78-
/*baseSpec=*/ spec,
81+
/* baseSpec= */ spec,
7982
new CircularDrawingDelegate(spec),
8083
new CircularIndeterminateAnimatorDelegate(spec));
8184
}
@@ -140,22 +143,68 @@ public void draw(@NonNull Canvas canvas) {
140143
canvas.save();
141144
drawingDelegate.validateSpecAndAdjustCanvas(canvas, getBounds(), getGrowFraction());
142145

143-
// Draws the track.
144-
drawingDelegate.fillTrack(canvas, paint);
145-
// Draws the indicators.
146+
if (baseSpec.indicatorTrackGapSize > 0) {
147+
if (drawingDelegate instanceof LinearDrawingDelegate) {
148+
((LinearProgressIndicatorSpec) drawingDelegate.spec).trackStopIndicatorSize = 0;
149+
} else if (drawingDelegate instanceof CircularDrawingDelegate) {
150+
// TODO: workaround preventing exiting the indicatorTrackGapSize > 0 logic while keeping
151+
// the animation smooth.
152+
baseSpec.indicatorTrackGapSize = 1;
153+
}
154+
155+
// Draws the transparent track.
156+
int trackColor = baseSpec.trackColor;
157+
baseSpec.trackColor = Color.TRANSPARENT;
158+
drawingDelegate.fillTrack(canvas, paint);
159+
baseSpec.trackColor = trackColor;
160+
} else {
161+
drawingDelegate.fillTrack(canvas, paint);
162+
}
163+
146164
for (int segmentIndex = 0;
147165
segmentIndex < animatorDelegate.segmentColors.length;
148166
segmentIndex++) {
167+
168+
// Draws the actual indicators.
149169
drawingDelegate.fillIndicator(
150170
canvas,
151171
paint,
152172
animatorDelegate.segmentPositions[2 * segmentIndex],
153173
animatorDelegate.segmentPositions[2 * segmentIndex + 1],
154174
animatorDelegate.segmentColors[segmentIndex]);
175+
176+
if (drawingDelegate instanceof LinearDrawingDelegate && baseSpec.indicatorTrackGapSize > 0) {
177+
// Draws the track using fake indicators around the current indicator.
178+
drawTrackIndicators(canvas, segmentIndex);
179+
}
155180
}
181+
156182
canvas.restore();
157183
}
158184

185+
private void drawTrackIndicators(@NonNull Canvas canvas, int segmentIndex) {
186+
int trackColorWithAlpha =
187+
MaterialColors.compositeARGBWithAlpha(baseSpec.trackColor, getAlpha());
188+
float previousSegmentEndPosition =
189+
segmentIndex == 0 ? 0f : animatorDelegate.segmentPositions[2 * segmentIndex - 1];
190+
// Draws the fake indicators as the track to the left of the current indicator.
191+
drawingDelegate.fillIndicator(
192+
canvas,
193+
paint,
194+
previousSegmentEndPosition,
195+
animatorDelegate.segmentPositions[2 * segmentIndex],
196+
trackColorWithAlpha);
197+
if (segmentIndex == animatorDelegate.segmentColors.length - 1) {
198+
// Draws the fake indicator as the track to the right of the last indicator.
199+
drawingDelegate.fillIndicator(
200+
canvas,
201+
paint,
202+
animatorDelegate.segmentPositions[2 * segmentIndex + 1],
203+
1f,
204+
trackColorWithAlpha);
205+
}
206+
}
207+
159208
// ******************* Setter and getter *******************
160209

161210
@NonNull

0 commit comments

Comments
 (0)