Skip to content

Commit

Permalink
🐛 Setup color tween for RefreshIndicator in a better way (#134492)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexV525 authored Sep 14, 2023
1 parent 9fa09ea commit 5c09cca
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 19 deletions.
45 changes: 26 additions & 19 deletions packages/flutter/lib/src/material/refresh_indicator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -293,31 +294,15 @@ 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();
}

@override
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();
}
}

Expand All @@ -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.
Expand Down Expand Up @@ -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;
}
}
Expand Down
63 changes: 63 additions & 0 deletions packages/flutter/test/material/refresh_indicator_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
});
}

0 comments on commit 5c09cca

Please sign in to comment.