Skip to content

Commit 627d141

Browse files
pekingmepaulfthomas
authored andcommitted
[ProgressIndicator] Added ramping animation to wave amplitude for both Linear and Circular types.
PiperOrigin-RevId: 620312414
1 parent 0f47e78 commit 627d141

File tree

4 files changed

+94
-6
lines changed

4 files changed

+94
-6
lines changed

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

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ void fillIndicator(
173173
color,
174174
activeIndicator.gapSize,
175175
activeIndicator.gapSize,
176+
activeIndicator.amplitudeFraction,
176177
activeIndicator.phaseFraction,
177178
/* shouldDrawActiveIndicator= */ true);
178179
}
@@ -195,6 +196,7 @@ void fillTrack(
195196
color,
196197
gapSize,
197198
gapSize,
199+
/* amplitudeFraction= */ 0f,
198200
/* phaseFraction= */ 0f,
199201
/* shouldDrawActiveIndicator= */ false);
200202
}
@@ -210,6 +212,7 @@ void fillTrack(
210212
* @param paintColor The color used to draw the indicator.
211213
* @param startGapSize The gap size applied to the start (rotating behind) of the drawing part.
212214
* @param endGapSize The gap size applied to the end (rotating ahead) of the drawing part.
215+
* @param amplitudeFraction The fraction [0, 1] of amplitude applied to the part.
213216
* @param phaseFraction The fraction [0, 1] of initial phase in one cycle.
214217
* @param shouldDrawActiveIndicator Whether this part should be drawn as an active indicator.
215218
*/
@@ -221,6 +224,7 @@ private void drawArc(
221224
@ColorInt int paintColor,
222225
@Px int startGapSize,
223226
@Px int endGapSize,
227+
float amplitudeFraction,
224228
float phaseFraction,
225229
boolean shouldDrawActiveIndicator) {
226230
float arcFraction =
@@ -242,6 +246,7 @@ private void drawArc(
242246
paintColor,
243247
startGapSize,
244248
/* endGapSize= */ 0,
249+
amplitudeFraction,
245250
phaseFraction,
246251
shouldDrawActiveIndicator);
247252
drawArc(
@@ -252,6 +257,7 @@ private void drawArc(
252257
paintColor,
253258
/* startGapSize= */ 0,
254259
endGapSize,
260+
amplitudeFraction,
255261
phaseFraction,
256262
shouldDrawActiveIndicator);
257263
return;
@@ -282,7 +288,8 @@ private void drawArc(
282288
return;
283289
}
284290

285-
boolean shouldDrawWavyPath = spec.hasWavyEffect() && shouldDrawActiveIndicator;
291+
boolean shouldDrawWavyPath =
292+
spec.hasWavyEffect() && shouldDrawActiveIndicator && amplitudeFraction > 0f;
286293

287294
// Sets up the paint.
288295
paint.setAntiAlias(true);
@@ -330,6 +337,7 @@ private void drawArc(
330337
displayedActivePath,
331338
startDegreeWithoutCorners / 360,
332339
arcDegreeWithoutCorners / 360,
340+
amplitudeFraction,
333341
phaseFraction);
334342
canvas.drawPath(displayedActivePath, paint);
335343
}
@@ -420,7 +428,7 @@ void invalidateCachedPaths() {
420428
cachedActivePath.transform(transform);
421429
if (spec.hasWavyEffect()) {
422430
activePathMeasure.setPath(cachedActivePath, false);
423-
createWavyPath(activePathMeasure, cachedActivePath, displayedAmplitude);
431+
createWavyPath(activePathMeasure, cachedActivePath, cachedAmplitude);
424432
}
425433
activePathMeasure.setPath(cachedActivePath, false);
426434
}
@@ -483,10 +491,12 @@ private Pair<PathPoint, PathPoint> getDisplayedPath(
483491
@NonNull Path displayedPath,
484492
float start,
485493
float span,
494+
float amplitudeFraction,
486495
float phaseFraction) {
487496
if (adjustedRadius != cachedRadius
488-
|| (pathMeasure == activePathMeasure && displayedAmplitude != cachedAmplitude)) {
489-
cachedAmplitude = displayedAmplitude;
497+
|| (pathMeasure == activePathMeasure
498+
&& displayedAmplitude * amplitudeFraction != cachedAmplitude)) {
499+
cachedAmplitude = displayedAmplitude * amplitudeFraction;
490500
cachedRadius = adjustedRadius;
491501
invalidateCachedPaths();
492502
}

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

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616

1717
package com.google.android.material.progressindicator;
1818

19+
import com.google.android.material.R;
20+
21+
import android.animation.TimeInterpolator;
1922
import android.animation.ValueAnimator;
2023
import android.content.Context;
2124
import android.graphics.Canvas;
@@ -28,20 +31,28 @@
2831
import androidx.dynamicanimation.animation.FloatPropertyCompat;
2932
import androidx.dynamicanimation.animation.SpringAnimation;
3033
import androidx.dynamicanimation.animation.SpringForce;
34+
import com.google.android.material.animation.AnimationUtils;
35+
import com.google.android.material.motion.MotionUtils;
3136
import com.google.android.material.progressindicator.DrawingDelegate.ActiveIndicator;
3237

3338
/** This class draws the graphics for determinate mode. */
3439
public final class DeterminateDrawable<S extends BaseProgressIndicatorSpec>
3540
extends DrawableWithAnimatedVisibilityChange {
3641
// Constants for drawing progress.
3742
private static final int MAX_DRAWABLE_LEVEL = 10000;
43+
// Constants for amplitude animation.
44+
private static final float FULL_AMPLITUDE_FRACTION_MIN = 0.1f;
45+
private static final float FULL_AMPLITUDE_FRACTION_MAX = 0.9f;
46+
3847
// The constant for spring force stiffness.
3948
private static final float SPRING_FORCE_STIFFNESS = SpringForce.STIFFNESS_VERY_LOW;
4049
// If the progress is less than 1%, the gap will be proportional to the progress. So that, it
4150
// draws a full track at 0%.
4251
static final float GAP_RAMP_DOWN_THRESHOLD = 0.01f;
4352
// The duration of repeated initial phase animation in ms. It can be any positive values.
4453
private static final int PHASE_ANIMATION_DURATION_MS = 1000;
54+
// The duration of amplitude ramping animation in ms.
55+
private static final int AMPLITUDE_ANIMATION_DURATION_MS = 500;
4556

4657
// Drawing delegate object.
4758
private DrawingDelegate<S> drawingDelegate;
@@ -51,10 +62,16 @@ public final class DeterminateDrawable<S extends BaseProgressIndicatorSpec>
5162
private final SpringAnimation springAnimation;
5263
// Active indicator for the progress.
5364
private final ActiveIndicator activeIndicator;
65+
// Fraction of displayed amplitude.
66+
private float targetAmplitudeFraction;
5467
// Whether to skip the spring animation on level change event.
5568
private boolean skipAnimationOnLevelChange = false;
5669

5770
@NonNull private final ValueAnimator phaseAnimator;
71+
@NonNull private final ValueAnimator amplitudeAnimator;
72+
private TimeInterpolator amplitudeInterpolator;
73+
@NonNull private final TimeInterpolator amplitudeOnInterpolator;
74+
@NonNull private final TimeInterpolator amplitudeOffInterpolator;
5875

5976
DeterminateDrawable(
6077
@NonNull Context context,
@@ -87,6 +104,25 @@ public final class DeterminateDrawable<S extends BaseProgressIndicatorSpec>
87104
});
88105
phaseAnimator.start();
89106

107+
// Initializes a linear animator to turn on/off wave amplitude.
108+
amplitudeOnInterpolator =
109+
MotionUtils.resolveThemeInterpolator(
110+
context, R.attr.motionEasingStandardInterpolator, AnimationUtils.LINEAR_INTERPOLATOR);
111+
amplitudeOffInterpolator =
112+
MotionUtils.resolveThemeInterpolator(
113+
context,
114+
R.attr.motionEasingEmphasizedAccelerateInterpolator,
115+
AnimationUtils.LINEAR_INTERPOLATOR);
116+
amplitudeAnimator = new ValueAnimator();
117+
amplitudeAnimator.setDuration(AMPLITUDE_ANIMATION_DURATION_MS);
118+
amplitudeAnimator.setFloatValues(0, 1);
119+
amplitudeAnimator.setInterpolator(null);
120+
amplitudeAnimator.addUpdateListener(
121+
animation -> {
122+
activeIndicator.amplitudeFraction =
123+
amplitudeInterpolator.getInterpolation(amplitudeAnimator.getAnimatedFraction());
124+
});
125+
90126
setGrowFraction(1f);
91127
}
92128

@@ -209,9 +245,11 @@ public void jumpToCurrentState() {
209245
*/
210246
@Override
211247
protected boolean onLevelChange(int level) {
248+
float nextAmplitudeFraction = getAmplitudeFractionFromLevel(level);
212249
if (skipAnimationOnLevelChange) {
213250
springAnimation.skipToEnd();
214251
setIndicatorFraction((float) level / MAX_DRAWABLE_LEVEL);
252+
setAmplitudeFraction(nextAmplitudeFraction);
215253
} else {
216254
springAnimation.setStartValue(getIndicatorFraction() * MAX_DRAWABLE_LEVEL);
217255
springAnimation.animateToFinalPosition(level);
@@ -240,6 +278,30 @@ void setLevelByFraction(float fraction) {
240278
setLevel((int) (MAX_DRAWABLE_LEVEL * fraction));
241279
}
242280

281+
private float getAmplitudeFractionFromLevel(int level) {
282+
return level >= FULL_AMPLITUDE_FRACTION_MIN * MAX_DRAWABLE_LEVEL
283+
&& level <= FULL_AMPLITUDE_FRACTION_MAX * MAX_DRAWABLE_LEVEL
284+
? 1f
285+
: 0f;
286+
}
287+
288+
private void maybeStartAmplitudeAnimator(int level) {
289+
float newAmplitudeFraction = getAmplitudeFractionFromLevel(level);
290+
if (newAmplitudeFraction != targetAmplitudeFraction) {
291+
if (amplitudeAnimator.isRunning()) {
292+
amplitudeAnimator.cancel();
293+
}
294+
targetAmplitudeFraction = newAmplitudeFraction;
295+
if (targetAmplitudeFraction == 1f) {
296+
amplitudeInterpolator = amplitudeOnInterpolator;
297+
amplitudeAnimator.start();
298+
} else {
299+
amplitudeInterpolator = amplitudeOffInterpolator;
300+
amplitudeAnimator.reverse();
301+
}
302+
}
303+
}
304+
243305
// ******************* Drawing methods *******************
244306

245307
@Override
@@ -306,6 +368,11 @@ private void setIndicatorFraction(float indicatorFraction) {
306368
invalidateSelf();
307369
}
308370

371+
private void setAmplitudeFraction(float amplitudeFraction) {
372+
this.activeIndicator.amplitudeFraction = amplitudeFraction;
373+
invalidateSelf();
374+
}
375+
309376
@NonNull
310377
DrawingDelegate<S> getDrawingDelegate() {
311378
return drawingDelegate;
@@ -327,6 +394,7 @@ public float getValue(DeterminateDrawable<?> drawable) {
327394
@Override
328395
public void setValue(DeterminateDrawable<?> drawable, float value) {
329396
drawable.setIndicatorFraction(value / MAX_DRAWABLE_LEVEL);
397+
drawable.maybeStartAmplitudeAnimator((int) value);
330398
}
331399
};
332400
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,10 @@ protected static class ActiveIndicator {
169169
// to each other. Gaps are needed in this case.
170170
@Px int gapSize;
171171

172+
// The fraction [0, 1] of the amplitude on indicator.
173+
@FloatRange(from = 0.0, to = 1.0)
174+
float amplitudeFraction = 1;
175+
172176
// The fraction [0, 1] of the initial phase [0, 2 * PI] on indicator.
173177
@FloatRange(from = 0.0, to = 1.0)
174178
float phaseFraction;

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ void fillIndicator(
158158
color,
159159
activeIndicator.gapSize,
160160
activeIndicator.gapSize,
161+
activeIndicator.amplitudeFraction,
161162
activeIndicator.phaseFraction,
162163
/* drawingActiveIndicator= */ true);
163164
}
@@ -180,6 +181,7 @@ void fillTrack(
180181
color,
181182
gapSize,
182183
gapSize,
184+
/* amplitudeFraction= */ 0f,
183185
/* phaseFraction= */ 0f,
184186
/* drawingActiveIndicator= */ false);
185187
}
@@ -194,6 +196,7 @@ void fillTrack(
194196
* @param paintColor The color used to draw the indicator.
195197
* @param startGapSize The gap size applied to the start (left) of the drawing part.
196198
* @param endGapSize The gap size applied to the end (right) of the drawing part.
199+
* @param amplitudeFraction The fraction [0, 1] of amplitude applied to the part.
197200
* @param phaseFraction The fraction [0, 1] of initial phase in one cycle.
198201
* @param drawingActiveIndicator Whether this part should be drawn as an active indicator.
199202
*/
@@ -205,6 +208,7 @@ private void drawLine(
205208
@ColorInt int paintColor,
206209
@Px int startGapSize,
207210
@Px int endGapSize,
211+
float amplitudeFraction,
208212
float phaseFraction,
209213
boolean drawingActiveIndicator) {
210214
startFraction = clamp(startFraction, 0f, 1f);
@@ -230,7 +234,7 @@ private void drawLine(
230234
// Adjusts start/end X so the progress indicator will start from 0 when startFraction == 0.
231235
float originX = -trackLength / 2;
232236

233-
boolean drawWavyPath = spec.hasWavyEffect() && drawingActiveIndicator;
237+
boolean drawWavyPath = spec.hasWavyEffect() && drawingActiveIndicator && amplitudeFraction > 0f;
234238

235239
// No need to draw on track if start and end are out of visible range.
236240
if (startPx <= endPx) {
@@ -275,6 +279,7 @@ private void drawLine(
275279
displayedActivePath,
276280
startBlockCenterX / trackLength,
277281
endBlockCenterX / trackLength,
282+
amplitudeFraction,
278283
phaseFraction);
279284
canvas.drawPath(displayedActivePath, paint);
280285
}
@@ -382,6 +387,7 @@ private Pair<PathPoint, PathPoint> getDisplayedPath(
382387
@NonNull Path displayedPath,
383388
float start,
384389
float end,
390+
float amplitudeFraction,
385391
float phaseFraction) {
386392
displayedPath.rewind();
387393
float resultTranslationX = -trackLength / 2;
@@ -407,7 +413,7 @@ private Pair<PathPoint, PathPoint> getDisplayedPath(
407413
startPoint.translate(resultTranslationX, 0);
408414
endPoint.translate(resultTranslationX, 0);
409415
if (spec.hasWavyEffect()) {
410-
float scaleY = displayedAmplitude;
416+
float scaleY = displayedAmplitude * amplitudeFraction;
411417
transform.postScale(1, scaleY);
412418
startPoint.scale(1, scaleY);
413419
endPoint.scale(1, scaleY);

0 commit comments

Comments
 (0)