Skip to content

Commit ac82767

Browse files
pekingmedsn5ft
authored andcommitted
[LoadingIndicator] Added showDelay and minHideDelay attribute.
Resolves #4799 PiperOrigin-RevId: 793449299
1 parent 90a1f22 commit ac82767

File tree

3 files changed

+158
-7
lines changed

3 files changed

+158
-7
lines changed

docs/components/LoadingIndicator.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,15 @@ indicator.
5555

5656
#### Attributes
5757

58-
Element | Attribute | Related method(s) | Default value
59-
-------------------- | --------------------- | --------------------------------------------- | -------------
60-
**Indicator color** | `app:indicatorColor` | `setIndicatorColor`</br>`getIndicatorColor` | `colorPrimary`
61-
**Container color** | `app:containerColor` | `setContainerColor`</br>`getContainerColor` | `transparent`
62-
**Indicator size** | `app:indicatorSize` | `setIndicatorSize`</br>`getIndicatorSize` | 38dp
63-
**Container width** | `app:containerWidth` | `setContainerWidth`</br>`getContainerWidth` | 48dp
64-
**Container height** | `app:containerHeight` | `setContainerHeight`</br>`getContainerHeight` | 48dp
58+
Element | Attribute | Related method(s) | Default value
59+
----------------------------- | --------------------- | --------------------------------------------- | -------------
60+
**Indicator color** | `app:indicatorColor` | `setIndicatorColor`</br>`getIndicatorColor` | `colorPrimary`
61+
**Container color** | `app:containerColor` | `setContainerColor`</br>`getContainerColor` | `transparent`
62+
**Indicator size** | `app:indicatorSize` | `setIndicatorSize`</br>`getIndicatorSize` | 38dp
63+
**Container width** | `app:containerWidth` | `setContainerWidth`</br>`getContainerWidth` | 48dp
64+
**Container height** | `app:containerHeight` | `setContainerHeight`</br>`getContainerHeight` | 48dp
65+
**Delay (in ms) to show** | `app:showDelay` | N/A | 0
66+
**Min delay (in ms) to hide** | `app:minHideDelay` | N/A | 0
6567

6668
#### Styles
6769

lib/java/com/google/android/material/loadingindicator/LoadingIndicator.java

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@
2626
import static java.lang.Math.min;
2727

2828
import android.content.Context;
29+
import android.content.res.TypedArray;
2930
import android.graphics.Canvas;
3031
import android.graphics.drawable.Drawable;
32+
import android.os.SystemClock;
3133
import android.util.AttributeSet;
3234
import android.view.View;
3335
import android.view.ViewGroup;
@@ -42,16 +44,41 @@
4244
import androidx.annotation.RestrictTo.Scope;
4345
import androidx.annotation.VisibleForTesting;
4446
import com.google.android.material.color.MaterialColors;
47+
import com.google.android.material.internal.ThemeEnforcement;
4548
import com.google.android.material.progressindicator.AnimatorDurationScaleProvider;
4649
import java.util.Arrays;
4750

