Fix InkWell overlayColor resolution ignores selected state (#159072)
## Description
This PR fixes `InkWell` overlay colors resolution.
The `InkWell` overlay color is resolved when the `InkWell` is either
focused, hovered , and/or pressed.
This resolution happens at two places:
- first when the highlight is created.
- then on each build, using the inner function named
`getHighlightColorForType`.
This second resolution should be aware of other current states (such as
selected) as it might impact the color resolution.
For instance, several Material styles have colors resolution define
similarly to:
dc44547d0d/packages/flutter/lib/src/material/date_picker_theme.dart (L982-L1006)
## Related Issue
Fixes [InkWell overlay colors aren't applied on MaterialState.selected
state](https://github.com/flutter/flutter/issues/159063)
First step for [Date picker overlay colors aren't applied on
MaterialState.selected state](Date picker overlay colors aren't applied
on MaterialState.selected state).
## Tests
Adds 3 tests.
This commit is contained in:
parent
966aeb28ea
commit
043c59b075
@ -1283,12 +1283,16 @@ class _InkResponseState extends State<_InkResponseStateWidget>
|
||||
assert(widget.debugCheckContext(context));
|
||||
super.build(context); // See AutomaticKeepAliveClientMixin.
|
||||
|
||||
Color getHighlightColorForType(_HighlightType type) {
|
||||
const Set<MaterialState> pressed = <MaterialState>{MaterialState.pressed};
|
||||
const Set<MaterialState> focused = <MaterialState>{MaterialState.focused};
|
||||
const Set<MaterialState> hovered = <MaterialState>{MaterialState.hovered};
|
||||
final ThemeData theme = Theme.of(context);
|
||||
const Set<MaterialState> highlightableStates = <MaterialState>{MaterialState.focused, MaterialState.hovered, MaterialState.pressed};
|
||||
final Set<MaterialState> nonHighlightableStates = statesController.value.difference(highlightableStates);
|
||||
// Each highlightable state will be resolved separately to get the corresponding color.
|
||||
// For this resolution to be correct, the non-highlightable states should be preserved.
|
||||
final Set<MaterialState> pressed = <MaterialState>{...nonHighlightableStates, MaterialState.pressed};
|
||||
final Set<MaterialState> focused = <MaterialState>{...nonHighlightableStates, MaterialState.focused};
|
||||
final Set<MaterialState> hovered = <MaterialState>{...nonHighlightableStates, MaterialState.hovered};
|
||||
|
||||
final ThemeData theme = Theme.of(context);
|
||||
Color getHighlightColorForType(_HighlightType type) {
|
||||
return switch (type) {
|
||||
// The pressed state triggers a ripple (ink splash), per the current
|
||||
// Material Design spec. A separate highlight is no longer used.
|
||||
|
@ -11,6 +11,10 @@ import '../widgets/feedback_tester.dart';
|
||||
import '../widgets/semantics_tester.dart';
|
||||
|
||||
void main() {
|
||||
RenderObject getInkFeatures(WidgetTester tester) {
|
||||
return tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
|
||||
}
|
||||
|
||||
testWidgets('InkWell gestures control test', (WidgetTester tester) async {
|
||||
final List<String> log = <String>[];
|
||||
|
||||
@ -170,7 +174,7 @@ void main() {
|
||||
await gesture.addPointer();
|
||||
await gesture.moveTo(tester.getCenter(find.byType(SizedBox)));
|
||||
await tester.pumpAndSettle();
|
||||
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
|
||||
final RenderObject inkFeatures = getInkFeatures(tester);
|
||||
expect(inkFeatures, paints..rect(rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), color: const Color(0xff00ff00)));
|
||||
});
|
||||
|
||||
@ -209,7 +213,7 @@ void main() {
|
||||
await gesture.addPointer();
|
||||
await gesture.moveTo(tester.getCenter(find.byType(SizedBox)));
|
||||
await tester.pumpAndSettle();
|
||||
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
|
||||
final RenderObject inkFeatures = getInkFeatures(tester);
|
||||
expect(inkFeatures, paints..rect(rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), color: const Color(0xff00ff00)));
|
||||
});
|
||||
|
||||
@ -240,7 +244,7 @@ void main() {
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
|
||||
final RenderObject inkFeatures = getInkFeatures(tester);
|
||||
expect(inkFeatures, paintsExactlyCountTimes(#drawRect, 0));
|
||||
focusNode.requestFocus();
|
||||
await tester.pumpAndSettle();
|
||||
@ -289,7 +293,7 @@ void main() {
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
|
||||
final RenderObject inkFeatures = getInkFeatures(tester);
|
||||
expect(inkFeatures, paintsExactlyCountTimes(#drawRect, 0));
|
||||
focusNode.requestFocus();
|
||||
await tester.pumpAndSettle();
|
||||
@ -327,13 +331,101 @@ void main() {
|
||||
));
|
||||
await tester.pumpAndSettle();
|
||||
final TestGesture gesture = await tester.startGesture(tester.getRect(find.byType(InkWell)).center);
|
||||
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
|
||||
final RenderObject inkFeatures = getInkFeatures(tester);
|
||||
expect(inkFeatures, paints..rect(rect: const Rect.fromLTRB(0, 0, 100, 100), color: pressedColor.withAlpha(0)));
|
||||
await tester.pumpAndSettle(); // Let the press highlight animation finish.
|
||||
expect(inkFeatures, paints..rect(rect: const Rect.fromLTRB(0, 0, 100, 100), color: pressedColor));
|
||||
await gesture.up();
|
||||
});
|
||||
|
||||
group('Ink well overlayColor resolution respects WidgetState.selected', () {
|
||||
const Color selectedHoveredColor = Color(0xff00ff00);
|
||||
const Color selectedFocusedColor = Color(0xff0000ff);
|
||||
const Color selectedPressedColor = Color(0xff00ffff);
|
||||
const Rect inkRect = Rect.fromLTRB(0, 0, 100, 100);
|
||||
|
||||
Widget boilerplate({ FocusNode? focusNode }) {
|
||||
final WidgetStatesController statesController = WidgetStatesController(<MaterialState>{MaterialState.selected});
|
||||
addTearDown(statesController.dispose);
|
||||
|
||||
return Material(
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: SizedBox(
|
||||
width: 100,
|
||||
height: 100,
|
||||
child: InkWell(
|
||||
splashFactory: NoSplash.splashFactory,
|
||||
focusNode: focusNode,
|
||||
statesController: statesController,
|
||||
overlayColor: WidgetStateProperty.resolveWith<Color>((Set<WidgetState> states) {
|
||||
if (states.contains(WidgetState.selected)) {
|
||||
if (states.contains(WidgetState.pressed)) {
|
||||
return selectedPressedColor;
|
||||
}
|
||||
if (states.contains(WidgetState.hovered)) {
|
||||
return selectedHoveredColor;
|
||||
}
|
||||
if (states.contains(WidgetState.focused)) {
|
||||
return selectedFocusedColor;
|
||||
}
|
||||
return const Color(0xffbadbad); // Shouldn't happen.
|
||||
} else {
|
||||
return Colors.black;
|
||||
}
|
||||
}),
|
||||
onTap: () { },
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('when focused', (WidgetTester tester) async {
|
||||
FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
||||
final FocusNode focusNode = FocusNode(debugLabel: 'Ink Focus');
|
||||
addTearDown(focusNode.dispose);
|
||||
|
||||
await tester.pumpWidget(boilerplate(focusNode: focusNode));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final RenderObject inkFeatures = getInkFeatures(tester);
|
||||
expect(inkFeatures, paintsExactlyCountTimes(#drawRect, 0));
|
||||
focusNode.requestFocus();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(inkFeatures, paints..rect(rect: inkRect, color: selectedFocusedColor));
|
||||
});
|
||||
|
||||
testWidgets('when hovered', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(boilerplate());
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||
await gesture.addPointer();
|
||||
await gesture.moveTo(tester.getCenter(find.byType(SizedBox)));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final RenderObject inkFeatures = getInkFeatures(tester);
|
||||
expect(inkFeatures, paints..rect(rect: inkRect, color: selectedHoveredColor));
|
||||
});
|
||||
|
||||
testWidgets('when pressed', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(boilerplate());
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final TestGesture gesture = await tester.startGesture(tester.getRect(find.byType(InkWell)).center);
|
||||
final RenderObject inkFeatures = getInkFeatures(tester);
|
||||
expect(inkFeatures, paints..rect(rect: inkRect, color: selectedPressedColor.withAlpha(0)));
|
||||
await tester.pumpAndSettle(); // Let the press highlight animation finish.
|
||||
expect(inkFeatures, paints..rect(rect: inkRect, color: selectedPressedColor));
|
||||
await gesture.up();
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('ink response splashColor matches splashColor parameter', (WidgetTester tester) async {
|
||||
FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTouch;
|
||||
final FocusNode focusNode = FocusNode(debugLabel: 'Ink Focus');
|
||||
@ -367,7 +459,7 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
final TestGesture gesture = await tester.startGesture(tester.getRect(find.byType(InkWell)).center);
|
||||
await tester.pump(const Duration(milliseconds: 200)); // unconfirmed splash is well underway
|
||||
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
|
||||
final RenderObject inkFeatures = getInkFeatures(tester);
|
||||
expect(inkFeatures, paints..circle(x: 50, y: 50, color: splashColor));
|
||||
await gesture.up();
|
||||
focusNode.dispose();
|
||||
@ -417,7 +509,7 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
final TestGesture gesture = await tester.startGesture(tester.getRect(find.byType(InkWell)).center);
|
||||
await tester.pump(const Duration(milliseconds: 200)); // unconfirmed splash is well underway
|
||||
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
|
||||
final RenderObject inkFeatures = getInkFeatures(tester);
|
||||
expect(inkFeatures, paints..circle(x: 50, y: 50, color: splashColor));
|
||||
await gesture.up();
|
||||
focusNode.dispose();
|
||||
@ -446,7 +538,7 @@ void main() {
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
|
||||
final RenderObject inkFeatures = getInkFeatures(tester);
|
||||
expect(inkFeatures, paintsExactlyCountTimes(#drawCircle, 0));
|
||||
focusNode.requestFocus();
|
||||
await tester.pumpAndSettle();
|
||||
@ -477,7 +569,7 @@ void main() {
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
|
||||
final RenderObject inkFeatures = getInkFeatures(tester);
|
||||
expect(inkFeatures, paintsExactlyCountTimes(#drawRRect, 0));
|
||||
|
||||
focusNode.requestFocus();
|
||||
@ -513,7 +605,7 @@ void main() {
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
|
||||
final RenderObject inkFeatures = getInkFeatures(tester);
|
||||
expect(inkFeatures, paintsExactlyCountTimes(#drawRRect, 0));
|
||||
|
||||
// Hover the ink well.
|
||||
@ -555,7 +647,7 @@ void main() {
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
|
||||
final RenderObject inkFeatures = getInkFeatures(tester);
|
||||
expect(inkFeatures, paintsExactlyCountTimes(#clipPath, 0));
|
||||
expect(inkFeatures, paintsExactlyCountTimes(#drawRRect, 0));
|
||||
|
||||
@ -607,7 +699,7 @@ void main() {
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
|
||||
final RenderObject inkFeatures = getInkFeatures(tester);
|
||||
expect(inkFeatures, paintsExactlyCountTimes(#clipPath, 0));
|
||||
expect(inkFeatures, paintsExactlyCountTimes(#drawRRect, 0));
|
||||
|
||||
@ -660,7 +752,7 @@ testWidgets('InkResponse radius can be updated', (WidgetTester tester) async {
|
||||
}
|
||||
await tester.pumpWidget(boilerplate(10));
|
||||
await tester.pumpAndSettle();
|
||||
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
|
||||
final RenderObject inkFeatures = getInkFeatures(tester);
|
||||
expect(inkFeatures, paintsExactlyCountTimes(#drawCircle, 0));
|
||||
|
||||
focusNode.requestFocus();
|
||||
@ -701,7 +793,7 @@ testWidgets('InkResponse radius can be updated', (WidgetTester tester) async {
|
||||
|
||||
await tester.pumpWidget(boilerplate(BoxShape.circle));
|
||||
await tester.pumpAndSettle();
|
||||
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
|
||||
final RenderObject inkFeatures = getInkFeatures(tester);
|
||||
expect(inkFeatures, paintsExactlyCountTimes(#drawCircle, 0));
|
||||
expect(inkFeatures, paintsExactlyCountTimes(#drawRRect, 0));
|
||||
|
||||
@ -742,7 +834,7 @@ testWidgets('InkResponse radius can be updated', (WidgetTester tester) async {
|
||||
|
||||
await tester.pumpWidget(boilerplate(BorderRadius.circular(10)));
|
||||
await tester.pumpAndSettle();
|
||||
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
|
||||
final RenderObject inkFeatures = getInkFeatures(tester);
|
||||
expect(inkFeatures, paintsExactlyCountTimes(#drawRRect, 0));
|
||||
|
||||
focusNode.requestFocus();
|
||||
@ -791,7 +883,7 @@ testWidgets('InkResponse radius can be updated', (WidgetTester tester) async {
|
||||
|
||||
await tester.pumpWidget(boilerplate(BorderRadius.circular(20)));
|
||||
await tester.pumpAndSettle();
|
||||
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
|
||||
final RenderObject inkFeatures = getInkFeatures(tester);
|
||||
expect(inkFeatures, paintsExactlyCountTimes(#clipPath, 0));
|
||||
|
||||
focusNode.requestFocus();
|
||||
@ -862,7 +954,7 @@ testWidgets('InkResponse radius can be updated', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(boilerplate(BorderRadius.circular(20)));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
|
||||
final RenderObject inkFeatures = getInkFeatures(tester);
|
||||
expect(inkFeatures, paintsExactlyCountTimes(#clipPath, 0));
|
||||
|
||||
final TestGesture gesture = await tester.startGesture(tester.getRect(find.byType(InkWell)).center);
|
||||
@ -945,7 +1037,7 @@ testWidgets('InkResponse radius can be updated', (WidgetTester tester) async {
|
||||
),
|
||||
));
|
||||
await tester.pumpAndSettle();
|
||||
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
|
||||
final RenderObject inkFeatures = getInkFeatures(tester);
|
||||
expect(inkFeatures, paintsExactlyCountTimes(#drawRect, 0));
|
||||
focusNode.requestFocus();
|
||||
await tester.pumpAndSettle();
|
||||
@ -2048,7 +2140,7 @@ testWidgets('InkResponse radius can be updated', (WidgetTester tester) async {
|
||||
expect(log, equals(<String>['tap-down', 'double-tap']));
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
|
||||
final RenderObject inkFeatures = getInkFeatures(tester);
|
||||
expect(inkFeatures, paintsExactlyCountTimes(#drawRect, 0));
|
||||
});
|
||||
|
||||
@ -2093,7 +2185,7 @@ testWidgets('InkResponse radius can be updated', (WidgetTester tester) async {
|
||||
expect(log, equals(<String>['tap-down', 'tap-down', 'tap-cancel', 'double-tap']));
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
|
||||
final RenderObject inkFeatures = getInkFeatures(tester);
|
||||
expect(inkFeatures, paintsExactlyCountTimes(#drawCircle, 0));
|
||||
});
|
||||
|
||||
@ -2171,7 +2263,7 @@ testWidgets('InkResponse radius can be updated', (WidgetTester tester) async {
|
||||
await tester.pumpAndSettle();
|
||||
await gesture.moveTo(const Offset(10, 10)); // fade out the overlay
|
||||
await tester.pump(); // trigger the fade out animation
|
||||
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
|
||||
final RenderObject inkFeatures = getInkFeatures(tester);
|
||||
// Fadeout begins with the MaterialStates.hovered overlay color
|
||||
expect(inkFeatures, paints..rect(rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), color: const Color(0xff00ff00)));
|
||||
// 50ms fadeout is 50% complete, overlay color alpha goes from 0xff to 0x80
|
||||
@ -2236,7 +2328,7 @@ testWidgets('InkResponse radius can be updated', (WidgetTester tester) async {
|
||||
await tester.pump(const Duration(milliseconds: 200));
|
||||
|
||||
// No splash should be painted.
|
||||
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
|
||||
final RenderObject inkFeatures = getInkFeatures(tester);
|
||||
expect(inkFeatures, paintsExactlyCountTimes(#drawCircle, 0));
|
||||
|
||||
await gesture.up();
|
||||
|
Loading…
x
Reference in New Issue
Block a user