Skip to content

Commit 5c09cca

Browse files
authored
🐛 Setup color tween for RefreshIndicator in a better way (flutter#134492)
Fixes flutter#134489.
1 parent 9fa09ea commit 5c09cca

File tree

2 files changed

+89
-19
lines changed

2 files changed

+89
-19
lines changed

packages/flutter/lib/src/material/refresh_indicator.dart

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ class RefreshIndicatorState extends State<RefreshIndicator> with TickerProviderS
275275
late Future<void> _pendingRefreshFuture;
276276
bool? _isIndicatorAtTop;
277277
double? _dragOffset;
278+
late Color _effectiveValueColor = widget.color ?? Theme.of(context).colorScheme.primary;
278279

279280
static final Animatable<double> _threeQuarterTween = Tween<double>(begin: 0.0, end: 0.75);
280281
static final Animatable<double> _kDragSizeFactorLimitTween = Tween<double>(begin: 0.0, end: _kDragSizeFactorLimit);
@@ -293,31 +294,15 @@ class RefreshIndicatorState extends State<RefreshIndicator> with TickerProviderS
293294

294295
@override
295296
void didChangeDependencies() {
296-
final ThemeData theme = Theme.of(context);
297-
_valueColor = _positionController.drive(
298-
ColorTween(
299-
begin: (widget.color ?? theme.colorScheme.primary).withOpacity(0.0),
300-
end: (widget.color ?? theme.colorScheme.primary).withOpacity(1.0),
301-
).chain(CurveTween(
302-
curve: const Interval(0.0, 1.0 / _kDragSizeFactorLimit),
303-
)),
304-
);
297+
_setupColorTween();
305298
super.didChangeDependencies();
306299
}
307300

308301
@override
309302
void didUpdateWidget(covariant RefreshIndicator oldWidget) {
310303
super.didUpdateWidget(oldWidget);
311304
if (oldWidget.color != widget.color) {
312-
final ThemeData theme = Theme.of(context);
313-
_valueColor = _positionController.drive(
314-
ColorTween(
315-
begin: (widget.color ?? theme.colorScheme.primary).withOpacity(0.0),
316-
end: (widget.color ?? theme.colorScheme.primary).withOpacity(1.0),
317-
).chain(CurveTween(
318-
curve: const Interval(0.0, 1.0 / _kDragSizeFactorLimit),
319-
)),
320-
);
305+
_setupColorTween();
321306
}
322307
}
323308

@@ -328,6 +313,28 @@ class RefreshIndicatorState extends State<RefreshIndicator> with TickerProviderS
328313
super.dispose();
329314
}
330315

316+
void _setupColorTween() {
317+
// Reset the current value color.
318+
_effectiveValueColor = widget.color ?? Theme.of(context).colorScheme.primary;
319+
final Color color = _effectiveValueColor;
320+
if (color.alpha == 0x00) {
321+
// Set an always stopped animation instead of a driven tween.
322+
_valueColor = AlwaysStoppedAnimation<Color>(color);
323+
} else {
324+
// Respect the alpha of the given color.
325+
_valueColor = _positionController.drive(
326+
ColorTween(
327+
begin: color.withAlpha(0),
328+
end: color.withAlpha(color.alpha),
329+
).chain(
330+
CurveTween(
331+
curve: const Interval(0.0, 1.0 / _kDragSizeFactorLimit),
332+
),
333+
),
334+
);
335+
}
336+
}
337+
331338
bool _shouldStart(ScrollNotification notification) {
332339
// If the notification.dragDetails is null, this scroll is not triggered by
333340
// user dragging. It may be a result of ScrollController.jumpTo or ballistic scroll.
@@ -448,7 +455,7 @@ class RefreshIndicatorState extends State<RefreshIndicator> with TickerProviderS
448455
newValue = math.max(newValue, 1.0 / _kDragSizeFactorLimit);
449456
}
450457
_positionController.value = clampDouble(newValue, 0.0, 1.0); // this triggers various rebuilds
451-
if (_mode == _RefreshIndicatorMode.drag && _valueColor.value!.alpha == 0xFF) {
458+
if (_mode == _RefreshIndicatorMode.drag && _valueColor.value!.alpha == _effectiveValueColor.alpha) {
452459
_mode = _RefreshIndicatorMode.armed;
453460
}
454461
}

packages/flutter/test/material/refresh_indicator_test.dart

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1070,4 +1070,67 @@ void main() {
10701070
expect(refreshCalled, true);
10711071
expect(stretchAccepted, false);
10721072
});
1073+
1074+
testWidgetsWithLeakTracking('RefreshIndicator manipulates value color opacity correctly', (WidgetTester tester) async {
1075+
final List<Color> colors = <Color>[
1076+
Colors.black,
1077+
Colors.black54,
1078+
Colors.white,
1079+
Colors.white54,
1080+
Colors.transparent,
1081+
];
1082+
const List<double> positions = <double>[50.0, 100.0, 150.0];
1083+
1084+
Future<void> testColor(Color color) async {
1085+
final AnimationController positionController = AnimationController(vsync: const TestVSync());
1086+
// Correspond to [_setupColorTween].
1087+
final Animation<Color?> valueColorAnimation = positionController.drive(
1088+
ColorTween(
1089+
begin: color.withAlpha(0),
1090+
end: color.withAlpha(color.alpha),
1091+
).chain(
1092+
CurveTween(
1093+
// Correspond to [_kDragSizeFactorLimit].
1094+
curve: const Interval(0.0, 1.0 / 1.5),
1095+
),
1096+
),
1097+
);
1098+
await tester.pumpWidget(
1099+
MaterialApp(
1100+
home: RefreshIndicator(
1101+
onRefresh: refresh,
1102+
color: color,
1103+
child: ListView(
1104+
physics: const AlwaysScrollableScrollPhysics(),
1105+
children: const <Widget>[Text('X')],
1106+
),
1107+
),
1108+
),
1109+
);
1110+
1111+
RefreshProgressIndicator getIndicator() {
1112+
return tester.widget<RefreshProgressIndicator>(
1113+
find.byType(RefreshProgressIndicator),
1114+
);
1115+
}
1116+
1117+
// Correspond to [_kDragContainerExtentPercentage].
1118+
final double maxPosition = tester.view.physicalSize.height / tester.view.devicePixelRatio * 0.25;
1119+
for (final double position in positions) {
1120+
await tester.fling(find.text('X'), Offset(0.0, position), 1.0);
1121+
await tester.pump();
1122+
positionController.value = position / maxPosition;
1123+
expect(
1124+
getIndicator().valueColor!.value!.alpha,
1125+
valueColorAnimation.value!.alpha,
1126+
);
1127+
// Wait until the fling finishes before starting the next fling.
1128+
await tester.pumpAndSettle();
1129+
}
1130+
}
1131+
1132+
for (final Color color in colors) {
1133+
await testColor(color);
1134+
}
1135+
});
10731136
}

0 commit comments

Comments
 (0)