4851
/** This class implements the loading indicators. */
4952
public final class LoadingIndicator extends View implements Drawable.Callback {
5053
static final int DEF_STYLE_RES = R.style.Widget_Material3_LoadingIndicator;
5154

55+
/**
56+
* The maximum time, in milliseconds, that the requested hide action is allowed to wait once
57+
* {@link #show()} is called.
58+
*/
59+
static final int MAX_HIDE_DELAY = 1000;
60+
5261
@NonNull private final LoadingIndicatorDrawable drawable;
5362
@NonNull private final LoadingIndicatorSpec specs;
5463

64+
/**
65+
* The time, in milliseconds, that the loading indicator will wait to show once the component
66+
* becomes visible. If set to zero (as default) or negative values, the show action will start
67+
* immediately.
68+
*/
69+
private final int showDelay;
70+
71+
/**
72+
* The minimum time, in milliseconds, that the requested hide action will wait to start once
73+
* {@link #show()} is called. If set to zero or negative values, the requested hide action will
74+
* start as soon as {@link #hide()} is called. This value is capped to {@link #MAX_HIDE_DELAY}.
75+
*
76+
* @see #showDelay
77+
*/
78+
private final int minHideDelay;
79+
80+
private long lastShowStartTime = -1L;
81+
5582
public LoadingIndicator(@NonNull Context context) {
5683
this(context, null);
5784
}
@@ -74,9 +101,81 @@ public LoadingIndicator(
74101
drawable.setCallback(this);
75102

76103
specs = drawable.getDrawingDelegate().specs;
104+
105+
TypedArray a =
106+
ThemeEnforcement.obtainStyledAttributes(
107+
context, attrs, R.styleable.LoadingIndicator, defStyleAttr, DEF_STYLE_RES);
108+
showDelay = a.getInt(R.styleable.LoadingIndicator_showDelay, -1);
109+
int minHideDelayUncapped = a.getInt(R.styleable.LoadingIndicator_minHideDelay, -1);
110+
minHideDelay = min(minHideDelayUncapped, MAX_HIDE_DELAY);
111+
a.recycle();
112+
77113
setAnimatorDurationScaleProvider(new AnimatorDurationScaleProvider());
78114
}
79115

116+
/**
117+
* Shows the loading indicator. If {@code showDelay} has been set to a positive value, wait until
118+
* the delay elapsed before starting the show action. Otherwise start showing immediately.
119+
*/
120+
public void show() {
121+
if (showDelay > 0) {
122+
removeCallbacks(delayedShow);
123+
postDelayed(delayedShow, showDelay);
124+
} else {
125+
delayedShow.run();
126+
}
127+
}
128+
129+
/**
130+
* Sets the visibility to {@code VISIBLE}. If this changes the visibility it will invoke {@code
131+
* onVisibilityChanged} and handle the visibility with animation of the drawables.
132+
*
133+
* @see #onVisibilityChanged(View, int)
134+
*/
135+
private void internalShow() {
136+
if (minHideDelay > 0) {
137+
// The hide delay is positive, saves the time of starting show action.
138+
lastShowStartTime = SystemClock.uptimeMillis();
139+
}
140+
setVisibility(VISIBLE);
141+
}
142+
143+
/**
144+
* Hides the loading indicator. If {@code minHideDelay} has been set to a positive value, wait
145+
* until the delay elapsed before starting the hide action. Otherwise start hiding immediately.
146+
*/
147+
public void hide() {
148+
if (getVisibility() != VISIBLE) {
149+
// No need to hide, as the component is still invisible.
150+
removeCallbacks(delayedShow);
151+
return;
152+
}
153+
154+
removeCallbacks(delayedHide);
155+
long timeElapsedSinceShowStart = SystemClock.uptimeMillis() - lastShowStartTime;
156+
boolean enoughTimeElapsed = timeElapsedSinceShowStart >= minHideDelay;
157+
if (enoughTimeElapsed) {
158+
delayedHide.run();
159+
return;
160+
}
161+
postDelayed(delayedHide, /* delayMillis= */ minHideDelay - timeElapsedSinceShowStart);
162+
}
163+
164+
/**
165+
* If the component uses {@link DrawableWithAnimatedVisibilityChange} and needs to be hidden with
166+
* animation, it will trigger the drawable to start the hide animation. Otherwise, it will
167+
* directly set the visibility to {@code INVISIBLE}.
168+
*
169+
* @see #hide()
170+
*/
171+
private void internalHide() {
172+
getDrawable().setVisible(/* visible= */ false, /* restart= */ false, /* animate= */ true);
173+
174+
if (!getDrawable().isVisible()) {
175+
setVisibility(INVISIBLE);
176+
}
177+
}
178+
80179
@Override
81180
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
82181
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
@@ -142,6 +241,14 @@ protected void onWindowVisibilityChanged(int visibility) {
142241
visibleToUser(), /* restart= */ false, /* animate= */ visibility == VISIBLE);
143242
}
144243

244+
@Override
245+
protected void onAttachedToWindow() {
246+
super.onAttachedToWindow();
247+
if (visibleToUser()) {
248+
internalShow();
249+
}
250+
}
251+
145252
@Override
146253
public void invalidateDrawable(@NonNull Drawable drawable) {
147254
invalidate();
@@ -318,4 +425,33 @@ public void setAnimatorDurationScaleProvider(
318425
@NonNull AnimatorDurationScaleProvider animatorDurationScaleProvider) {
319426
drawable.animatorDurationScaleProvider = animatorDurationScaleProvider;
320427
}
428+
429+
// ************************ In-place defined parameters ****************************
430+
431+
/**
432+
* The runnable, which executes the start action. This is used to schedule delayed show actions.
433+
*
434+
* @see #show()
435+
*/
436+
private final Runnable delayedShow =
437+
new Runnable() {
438+
@Override
439+
public void run() {
440+
internalShow();
441+
}
442+
};
443+
444+
/**
445+
* The runnable, which executes the hide action. This is used to schedule delayed hide actions.
446+
*
447+
* @see #hide()
448+
*/
449+
private final Runnable delayedHide =
450+
new Runnable() {
451+
@Override
452+
public void run() {
453+
internalHide();
454+
lastShowStartTime = -1L;
455+
}
456+
};
321457
}

lib/java/com/google/android/material/loadingindicator/res/values/attrs.xml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,19 @@
2121
<attr name="indicatorSize" />
2222
<attr name="containerWidth" format="dimension" />
2323
<attr name="containerHeight" format="dimension" />
24+
<!--
25+
The time, in milliseconds, that the loading indicator will wait to show
26+
once show() is called. If set to zero or negative values (-1 as default),
27+
the show action will start immediately.
28+
-->
29+
<attr name="showDelay" />
30+
<!--
31+
The minimum time, in milliseconds, that the requested hide action will
32+
wait to start once show action is started. If set to zero or negative
33+
values (-1 as default), the requested hide action will start immediately.
34+
This value is capped to a limit defined in LoadingIndicator class.
35+
-->
36+
<attr name="minHideDelay" />
2437
</declare-styleable>
2538

2639
<attr name="loadingIndicatorStyle" format="reference"/>

0 commit comments

Comments
 (0)