Revert "Let InkWell/Ink/ancestor support GlobalKey so that splash does not stop when changing position. (#71138)" (#79300)
This reverts commit e7266dbb079d31fff0f886518c6a1b25bf89bb94.
This commit is contained in:
parent
a603714610
commit
c4ba26e31b
@ -251,21 +251,9 @@ class _InkState extends State<Ink> {
|
||||
|
||||
@override
|
||||
void deactivate() {
|
||||
_ink?.visible = false;
|
||||
super.deactivate();
|
||||
}
|
||||
|
||||
@override
|
||||
void reactivate() {
|
||||
_ink?.visible = true;
|
||||
super.reactivate();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_ink?.dispose();
|
||||
assert(_ink == null);
|
||||
super.dispose();
|
||||
super.deactivate();
|
||||
}
|
||||
|
||||
Widget _build(BuildContext context) {
|
||||
|
@ -733,7 +733,6 @@ class _InkResponseState extends State<_InkResponseStateWidget>
|
||||
implements _ParentInkResponseState {
|
||||
Set<InteractiveInkFeature>? _splashes;
|
||||
InteractiveInkFeature? _currentSplash;
|
||||
bool _active = true;
|
||||
bool _hovering = false;
|
||||
final Map<_HighlightType, InkHighlight?> _highlights = <_HighlightType, InkHighlight?>{};
|
||||
late final Map<Type, Action<Intent>> _actionMap = <Type, Action<Intent>>{
|
||||
@ -780,16 +779,7 @@ class _InkResponseState extends State<_InkResponseStateWidget>
|
||||
@override
|
||||
void didUpdateWidget(_InkResponseStateWidget oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
final InkFeature? validInkFeature = _getSingleInkFeature();
|
||||
if (validInkFeature != null && !identical(validInkFeature.controller, Material.of(context)!)) {
|
||||
_removeAllFeatures();
|
||||
if (_hovering && enabled)
|
||||
updateHighlight(_HighlightType.hover, value: _hovering, callOnHover: false);
|
||||
_updateFocusHighlights();
|
||||
return;
|
||||
}
|
||||
|
||||
if (enabled != _isWidgetEnabled(oldWidget)) {
|
||||
if (_isWidgetEnabled(widget) != _isWidgetEnabled(oldWidget)) {
|
||||
if (enabled) {
|
||||
// Don't call wigdet.onHover because many wigets, including the button
|
||||
// widgets, apply setState to an ancestor context from onHover.
|
||||
@ -799,47 +789,12 @@ class _InkResponseState extends State<_InkResponseStateWidget>
|
||||
}
|
||||
}
|
||||
|
||||
InkFeature? _getSingleInkFeature() {
|
||||
final List<InkFeature?> inkFeatures = <InkFeature?>[...?_splashes, ..._highlights.values];
|
||||
assert(() {
|
||||
MaterialInkController? lastController;
|
||||
for (final InkFeature? inkFeature in inkFeatures) {
|
||||
if (inkFeature == null)
|
||||
continue;
|
||||
final MaterialInkController controller = inkFeature.controller;
|
||||
if (lastController != null && !identical(controller, lastController))
|
||||
return false;
|
||||
lastController = controller;
|
||||
}
|
||||
return true;
|
||||
}());
|
||||
final InkFeature? validInkFeature = inkFeatures.firstWhere((InkFeature? inkFeature) => inkFeature != null, orElse: () => null);
|
||||
return validInkFeature;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_removeAllFeatures();
|
||||
FocusManager.instance.removeHighlightModeListener(_handleFocusHighlightModeChange);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _removeAllFeatures() {
|
||||
if (_splashes != null) {
|
||||
final Set<InteractiveInkFeature> splashes = _splashes!;
|
||||
_splashes = null;
|
||||
for (final InteractiveInkFeature splash in splashes)
|
||||
splash.dispose();
|
||||
_currentSplash = null;
|
||||
}
|
||||
assert(_currentSplash == null);
|
||||
for (final _HighlightType highlight in _highlights.keys) {
|
||||
_highlights[highlight]?.dispose();
|
||||
_highlights[highlight] = null;
|
||||
}
|
||||
widget.parentState?.markChildInkResponsePressed(this, false);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => highlightsExist || (_splashes != null && _splashes!.isNotEmpty);
|
||||
|
||||
@ -875,8 +830,7 @@ class _InkResponseState extends State<_InkResponseStateWidget>
|
||||
void handleInkRemoval() {
|
||||
assert(_highlights[type] != null);
|
||||
_highlights[type] = null;
|
||||
if (_active)
|
||||
updateKeepAlive();
|
||||
updateKeepAlive();
|
||||
}
|
||||
|
||||
if (type == _HighlightType.pressed) {
|
||||
@ -1059,26 +1013,24 @@ class _InkResponseState extends State<_InkResponseStateWidget>
|
||||
}
|
||||
}
|
||||
|
||||
void _setAllFeaturesVisible(bool visible) {
|
||||
for (final InkFeature? splash in <InkFeature?>[...?_splashes, ..._highlights.values])
|
||||
splash?.visible = visible;
|
||||
}
|
||||
|
||||
@override
|
||||
void deactivate() {
|
||||
_active = !_active;
|
||||
_setAllFeaturesVisible(false);
|
||||
if (_splashes != null) {
|
||||
final Set<InteractiveInkFeature> splashes = _splashes!;
|
||||
_splashes = null;
|
||||
for (final InteractiveInkFeature splash in splashes)
|
||||
splash.dispose();
|
||||
_currentSplash = null;
|
||||
}
|
||||
assert(_currentSplash == null);
|
||||
for (final _HighlightType highlight in _highlights.keys) {
|
||||
_highlights[highlight]?.dispose();
|
||||
_highlights[highlight] = null;
|
||||
}
|
||||
widget.parentState?.markChildInkResponsePressed(this, false);
|
||||
super.deactivate();
|
||||
}
|
||||
|
||||
@override
|
||||
void reactivate() {
|
||||
_active = !_active;
|
||||
_setAllFeaturesVisible(true);
|
||||
updateKeepAlive();
|
||||
super.reactivate();
|
||||
}
|
||||
|
||||
bool _isWidgetEnabled(_InkResponseStateWidget widget) {
|
||||
return widget.onTap != null || widget.onDoubleTap != null || widget.onLongPress != null;
|
||||
}
|
||||
@ -1249,10 +1201,6 @@ class _InkResponseState extends State<_InkResponseStateWidget>
|
||||
/// during animation. You should avoid using InkWells within [Material] widgets
|
||||
/// that are changing size.
|
||||
///
|
||||
/// Animations triggered by an [InkWell] will survive their widget moving due
|
||||
/// to [GlobalKey] reparenting, as long as the nearest [Material] ancestor is
|
||||
/// the same before and after the move.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [GestureDetector], for listening for gestures without ink splashes.
|
||||
|
@ -544,20 +544,12 @@ class _RenderInkFeatures extends RenderProxyBox implements MaterialInkController
|
||||
canvas.save();
|
||||
canvas.translate(offset.dx, offset.dy);
|
||||
canvas.clipRect(Offset.zero & size);
|
||||
for (final InkFeature inkFeature in _inkFeatures!) {
|
||||
if (inkFeature.visible)
|
||||
inkFeature._paint(canvas);
|
||||
}
|
||||
for (final InkFeature inkFeature in _inkFeatures!)
|
||||
inkFeature._paint(canvas);
|
||||
canvas.restore();
|
||||
}
|
||||
super.paint(context, offset);
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
// [InkFeature.dispose] will eventually call [_inkFeatures!.remove].
|
||||
while (_inkFeatures?.isNotEmpty == true)
|
||||
_inkFeatures!.first.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class _InkFeatures extends SingleChildRenderObjectWidget {
|
||||
@ -593,11 +585,6 @@ class _InkFeatures extends SingleChildRenderObjectWidget {
|
||||
..absorbHitTest = absorbHitTest;
|
||||
assert(vsync == renderObject.vsync);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUnmountRenderObject(_RenderInkFeatures renderObject) {
|
||||
renderObject.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// A visual reaction on a piece of [Material].
|
||||
@ -630,15 +617,6 @@ abstract class InkFeature {
|
||||
|
||||
bool _debugDisposed = false;
|
||||
|
||||
/// Whether or not visual reaction is activated.
|
||||
///
|
||||
/// Change this field will affect whether this InkFeature is render in next
|
||||
/// frame.
|
||||
///
|
||||
/// For this InkFeature to render properly, it should usually be change in
|
||||
/// [State.deactivate] and [State.reactivate].
|
||||
bool visible = true;
|
||||
|
||||
/// Free up the resources associated with this ink feature.
|
||||
@mustCallSuper
|
||||
void dispose() {
|
||||
|
@ -584,6 +584,7 @@ class _MergeableMaterialState extends State<MergeableMaterial> with TickerProvid
|
||||
}
|
||||
|
||||
child = AnimatedContainer(
|
||||
key: _MergeableMaterialSliceKey(_children[i].key),
|
||||
decoration: BoxDecoration(border: border),
|
||||
duration: kThemeAnimationDuration,
|
||||
curve: Curves.fastOutSlowIn,
|
||||
@ -599,7 +600,6 @@ class _MergeableMaterialState extends State<MergeableMaterial> with TickerProvid
|
||||
shape: BoxShape.rectangle,
|
||||
),
|
||||
child: Material(
|
||||
key: _MergeableMaterialSliceKey(_children[i].key),
|
||||
type: MaterialType.transparency,
|
||||
child: child,
|
||||
),
|
||||
|
@ -930,12 +930,6 @@ abstract class State<T extends StatefulWidget> with Diagnosticable {
|
||||
/// It is an error to call [setState] unless [mounted] is true.
|
||||
bool get mounted => _element != null;
|
||||
|
||||
/// This field is used tracks [reactivate] and [deactivate], to assert that
|
||||
/// they are called alternatively.
|
||||
///
|
||||
/// This field is not set in release mode.
|
||||
bool _debugActive = true;
|
||||
|
||||
/// Called when this object is inserted into the tree.
|
||||
///
|
||||
/// The framework will call this method exactly once for each [State] object
|
||||
@ -1116,17 +1110,17 @@ abstract class State<T extends StatefulWidget> with Diagnosticable {
|
||||
_element!.markNeedsBuild();
|
||||
}
|
||||
|
||||
/// Whenever the framework removes this [State] object from the tree, the
|
||||
/// framework will call this method.
|
||||
/// Called when this object is removed from the tree.
|
||||
///
|
||||
/// In some cases, the framework will reinsert the [State] object into
|
||||
/// another part of the tree (e.g., if the subtree containing this [State]
|
||||
/// object is grafted from one location in the tree to another). If that
|
||||
/// happens, the framework will ensure that it calls [reactivate] to give the
|
||||
/// [State] object a chance to adapt to its new location in the tree. If the
|
||||
/// framework does reinsert this subtree, it will do so before the end of the
|
||||
/// animation frame in which the subtree was removed from the tree. For this
|
||||
/// reason, [State] objects can defer releasing most resources until the
|
||||
/// The framework calls this method whenever it removes this [State] object
|
||||
/// from the tree. In some cases, the framework will reinsert the [State]
|
||||
/// object into another part of the tree (e.g., if the subtree containing this
|
||||
/// [State] object is grafted from one location in the tree to another). If
|
||||
/// that happens, the framework will ensure that it calls [build] to give the
|
||||
/// [State] object a chance to adapt to its new location in the tree. If
|
||||
/// the framework does reinsert this subtree, it will do so before the end of
|
||||
/// the animation frame in which the subtree was removed from the tree. For
|
||||
/// this reason, [State] objects can defer releasing most resources until the
|
||||
/// framework calls their [dispose] method.
|
||||
///
|
||||
/// Subclasses should override this method to clean up any links between
|
||||
@ -1142,35 +1136,7 @@ abstract class State<T extends StatefulWidget> with Diagnosticable {
|
||||
/// from the tree permanently.
|
||||
@protected
|
||||
@mustCallSuper
|
||||
void deactivate() {
|
||||
assert(() {
|
||||
_debugActive = !_debugActive;
|
||||
return !_debugActive;
|
||||
}());
|
||||
}
|
||||
|
||||
/// Called when this object is reactivated.
|
||||
///
|
||||
/// If the [widget] or one of its ancestors has a [GlobalKey], the framework
|
||||
/// will mark this object as inactive when it is removed and call
|
||||
/// [deactivate].
|
||||
///
|
||||
/// If the object is reinserted to the tree in the next frame (e.g. by
|
||||
/// changing position), it will be marked as active again and this method will be
|
||||
/// called.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [Element.activate] and [Element.deactivate] for more information about
|
||||
/// lifecycle.
|
||||
@protected
|
||||
@mustCallSuper
|
||||
void reactivate() {
|
||||
assert(() {
|
||||
_debugActive = !_debugActive;
|
||||
return _debugActive;
|
||||
}());
|
||||
}
|
||||
void deactivate() { }
|
||||
|
||||
/// Called when this object is removed from the tree permanently.
|
||||
///
|
||||
@ -4811,7 +4777,6 @@ class StatefulElement extends ComponentElement {
|
||||
@override
|
||||
void activate() {
|
||||
super.activate();
|
||||
state.reactivate();
|
||||
// Since the State could have observed the deactivate() and thus disposed of
|
||||
// resources allocated in the build method, we have to rebuild the widget
|
||||
// so that its State can reallocate its resources.
|
||||
|
@ -431,67 +431,4 @@ void main() {
|
||||
throw 'Expected: paint.color.alpha == 0, found: ${paint.color.alpha}';
|
||||
}));
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/6751
|
||||
testWidgets('When Ink has a GlobalKey and changes position, splash should not stop', (WidgetTester tester) async {
|
||||
const Color color = Colors.blue;
|
||||
const Color splashColor = Colors.green;
|
||||
|
||||
void expectTest(bool painted) {
|
||||
final PaintPattern paintPattern = paints
|
||||
..rect(color: Color(color.value))
|
||||
..circle(color: Color(splashColor.value));
|
||||
|
||||
expect(
|
||||
Material.of(tester.element(find.byType(InkWell)))! as RenderBox,
|
||||
painted ? paintPattern : isNot(paintPattern),
|
||||
);
|
||||
}
|
||||
|
||||
bool wrap = false;
|
||||
final Key globalKey = GlobalKey();
|
||||
await tester.pumpWidget(Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Material(
|
||||
child: Center(
|
||||
child: StatefulBuilder(
|
||||
builder: (BuildContext context, void Function(void Function()) setState) {
|
||||
Widget child = Ink(
|
||||
key: globalKey,
|
||||
color: color,
|
||||
width: 200.0,
|
||||
height: 200.0,
|
||||
child: InkWell(
|
||||
splashColor: splashColor,
|
||||
onTap: () { },
|
||||
onTapDown: (_) async {
|
||||
await Future<void>.delayed(const Duration(milliseconds: 50));
|
||||
|
||||
setState(() {
|
||||
wrap = !wrap;
|
||||
});
|
||||
}
|
||||
),
|
||||
);
|
||||
|
||||
if (wrap) {
|
||||
child = Container(
|
||||
margin: const EdgeInsets.only(top: 320),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
return child;
|
||||
}
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
final TestGesture testGesture = await tester.startGesture(tester.getRect(find.byType(InkWell)).center);
|
||||
await tester.pump(const Duration(milliseconds: 60));
|
||||
expectTest(true);
|
||||
await testGesture.up();
|
||||
await tester.pumpAndSettle();
|
||||
expectTest(false);
|
||||
});
|
||||
}
|
||||
|
@ -1055,6 +1055,118 @@ void main() {
|
||||
await gesture3.up();
|
||||
});
|
||||
|
||||
testWidgets('When ink wells are reparented, the old parent can display splash while the new parent can not', (WidgetTester tester) async {
|
||||
final GlobalKey innerKey = GlobalKey();
|
||||
final GlobalKey leftKey = GlobalKey();
|
||||
final GlobalKey rightKey = GlobalKey();
|
||||
|
||||
Widget doubleInkWellRow({
|
||||
required double leftWidth,
|
||||
required double rightWidth,
|
||||
Widget? leftChild,
|
||||
Widget? rightChild,
|
||||
}) {
|
||||
return Material(
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: SizedBox(
|
||||
width: leftWidth+rightWidth,
|
||||
height: 100,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
SizedBox(
|
||||
width: leftWidth,
|
||||
height: 100,
|
||||
child: InkWell(
|
||||
key: leftKey,
|
||||
onTap: () {},
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: leftWidth,
|
||||
height: 50,
|
||||
child: leftChild,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: rightWidth,
|
||||
height: 100,
|
||||
child: InkWell(
|
||||
key: rightKey,
|
||||
onTap: () {},
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: leftWidth,
|
||||
height: 50,
|
||||
child: rightChild,
|
||||
),
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(
|
||||
doubleInkWellRow(
|
||||
leftWidth: 110,
|
||||
rightWidth: 90,
|
||||
leftChild: InkWell(
|
||||
key: innerKey,
|
||||
onTap: () {},
|
||||
),
|
||||
),
|
||||
);
|
||||
final MaterialInkController material = Material.of(tester.element(find.byKey(innerKey)))!;
|
||||
|
||||
// Press inner
|
||||
final TestGesture gesture = await tester.startGesture(const Offset(100, 50), pointer: 1);
|
||||
await tester.pump(const Duration(milliseconds: 200));
|
||||
expect(material, paintsExactlyCountTimes(#drawCircle, 1));
|
||||
|
||||
// Switch side
|
||||
await tester.pumpWidget(
|
||||
doubleInkWellRow(
|
||||
leftWidth: 90,
|
||||
rightWidth: 110,
|
||||
rightChild: InkWell(
|
||||
key: innerKey,
|
||||
onTap: () {},
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(material, paintsExactlyCountTimes(#drawCircle, 0));
|
||||
|
||||
// A second pointer presses inner
|
||||
final TestGesture gesture2 = await tester.startGesture(const Offset(100, 50), pointer: 2);
|
||||
await tester.pump(const Duration(milliseconds: 200));
|
||||
expect(material, paintsExactlyCountTimes(#drawCircle, 1));
|
||||
|
||||
await gesture.up();
|
||||
await gesture2.up();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Press inner
|
||||
await gesture.down(const Offset(100, 50));
|
||||
await tester.pump(const Duration(milliseconds: 200));
|
||||
expect(material, paintsExactlyCountTimes(#drawCircle, 1));
|
||||
|
||||
// Press left
|
||||
await gesture2.down(const Offset(50, 50));
|
||||
await tester.pump(const Duration(milliseconds: 200));
|
||||
expect(material, paintsExactlyCountTimes(#drawCircle, 2));
|
||||
|
||||
await gesture.up();
|
||||
await gesture2.up();
|
||||
});
|
||||
|
||||
testWidgets("Ink wells's splash starts before tap is confirmed and disappear after tap is canceled", (WidgetTester tester) async {
|
||||
final GlobalKey innerKey = GlobalKey();
|
||||
await tester.pumpWidget(
|
||||
@ -1251,231 +1363,4 @@ void main() {
|
||||
textDirection: TextDirection.ltr,
|
||||
));
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/6751
|
||||
testWidgets('When InkWell has a GlobalKey and changes position, splash should not stop', (WidgetTester tester) async {
|
||||
final GlobalKey<_TestAppState> testAppKey = GlobalKey();
|
||||
int frames;
|
||||
|
||||
await tester.pumpWidget(TestApp(key: testAppKey));
|
||||
|
||||
void expectPaintedCircle(bool painted) {
|
||||
final PaintPattern paintPattern = paints..circle();
|
||||
expect(
|
||||
Material.of(tester.element(find.byType(InkWell)))! as RenderBox,
|
||||
painted ? paintPattern : isNot(paintPattern),
|
||||
);
|
||||
}
|
||||
Future<void> expectSplashContinueAfterMove(bool value) async {
|
||||
await tester.pump();
|
||||
expectPaintedCircle(true);
|
||||
await tester.pump(const Duration(milliseconds: 10));
|
||||
expectPaintedCircle(true);
|
||||
await tester.pump(const Duration(milliseconds: 40));
|
||||
expectPaintedCircle(value);
|
||||
}
|
||||
|
||||
// InkWell does not have any key, so splash will stop.
|
||||
testAppKey.currentState!.switchTapChangeWrap();
|
||||
await tester.pump();
|
||||
await tester.tap(find.byType(InkWell));
|
||||
await expectSplashContinueAfterMove(false);
|
||||
frames = await tester.pumpAndSettle();
|
||||
expect(frames, 1);
|
||||
expectPaintedCircle(false);
|
||||
|
||||
// InkWell has a ValueKey, so splash will also stop.
|
||||
testAppKey.currentState!.setInkWellKey(const Key('foo'));
|
||||
await tester.pump();
|
||||
await tester.tap(find.byType(InkWell));
|
||||
await expectSplashContinueAfterMove(false);
|
||||
frames = await tester.pumpAndSettle();
|
||||
expect(frames, 1);
|
||||
expectPaintedCircle(false);
|
||||
|
||||
// InkWell has a GlobalKey, so splash will continue.
|
||||
testAppKey.currentState!.setInkWellKey(GlobalKey());
|
||||
await tester.pump();
|
||||
await tester.tap(find.byType(InkWell));
|
||||
await expectSplashContinueAfterMove(true);
|
||||
frames = await tester.pumpAndSettle();
|
||||
expect(frames > 1, isTrue);
|
||||
expectPaintedCircle(false);
|
||||
|
||||
testAppKey.currentState!.switchTapDownChangeWrap();
|
||||
await tester.pump();
|
||||
final TestGesture testGesture = await tester.startGesture(tester.getRect(find.byType(InkWell)).center);
|
||||
await expectSplashContinueAfterMove(true);
|
||||
await testGesture.up();
|
||||
frames = await tester.pumpAndSettle();
|
||||
expect(frames > 1, isTrue);
|
||||
expectPaintedCircle(false);
|
||||
});
|
||||
|
||||
testWidgets('When InkWell/Ancestor has a GlobalKey and ancestor Material is replaced, splash should stop.', (WidgetTester tester) async {
|
||||
final Key key = GlobalKey();
|
||||
bool replaced = false;
|
||||
|
||||
await tester.pumpWidget(Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: StatefulBuilder(builder: (BuildContext context, void Function(void Function()) setState) {
|
||||
Future<void> changeReplace() async {
|
||||
await Future<void>.delayed(const Duration(milliseconds: 50));
|
||||
setState(() {
|
||||
replaced = !replaced;
|
||||
});
|
||||
}
|
||||
return Material(
|
||||
key: ValueKey<bool>(replaced),
|
||||
child: InkWell(
|
||||
key: key,
|
||||
onTap: () {
|
||||
changeReplace();
|
||||
},
|
||||
),
|
||||
);
|
||||
}),
|
||||
));
|
||||
|
||||
await tester.tap(find.byType(InkWell));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 60));
|
||||
|
||||
final PaintPattern paintPattern = paints..circle();
|
||||
expect(
|
||||
Material.of(tester.element(find.byType(InkWell)))! as RenderBox,
|
||||
isNot(paintPattern),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('When InkWell/Ancestor has a GlobalKey and ancestor Material is replaced, highlight should always be maintained.', (WidgetTester tester) async {
|
||||
const Color hoverColor = Color(0xff00ff00);
|
||||
final Key key = GlobalKey();
|
||||
int onHoverCount = 0;
|
||||
int callChangeReplaceCount = 0;
|
||||
bool replaced = false;
|
||||
int frames;
|
||||
|
||||
await tester.pumpWidget(Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: StatefulBuilder(builder: (BuildContext context, void Function(void Function()) setState) {
|
||||
Future<void> changeReplace() async {
|
||||
callChangeReplaceCount += 1;
|
||||
await Future<void>.delayed(const Duration(milliseconds: 50));
|
||||
setState(() {
|
||||
replaced = !replaced;
|
||||
});
|
||||
}
|
||||
return Container(
|
||||
margin: replaced ? const EdgeInsets.only(top: 1) : EdgeInsets.zero,
|
||||
child: Material(
|
||||
key: ValueKey<bool>(replaced),
|
||||
child: InkWell(
|
||||
key: key,
|
||||
hoverColor: hoverColor,
|
||||
onTap: () {},
|
||||
onHover: (bool hovered) {
|
||||
onHoverCount += 1;
|
||||
if (hovered)
|
||||
changeReplace();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
));
|
||||
|
||||
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||
await gesture.addPointer();
|
||||
addTearDown(gesture.removePointer);
|
||||
// When replaced is true, InkWell point at the top left is 1.
|
||||
await gesture.moveTo(Offset.zero);
|
||||
frames = await tester.pumpAndSettle();
|
||||
expect(frames > 1, isTrue);
|
||||
RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
|
||||
expect(inkFeatures, isNot(paints..rect(color: hoverColor)));
|
||||
expect(onHoverCount, 2);
|
||||
expect(callChangeReplaceCount, 1);
|
||||
expect(replaced, true);
|
||||
|
||||
await gesture.moveTo(tester.getCenter(find.byType(InkWell)));
|
||||
frames = await tester.pumpAndSettle();
|
||||
expect(frames > 1, isTrue);
|
||||
inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
|
||||
expect(inkFeatures, paints..rect(color: hoverColor));
|
||||
expect(onHoverCount, 3);
|
||||
expect(callChangeReplaceCount, 2);
|
||||
expect(replaced, false);
|
||||
});
|
||||
}
|
||||
|
||||
class TestApp extends StatefulWidget {
|
||||
const TestApp({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_TestAppState createState() => _TestAppState();
|
||||
}
|
||||
|
||||
class _TestAppState extends State<TestApp> {
|
||||
bool wrap = false;
|
||||
bool tapDownChangeWrap = false;
|
||||
bool tapChangeWrap = false;
|
||||
Key? inkWellKey;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget child = InkWell(
|
||||
key: inkWellKey,
|
||||
onTap: () async {
|
||||
if (tapChangeWrap) {
|
||||
await changeWrap();
|
||||
}
|
||||
},
|
||||
onTapDown: (_) async {
|
||||
if (tapDownChangeWrap) {
|
||||
await changeWrap();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if (wrap) {
|
||||
child = Container(
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
return Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Material(
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> changeWrap() async {
|
||||
await Future<void>.delayed(const Duration(milliseconds: 50));
|
||||
setState(() {
|
||||
wrap = !wrap;
|
||||
});
|
||||
}
|
||||
|
||||
void setInkWellKey(Key key) {
|
||||
setState(() {
|
||||
inkWellKey = key;
|
||||
});
|
||||
}
|
||||
|
||||
void switchTapDownChangeWrap() {
|
||||
setState(() {
|
||||
tapDownChangeWrap = true;
|
||||
tapChangeWrap = false;
|
||||
});
|
||||
}
|
||||
|
||||
void switchTapChangeWrap() {
|
||||
setState(() {
|
||||
tapChangeWrap = true;
|
||||
tapDownChangeWrap = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1331,28 +1331,24 @@ void main() {
|
||||
expect(key.currentState, isNotNull);
|
||||
expect(state.didChangeDependenciesCount, 1);
|
||||
expect(state.deactivatedCount, 0);
|
||||
expect(state.reactivatedCount, 0);
|
||||
|
||||
/// Rebuild with updated value - should call didChangeDependencies
|
||||
await tester.pumpWidget(Inherited(2, child: DependentStatefulWidget(key: key)));
|
||||
expect(key.currentState, isNotNull);
|
||||
expect(state.didChangeDependenciesCount, 2);
|
||||
expect(state.deactivatedCount, 0);
|
||||
expect(state.reactivatedCount, 0);
|
||||
|
||||
// reparent it - should call deactivate, reactivate, didChangeDependencies
|
||||
// reparent it - should call deactivate and didChangeDependencies
|
||||
await tester.pumpWidget(Inherited(3, child: SizedBox(child: DependentStatefulWidget(key: key))));
|
||||
expect(key.currentState, isNotNull);
|
||||
expect(state.didChangeDependenciesCount, 3);
|
||||
expect(state.deactivatedCount, 1);
|
||||
expect(state.reactivatedCount, 1);
|
||||
|
||||
// Remove it - should call deactivate, but not reactivate or didChangeDependencies
|
||||
// Remove it - should call deactivate, but not didChangeDependencies
|
||||
await tester.pumpWidget(const Inherited(4, child: SizedBox()));
|
||||
expect(key.currentState, isNull);
|
||||
expect(state.didChangeDependenciesCount, 3);
|
||||
expect(state.deactivatedCount, 2);
|
||||
expect(state.reactivatedCount, 1);
|
||||
});
|
||||
|
||||
testWidgets('StatefulElement subclass can decorate State.build', (WidgetTester tester) async {
|
||||
@ -1395,21 +1391,17 @@ void main() {
|
||||
expect(debugDoingBuildOnBuild, isTrue);
|
||||
});
|
||||
testWidgets('StatefulWidget', (WidgetTester tester) async {
|
||||
final Key key = GlobalKey();
|
||||
|
||||
late bool debugDoingBuildOnBuild;
|
||||
late bool debugDoingBuildOnInitState;
|
||||
late bool debugDoingBuildOnDidChangeDependencies;
|
||||
late bool debugDoingBuildOnDidUpdateWidget;
|
||||
bool? debugDoingBuildOnDispose;
|
||||
bool? debugDoingBuildOnDeactivate;
|
||||
bool? debugDoingBuildOnReactivate;
|
||||
|
||||
await tester.pumpWidget(
|
||||
Inherited(
|
||||
0,
|
||||
child: StatefulWidgetSpy(
|
||||
key: key,
|
||||
onInitState: (BuildContext context) {
|
||||
debugDoingBuildOnInitState = context.debugDoingBuild;
|
||||
},
|
||||
@ -1435,7 +1427,6 @@ void main() {
|
||||
Inherited(
|
||||
1,
|
||||
child: StatefulWidgetSpy(
|
||||
key: key,
|
||||
onDidUpdateWidget: (BuildContext context) {
|
||||
debugDoingBuildOnDidUpdateWidget = context.debugDoingBuild;
|
||||
},
|
||||
@ -1451,9 +1442,6 @@ void main() {
|
||||
onDeactivate: (BuildContext context) {
|
||||
debugDoingBuildOnDeactivate = context.debugDoingBuild;
|
||||
},
|
||||
onReactivate: (BuildContext context) {
|
||||
debugDoingBuildOnReactivate = context.debugDoingBuild;
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -1463,35 +1451,6 @@ void main() {
|
||||
expect(debugDoingBuildOnDidUpdateWidget, isFalse);
|
||||
expect(debugDoingBuildOnDidChangeDependencies, isFalse);
|
||||
expect(debugDoingBuildOnDeactivate, isNull);
|
||||
expect(debugDoingBuildOnReactivate, isNull);
|
||||
expect(debugDoingBuildOnDispose, isNull);
|
||||
|
||||
await tester.pumpWidget(
|
||||
Inherited(
|
||||
1,
|
||||
child: SizedBox(
|
||||
child: StatefulWidgetSpy(
|
||||
key: key,
|
||||
onBuild: (BuildContext context) {
|
||||
debugDoingBuildOnBuild = context.debugDoingBuild;
|
||||
},
|
||||
onDispose: (BuildContext context) {
|
||||
debugDoingBuildOnDispose = context.debugDoingBuild;
|
||||
},
|
||||
onDeactivate: (BuildContext context) {
|
||||
debugDoingBuildOnDeactivate = context.debugDoingBuild;
|
||||
},
|
||||
onReactivate: (BuildContext context) {
|
||||
debugDoingBuildOnReactivate = context.debugDoingBuild;
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(debugDoingBuildOnBuild, isTrue);
|
||||
expect(debugDoingBuildOnDeactivate, isFalse);
|
||||
expect(debugDoingBuildOnReactivate, isFalse);
|
||||
expect(debugDoingBuildOnDispose, isNull);
|
||||
|
||||
await tester.pumpWidget(Container());
|
||||
@ -1746,7 +1705,6 @@ class DependentStatefulWidget extends StatefulWidget {
|
||||
class DependentState extends State<DependentStatefulWidget> {
|
||||
int didChangeDependenciesCount = 0;
|
||||
int deactivatedCount = 0;
|
||||
int reactivatedCount = 0;
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
@ -1765,12 +1723,6 @@ class DependentState extends State<DependentStatefulWidget> {
|
||||
super.deactivate();
|
||||
deactivatedCount += 1;
|
||||
}
|
||||
|
||||
@override
|
||||
void reactivate() {
|
||||
super.reactivate();
|
||||
reactivatedCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
class SwapKeyWidget extends StatefulWidget {
|
||||
@ -1858,7 +1810,6 @@ class StatefulWidgetSpy extends StatefulWidget {
|
||||
this.onDidChangeDependencies,
|
||||
this.onDispose,
|
||||
this.onDeactivate,
|
||||
this.onReactivate,
|
||||
this.onDidUpdateWidget,
|
||||
}) : super(key: key);
|
||||
|
||||
@ -1867,7 +1818,6 @@ class StatefulWidgetSpy extends StatefulWidget {
|
||||
final void Function(BuildContext)? onDidChangeDependencies;
|
||||
final void Function(BuildContext)? onDispose;
|
||||
final void Function(BuildContext)? onDeactivate;
|
||||
final void Function(BuildContext)? onReactivate;
|
||||
final void Function(BuildContext)? onDidUpdateWidget;
|
||||
|
||||
@override
|
||||
@ -1887,12 +1837,6 @@ class _StatefulWidgetSpyState extends State<StatefulWidgetSpy> {
|
||||
widget.onDeactivate?.call(context);
|
||||
}
|
||||
|
||||
@override
|
||||
void reactivate() {
|
||||
super.reactivate();
|
||||
widget.onReactivate?.call(context);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
|
Loading…
x
Reference in New Issue
Block a user