Unverified Commit 5c09ccad authored by Alex Li's avatar Alex Li Committed by GitHub

🐛 Setup color tween for `RefreshIndicator` in a better way (#134492)

Fixes https://github.com/flutter/flutter/issues/134489.
parent 9fa09ea4
......@@ -275,6 +275,7 @@ class RefreshIndicatorState extends State<RefreshIndicator> with TickerProviderS
late Future<void> _pendingRefreshFuture;
bool? _isIndicatorAtTop;
double? _dragOffset;
late Color _effectiveValueColor = widget.color ?? Theme.of(context).colorScheme.primary;
static final Animatable<double> _threeQuarterTween = Tween<double>(begin: 0.0, end: 0.75);
static final Animatable<double> _kDragSizeFactorLimitTween = Tween<double>(begin: 0.0, end: _kDragSizeFactorLimit);
......@@ -293,15 +294,7 @@ class RefreshIndicatorState extends State<RefreshIndicator> with TickerProviderS
@override
void didChangeDependencies() {
final ThemeData theme = Theme.of(context);
_valueColor = _positionController.drive(
ColorTween(
begin: (widget.color ?? theme.colorScheme.primary).withOpacity(0.0),
end: (widget.color ?? theme.colorScheme.primary).withOpacity(1.0),
).chain(CurveTween(
curve: const Interval(0.0, 1.0 / _kDragSizeFactorLimit),
)),
);
_setupColorTween();
super.didChangeDependencies();
}
......@@ -309,15 +302,7 @@ class RefreshIndicatorState extends State<RefreshIndicator> with TickerProviderS
void didUpdateWidget(covariant RefreshIndicator oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.color != widget.color) {
final ThemeData theme = Theme.of(context);
_valueColor = _positionController.drive(
ColorTween(
begin: (widget.color ?? theme.colorScheme.primary).withOpacity(0.0),
end: (widget.color ?? theme.colorScheme.primary).withOpacity(1.0),
).chain(CurveTween(
curve: const Interval(0.0, 1.0 / _kDragSizeFactorLimit),
)),
);
_setupColorTween();
}
}
......@@ -328,6 +313,28 @@ class RefreshIndicatorState extends State<RefreshIndicator> with TickerProviderS
super.dispose();
}
void _setupColorTween() {
// Reset the current value color.
_effectiveValueColor = widget.color ?? Theme.of(context).colorScheme.primary;
final Color color = _effectiveValueColor;
if (color.alpha == 0x00) {
// Set an always stopped animation instead of a driven tween.
_valueColor = AlwaysStoppedAnimation<Color>(color);
} else {
// Respect the alpha of the given color.
_valueColor = _positionController.drive(
ColorTween(
begin: color.withAlpha(0),
end: color.withAlpha(color.alpha),
).chain(
CurveTween(
curve: const Interval(0.0, 1.0 / _kDragSizeFactorLimit),
),
),
);
}
}
bool _shouldStart(ScrollNotification notification) {
// If the notification.dragDetails is null, this scroll is not triggered by
// user dragging. It may be a result of ScrollController.jumpTo or ballistic scroll.
......@@ -448,7 +455,7 @@ class RefreshIndicatorState extends State<RefreshIndicator> with TickerProviderS
newValue = math.max(newValue, 1.0 / _kDragSizeFactorLimit);
}
_positionController.value = clampDouble(newValue, 0.0, 1.0); // this triggers various rebuilds
if (_mode == _RefreshIndicatorMode.drag && _valueColor.value!.alpha == 0xFF) {
if (_mode == _RefreshIndicatorMode.drag && _valueColor.value!.alpha == _effectiveValueColor.alpha) {
_mode = _RefreshIndicatorMode.armed;
}
}
......
......@@ -1070,4 +1070,67 @@ void main() {
expect(refreshCalled, true);
expect(stretchAccepted, false);
});
testWidgetsWithLeakTracking('RefreshIndicator manipulates value color opacity correctly', (WidgetTester tester) async {
final List<Color> colors = <Color>[
Colors.black,
Colors.black54,
Colors.white,
Colors.white54,
Colors.transparent,
];
const List<double> positions = <double>[50.0, 100.0, 150.0];
Future<void> testColor(Color color) async {
final AnimationController positionController = AnimationController(vsync: const TestVSync());
// Correspond to [_setupColorTween].
final Animation<Color?> valueColorAnimation = positionController.drive(
ColorTween(
begin: color.withAlpha(0),
end: color.withAlpha(color.alpha),
).chain(
CurveTween(
// Correspond to [_kDragSizeFactorLimit].
curve: const Interval(0.0, 1.0 / 1.5),
),
),
);
await tester.pumpWidget(
MaterialApp(
home: RefreshIndicator(
onRefresh: refresh,
color: color,
child: ListView(
physics: const AlwaysScrollableScrollPhysics(),
children: const <Widget>[Text('X')],
),
),
),
);
RefreshProgressIndicator getIndicator() {
return tester.widget<RefreshProgressIndicator>(
find.byType(RefreshProgressIndicator),
);
}
// Correspond to [_kDragContainerExtentPercentage].
final double maxPosition = tester.view.physicalSize.height / tester.view.devicePixelRatio * 0.25;
for (final double position in positions) {
await tester.fling(find.text('X'), Offset(0.0, position), 1.0);
await tester.pump();
positionController.value = position / maxPosition;
expect(
getIndicator().valueColor!.value!.alpha,
valueColorAnimation.value!.alpha,
);
// Wait until the fling finishes before starting the next fling.
await tester.pumpAndSettle();
}
}
for (final Color color in colors) {
await testColor(color);
}
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment