Skip to content

Commit 06adde0

Browse files
authored
Nested InkWells only show the innermost splash (flutter#56611)
1 parent 0786f29 commit 06adde0

File tree

2 files changed

+629
-24
lines changed

2 files changed

+629
-24
lines changed

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

Lines changed: 174 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,29 @@ abstract class InteractiveInkFeatureFactory {
174174
});
175175
}
176176

177+
abstract class _ParentInkResponseState {
178+
void markChildInkResponsePressed(_ParentInkResponseState childState, bool value);
179+
}
180+
181+
class _ParentInkResponseProvider extends InheritedWidget {
182+
const _ParentInkResponseProvider({
183+
this.state,
184+
Widget child,
185+
}) : super(child: child);
186+
187+
final _ParentInkResponseState state;
188+
189+
@override
190+
bool updateShouldNotify(_ParentInkResponseProvider oldWidget) => state != oldWidget.state;
191+
192+
static _ParentInkResponseState of(BuildContext context) {
193+
return context.dependOnInheritedWidgetOfExactType<_ParentInkResponseProvider>()?.state;
194+
}
195+
}
196+
197+
typedef _GetRectCallback = RectCallback Function(RenderBox referenceBox);
198+
typedef _CheckContext = bool Function(BuildContext context);
199+
177200
/// An area of a [Material] that responds to touch. Has a configurable shape and
178201
/// can be configured to clip splashes that extend outside its bounds or not.
179202
///
@@ -255,7 +278,7 @@ abstract class InteractiveInkFeatureFactory {
255278
/// * [GestureDetector], for listening for gestures without ink splashes.
256279
/// * [RaisedButton] and [FlatButton], two kinds of buttons in material design.
257280
/// * [IconButton], which combines [InkResponse] with an [Icon].
258-
class InkResponse extends StatefulWidget {
281+
class InkResponse extends StatelessWidget {
259282
/// Creates an area of a [Material] that responds to touch.
260283
///
261284
/// Must have an ancestor [Material] widget in which to cause ink reactions.
@@ -508,6 +531,40 @@ class InkResponse extends StatefulWidget {
508531
/// slightly more efficient).
509532
RectCallback getRectCallback(RenderBox referenceBox) => null;
510533

534+
@override
535+
Widget build(BuildContext context) {
536+
final _ParentInkResponseState parentState = _ParentInkResponseProvider.of(context);
537+
return _InnerInkResponse(
538+
child: child,
539+
onTap: onTap,
540+
onTapDown: onTapDown,
541+
onTapCancel: onTapCancel,
542+
onDoubleTap: onDoubleTap,
543+
onLongPress: onLongPress,
544+
onHighlightChanged: onHighlightChanged,
545+
onHover: onHover,
546+
containedInkWell: containedInkWell,
547+
highlightShape: highlightShape,
548+
radius: radius,
549+
borderRadius: borderRadius,
550+
customBorder: customBorder,
551+
focusColor: focusColor,
552+
hoverColor: hoverColor,
553+
highlightColor: highlightColor,
554+
splashColor: splashColor,
555+
splashFactory: splashFactory,
556+
enableFeedback: enableFeedback,
557+
excludeFromSemantics: excludeFromSemantics,
558+
focusNode: focusNode,
559+
canRequestFocus: canRequestFocus,
560+
onFocusChange: onFocusChange,
561+
autofocus: autofocus,
562+
parentState: parentState,
563+
getRectCallback: getRectCallback,
564+
debugCheckContext: debugCheckContext,
565+
);
566+
}
567+
511568
/// Asserts that the given context satisfies the prerequisites for
512569
/// this class.
513570
///
@@ -521,9 +578,74 @@ class InkResponse extends StatefulWidget {
521578
assert(debugCheckHasDirectionality(context));
522579
return true;
523580
}
581+
}
582+
583+
class _InnerInkResponse extends StatefulWidget {
584+
const _InnerInkResponse({
585+
this.child,
586+
this.onTap,
587+
this.onTapDown,
588+
this.onTapCancel,
589+
this.onDoubleTap,
590+
this.onLongPress,
591+
this.onHighlightChanged,
592+
this.onHover,
593+
this.containedInkWell = false,
594+
this.highlightShape = BoxShape.circle,
595+
this.radius,
596+
this.borderRadius,
597+
this.customBorder,
598+
this.focusColor,
599+
this.hoverColor,
600+
this.highlightColor,
601+
this.splashColor,
602+
this.splashFactory,
603+
this.enableFeedback = true,
604+
this.excludeFromSemantics = false,
605+
this.focusNode,
606+
this.canRequestFocus = true,
607+
this.onFocusChange,
608+
this.autofocus = false,
609+
this.parentState,
610+
this.getRectCallback,
611+
this.debugCheckContext,
612+
}) : assert(containedInkWell != null),
613+
assert(highlightShape != null),
614+
assert(enableFeedback != null),
615+
assert(excludeFromSemantics != null),
616+
assert(autofocus != null),
617+
assert(canRequestFocus != null);
618+
619+
final Widget child;
620+
final GestureTapCallback onTap;
621+
final GestureTapDownCallback onTapDown;
622+
final GestureTapCallback onTapCancel;
623+
final GestureTapCallback onDoubleTap;
624+
final GestureLongPressCallback onLongPress;
625+
final ValueChanged<bool> onHighlightChanged;
626+
final ValueChanged<bool> onHover;
627+
final bool containedInkWell;
628+
final BoxShape highlightShape;
629+
final double radius;
630+
final BorderRadius borderRadius;
631+
final ShapeBorder customBorder;
632+
final Color focusColor;
633+
final Color hoverColor;
634+
final Color highlightColor;
635+
final Color splashColor;
636+
final InteractiveInkFeatureFactory splashFactory;
637+
final bool enableFeedback;
638+
final bool excludeFromSemantics;
639+
final ValueChanged<bool> onFocusChange;
640+
final bool autofocus;
641+
final FocusNode focusNode;
642+
final bool canRequestFocus;
643+
final _ParentInkResponseState parentState;
644+
final _GetRectCallback getRectCallback;
645+
final _CheckContext debugCheckContext;
524646

525647
@override
526-
_InkResponseState<InkResponse> createState() => _InkResponseState<InkResponse>();
648+
_InkResponseState createState() => _InkResponseState();
527649

528650
@override
529651
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
@@ -554,7 +676,9 @@ enum _HighlightType {
554676
focus,
555677
}
556678

557-
class _InkResponseState<T extends InkResponse> extends State<T> with AutomaticKeepAliveClientMixin<T> {
679+
class _InkResponseState extends State<_InnerInkResponse>
680+
with AutomaticKeepAliveClientMixin<_InnerInkResponse>
681+
implements _ParentInkResponseState {
558682
Set<InteractiveInkFeature> _splashes;
559683
InteractiveInkFeature _currentSplash;
560684
bool _hovering = false;
@@ -563,6 +687,23 @@ class _InkResponseState<T extends InkResponse> extends State<T> with AutomaticKe
563687

564688
bool get highlightsExist => _highlights.values.where((InkHighlight highlight) => highlight != null).isNotEmpty;
565689

690+
final ObserverList<_ParentInkResponseState> _activeChildren = ObserverList<_ParentInkResponseState>();
691+
@override
692+
void markChildInkResponsePressed(_ParentInkResponseState childState, bool value) {
693+
assert(childState != null);
694+
final bool lastAnyPressed = _anyChildInkResponsePressed;
695+
if (value) {
696+
_activeChildren.add(childState);
697+
} else {
698+
_activeChildren.remove(childState);
699+
}
700+
final bool nowAnyPressed = _anyChildInkResponsePressed;
701+
if (nowAnyPressed != lastAnyPressed) {
702+
widget.parentState?.markChildInkResponsePressed(this, nowAnyPressed);
703+
}
704+
}
705+
bool get _anyChildInkResponsePressed => _activeChildren.isNotEmpty;
706+
566707
void _handleAction(ActivateIntent intent) {
567708
_startSplash(context: context);
568709
_handleTap(context);
@@ -578,7 +719,7 @@ class _InkResponseState<T extends InkResponse> extends State<T> with AutomaticKe
578719
}
579720

580721
@override
581-
void didUpdateWidget(T oldWidget) {
722+
void didUpdateWidget(_InnerInkResponse oldWidget) {
582723
super.didUpdateWidget(oldWidget);
583724
if (_isWidgetEnabled(widget) != _isWidgetEnabled(oldWidget)) {
584725
_handleHoverChange(_hovering);
@@ -628,6 +769,9 @@ class _InkResponseState<T extends InkResponse> extends State<T> with AutomaticKe
628769
updateKeepAlive();
629770
}
630771

772+
if (type == _HighlightType.pressed) {
773+
widget.parentState?.markChildInkResponsePressed(this, value);
774+
}
631775
if (value == (highlight != null && highlight.active))
632776
return;
633777
if (value) {
@@ -737,6 +881,8 @@ class _InkResponseState<T extends InkResponse> extends State<T> with AutomaticKe
737881
}
738882

739883
void _handleTapDown(TapDownDetails details) {
884+
if (_anyChildInkResponsePressed)
885+
return;
740886
_startSplash(details: details);
741887
if (widget.onTapDown != null) {
742888
widget.onTapDown(details);
@@ -813,10 +959,11 @@ class _InkResponseState<T extends InkResponse> extends State<T> with AutomaticKe
813959
_highlights[highlight]?.dispose();
814960
_highlights[highlight] = null;
815961
}
962+
widget.parentState?.markChildInkResponsePressed(this, false);
816963
super.deactivate();
817964
}
818965

819-
bool _isWidgetEnabled(InkResponse widget) {
966+
bool _isWidgetEnabled(_InnerInkResponse widget) {
820967
return widget.onTap != null || widget.onDoubleTap != null || widget.onLongPress != null;
821968
}
822969

@@ -840,25 +987,28 @@ class _InkResponseState<T extends InkResponse> extends State<T> with AutomaticKe
840987
}
841988
_currentSplash?.color = widget.splashColor ?? Theme.of(context).splashColor;
842989
final bool canRequestFocus = enabled && widget.canRequestFocus;
843-
return Actions(
844-
actions: _actionMap,
845-
child: Focus(
846-
focusNode: widget.focusNode,
847-
canRequestFocus: canRequestFocus,
848-
onFocusChange: _handleFocusUpdate,
849-
autofocus: widget.autofocus,
850-
child: MouseRegion(
851-
onEnter: enabled ? _handleMouseEnter : null,
852-
onExit: enabled ? _handleMouseExit : null,
853-
child: GestureDetector(
854-
onTapDown: enabled ? _handleTapDown : null,
855-
onTap: enabled ? () => _handleTap(context) : null,
856-
onTapCancel: enabled ? _handleTapCancel : null,
857-
onDoubleTap: widget.onDoubleTap != null ? _handleDoubleTap : null,
858-
onLongPress: widget.onLongPress != null ? () => _handleLongPress(context) : null,
859-
behavior: HitTestBehavior.opaque,
860-
excludeFromSemantics: widget.excludeFromSemantics,
861-
child: widget.child,
990+
return _ParentInkResponseProvider(
991+
state: this,
992+
child: Actions(
993+
actions: _actionMap,
994+
child: Focus(
995+
focusNode: widget.focusNode,
996+
canRequestFocus: canRequestFocus,
997+
onFocusChange: _handleFocusUpdate,
998+
autofocus: widget.autofocus,
999+
child: MouseRegion(
1000+
onEnter: enabled ? _handleMouseEnter : null,
1001+
onExit: enabled ? _handleMouseExit : null,
1002+
child: GestureDetector(
1003+
onTapDown: enabled ? _handleTapDown : null,
1004+
onTap: enabled ? () => _handleTap(context) : null,
1005+
onTapCancel: enabled ? _handleTapCancel : null,
1006+
onDoubleTap: widget.onDoubleTap != null ? _handleDoubleTap : null,
1007+
onLongPress: widget.onLongPress != null ? () => _handleLongPress(context) : null,
1008+
behavior: HitTestBehavior.opaque,
1009+
excludeFromSemantics: widget.excludeFromSemantics,
1010+
child: widget.child,
1011+
),
8621012
),
8631013
),
8641014
),

0 commit comments

Comments
 (0)