From 9cf042ec746629301c9c1c7b6e220b02eddec4ec Mon Sep 17 00:00:00 2001 From: Hans Muller Date: Wed, 1 Jun 2022 17:19:16 -0700 Subject: [PATCH] Revert "Added MaterialStatesController, updated InkWell et al. (#103167)" (#105138) This reverts commit 180566f2e59928912c893cc0e8a12f97e653c206. --- .../material/text_button/text_button.1.dart | 102 ---------- .../text_button/text_button.1_test.dart | 51 ----- .../lib/src/material/button_style_button.dart | 112 +++++------ .../lib/src/material/elevated_button.dart | 1 - .../flutter/lib/src/material/ink_well.dart | 180 ++++++------------ .../lib/src/material/material_state.dart | 23 --- .../lib/src/material/outlined_button.dart | 1 - .../flutter/lib/src/material/text_button.dart | 8 - .../test/material/elevated_button_test.dart | 117 ------------ .../flutter/test/material/ink_well_test.dart | 43 ----- .../material_states_controller_test.dart | 85 --------- .../test/material/outlined_button_test.dart | 117 ------------ .../test/material/text_button_test.dart | 118 +----------- 13 files changed, 106 insertions(+), 852 deletions(-) delete mode 100644 examples/api/lib/material/text_button/text_button.1.dart delete mode 100644 examples/api/test/material/text_button/text_button.1_test.dart delete mode 100644 packages/flutter/test/material/material_states_controller_test.dart diff --git a/examples/api/lib/material/text_button/text_button.1.dart b/examples/api/lib/material/text_button/text_button.1.dart deleted file mode 100644 index aa10c5a12a..0000000000 --- a/examples/api/lib/material/text_button/text_button.1.dart +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:flutter/material.dart'; - -void main() { - runApp(const MaterialApp(home: Home())); -} - -class SelectableButton extends StatefulWidget { - const SelectableButton({ - super.key, - required this.selected, - this.style, - required this.onPressed, - required this.child, - }); - - final bool selected; - final ButtonStyle? style; - final VoidCallback? onPressed; - final Widget child; - - @override - State createState() => _SelectableButtonState(); - -} - -class _SelectableButtonState extends State { - late final MaterialStatesController statesController; - - @override - void initState() { - super.initState(); - statesController = MaterialStatesController({ - if (widget.selected) MaterialState.selected - }); - } - - @override - void didUpdateWidget(SelectableButton oldWidget) { - super.didUpdateWidget(oldWidget); - if (widget.selected != oldWidget.selected) { - statesController.update(MaterialState.selected, widget.selected); - } - } - - @override - Widget build(BuildContext context) { - return TextButton( - statesController: statesController, - style: widget.style, - onPressed: widget.onPressed, - child: widget.child, - ); - } -} - -class Home extends StatefulWidget { - const Home({ super.key }); - - @override - State createState() => _HomeState(); -} - -class _HomeState extends State { - bool selected = false; - - @override - Widget build(BuildContext context) { - return Scaffold( - body: Center( - child: SelectableButton( - selected: selected, - style: ButtonStyle( - foregroundColor: MaterialStateProperty.resolveWith( - (Set states) { - if (states.contains(MaterialState.selected)) { - return Colors.white; - } - return null; // defer to the defaults - }, - ), - backgroundColor: MaterialStateProperty.resolveWith( - (Set states) { - if (states.contains(MaterialState.selected)) { - return Colors.indigo; - } - return null; // defer to the defaults - }, - ), - ), - onPressed: () { - setState(() { selected = !selected; }); - }, - child: const Text('toggle selected'), - ), - ), - ); - } -} diff --git a/examples/api/test/material/text_button/text_button.1_test.dart b/examples/api/test/material/text_button/text_button.1_test.dart deleted file mode 100644 index 795e090d03..0000000000 --- a/examples/api/test/material/text_button/text_button.1_test.dart +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:flutter/material.dart'; -import 'package:flutter_api_samples/material/text_button/text_button.1.dart' as example; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - - testWidgets('SelectableButton', (WidgetTester tester) async { - await tester.pumpWidget( - MaterialApp( - theme: ThemeData( - colorScheme: const ColorScheme.light(), - ), - home: const example.Home(), - ), - ); - - final Finder button = find.byType(example.SelectableButton); - - example.SelectableButton buttonWidget() => tester.widget(button); - - Material buttonMaterial() { - return tester.widget( - find.descendant( - of: find.byType(example.SelectableButton), - matching: find.byType(Material), - ), - ); - } - - expect(buttonWidget().selected, false); - expect(buttonMaterial().textStyle!.color, const ColorScheme.light().primary); // default button foreground color - expect(buttonMaterial().color, Colors.transparent); // default button background color - - await tester.tap(button); // Toggles the button's selected property. - await tester.pumpAndSettle(); - expect(buttonWidget().selected, true); - expect(buttonMaterial().textStyle!.color, Colors.white); - expect(buttonMaterial().color, Colors.indigo); - - - await tester.tap(button); // Toggles the button's selected property. - await tester.pumpAndSettle(); - expect(buttonWidget().selected, false); - expect(buttonMaterial().textStyle!.color, const ColorScheme.light().primary); - expect(buttonMaterial().color, Colors.transparent); - }); -} diff --git a/packages/flutter/lib/src/material/button_style_button.dart b/packages/flutter/lib/src/material/button_style_button.dart index 8b9da26909..097aa3c2f4 100644 --- a/packages/flutter/lib/src/material/button_style_button.dart +++ b/packages/flutter/lib/src/material/button_style_button.dart @@ -14,6 +14,7 @@ import 'constants.dart'; import 'ink_well.dart'; import 'material.dart'; import 'material_state.dart'; +import 'material_state_mixin.dart'; import 'theme_data.dart'; /// The base [StatefulWidget] class for buttons whose style is defined by a [ButtonStyle] object. @@ -38,7 +39,6 @@ abstract class ButtonStyleButton extends StatefulWidget { required this.focusNode, required this.autofocus, required this.clipBehavior, - this.statesController, required this.child, }) : assert(autofocus != null), assert(clipBehavior != null); @@ -95,9 +95,6 @@ abstract class ButtonStyleButton extends StatefulWidget { /// {@macro flutter.widgets.Focus.autofocus} final bool autofocus; - /// {@macro flutter.material.inkwell.statesController} - final MaterialStatesController? statesController; - /// Typically the button's label. final Widget? child; @@ -194,59 +191,34 @@ abstract class ButtonStyleButton extends StatefulWidget { /// * [TextButton], a simple button without a shadow. /// * [ElevatedButton], a filled button whose material elevates when pressed. /// * [OutlinedButton], similar to [TextButton], but with an outline. -class _ButtonStyleState extends State with TickerProviderStateMixin { - AnimationController? controller; - double? elevation; - Color? backgroundColor; - MaterialStatesController? internalStatesController; - - void handleStatesControllerChange() { - // Force a rebuild to resolve MaterialStateProperty properties - setState(() { }); - } - - MaterialStatesController get statesController => widget.statesController ?? internalStatesController!; - - void initStatesController() { - if (widget.statesController == null) { - internalStatesController = MaterialStatesController(); - } - statesController.update(MaterialState.disabled, !widget.enabled); - statesController.addListener(handleStatesControllerChange); - } +class _ButtonStyleState extends State with MaterialStateMixin, TickerProviderStateMixin { + AnimationController? _controller; + double? _elevation; + Color? _backgroundColor; @override void initState() { super.initState(); - initStatesController(); + setMaterialState(MaterialState.disabled, !widget.enabled); + } + + @override + void dispose() { + _controller?.dispose(); + super.dispose(); } @override void didUpdateWidget(ButtonStyleButton oldWidget) { super.didUpdateWidget(oldWidget); - if (widget.statesController != oldWidget.statesController) { - oldWidget.statesController?.removeListener(handleStatesControllerChange); - if (widget.statesController != null) { - internalStatesController?.dispose(); - internalStatesController = null; - } - initStatesController(); + setMaterialState(MaterialState.disabled, !widget.enabled); + // If the button is disabled while a press gesture is currently ongoing, + // InkWell makes a call to handleHighlightChanged. This causes an exception + // because it calls setState in the middle of a build. To preempt this, we + // manually update pressed to false when this situation occurs. + if (isDisabled && isPressed) { + removeMaterialState(MaterialState.pressed); } - if (widget.enabled != oldWidget.enabled) { - statesController.update(MaterialState.disabled, !widget.enabled); - if (!widget.enabled) { - // The button may have been disabled while a press gesture is currently underway. - statesController.update(MaterialState.pressed, false); - } - } - } - - @override - void dispose() { - statesController.removeListener(handleStatesControllerChange); - internalStatesController?.dispose(); - controller?.dispose(); - super.dispose(); } @override @@ -265,9 +237,7 @@ class _ButtonStyleState extends State with TickerProviderStat T? resolve(MaterialStateProperty? Function(ButtonStyle? style) getProperty) { return effectiveValue( - (ButtonStyle? style) { - return getProperty(style)?.resolve(statesController.value); - }, + (ButtonStyle? style) => getProperty(style)?.resolve(materialStates), ); } @@ -284,7 +254,7 @@ class _ButtonStyleState extends State with TickerProviderStat final BorderSide? resolvedSide = resolve((ButtonStyle? style) => style?.side); final OutlinedBorder? resolvedShape = resolve((ButtonStyle? style) => style?.shape); - final MaterialStateMouseCursor mouseCursor = _MouseCursor( + final MaterialStateMouseCursor resolvedMouseCursor = _MouseCursor( (Set states) => effectiveValue((ButtonStyle? style) => style?.mouseCursor?.resolve(states)), ); @@ -339,16 +309,16 @@ class _ButtonStyleState extends State with TickerProviderStat // animates its elevation but not its color. SKIA renders non-zero // elevations as a shadow colored fill behind the Material's background. if (resolvedAnimationDuration! > Duration.zero - && elevation != null - && backgroundColor != null - && elevation != resolvedElevation - && backgroundColor!.value != resolvedBackgroundColor!.value - && backgroundColor!.opacity == 1 + && _elevation != null + && _backgroundColor != null + && _elevation != resolvedElevation + && _backgroundColor!.value != resolvedBackgroundColor!.value + && _backgroundColor!.opacity == 1 && resolvedBackgroundColor.opacity < 1 && resolvedElevation == 0) { - if (controller?.duration != resolvedAnimationDuration) { - controller?.dispose(); - controller = AnimationController( + if (_controller?.duration != resolvedAnimationDuration) { + _controller?.dispose(); + _controller = AnimationController( duration: resolvedAnimationDuration, vsync: this, ) @@ -358,12 +328,12 @@ class _ButtonStyleState extends State with TickerProviderStat } }); } - resolvedBackgroundColor = backgroundColor; // Defer changing the background color. - controller!.value = 0; - controller!.forward(); + resolvedBackgroundColor = _backgroundColor; // Defer changing the background color. + _controller!.value = 0; + _controller!.forward(); } - elevation = resolvedElevation; - backgroundColor = resolvedBackgroundColor; + _elevation = resolvedElevation; + _backgroundColor = resolvedBackgroundColor; final Widget result = ConstrainedBox( constraints: effectiveConstraints, @@ -380,18 +350,24 @@ class _ButtonStyleState extends State with TickerProviderStat child: InkWell( onTap: widget.onPressed, onLongPress: widget.onLongPress, - onHover: widget.onHover, - mouseCursor: mouseCursor, + onHighlightChanged: updateMaterialState(MaterialState.pressed), + onHover: updateMaterialState( + MaterialState.hovered, + onChanged: widget.onHover, + ), + mouseCursor: resolvedMouseCursor, enableFeedback: resolvedEnableFeedback, focusNode: widget.focusNode, canRequestFocus: widget.enabled, - onFocusChange: widget.onFocusChange, + onFocusChange: updateMaterialState( + MaterialState.focused, + onChanged: widget.onFocusChange, + ), autofocus: widget.autofocus, splashFactory: resolvedSplashFactory, overlayColor: overlayColor, highlightColor: Colors.transparent, customBorder: resolvedShape, - statesController: statesController, child: IconTheme.merge( data: IconThemeData(color: resolvedForegroundColor), child: Padding( diff --git a/packages/flutter/lib/src/material/elevated_button.dart b/packages/flutter/lib/src/material/elevated_button.dart index f56a47b9d4..cb570834ce 100644 --- a/packages/flutter/lib/src/material/elevated_button.dart +++ b/packages/flutter/lib/src/material/elevated_button.dart @@ -71,7 +71,6 @@ class ElevatedButton extends ButtonStyleButton { super.focusNode, super.autofocus = false, super.clipBehavior = Clip.none, - super.statesController, required super.child, }); diff --git a/packages/flutter/lib/src/material/ink_well.dart b/packages/flutter/lib/src/material/ink_well.dart index 29dfe58d5b..87ff2f8e0f 100644 --- a/packages/flutter/lib/src/material/ink_well.dart +++ b/packages/flutter/lib/src/material/ink_well.dart @@ -319,7 +319,6 @@ class InkResponse extends StatelessWidget { this.canRequestFocus = true, this.onFocusChange, this.autofocus = false, - this.statesController, }) : assert(containedInkWell != null), assert(highlightShape != null), assert(enableFeedback != null), @@ -582,19 +581,6 @@ class InkResponse extends StatelessWidget { /// slightly more efficient). RectCallback? getRectCallback(RenderBox referenceBox) => null; - /// {@template flutter.material.inkwell.statesController} - /// Represents the interactive "state" of this widget in terms of - /// a set of [MaterialState]s, like [MaterialState.pressed] and - /// [MaterialState.focused]. - /// - /// Classes based on this one can provide their own - /// [MaterialStatesController] to which they've added listeners. - /// They can also update the controller's [MaterialStatesController.value] - /// however, this may only be done when it's safe to call - /// [State.setState], like in an event handler. - /// {@endtemplate} - final MaterialStatesController? statesController; - @override Widget build(BuildContext context) { final _ParentInkResponseState? parentState = _ParentInkResponseProvider.of(context); @@ -628,7 +614,6 @@ class InkResponse extends StatelessWidget { parentState: parentState, getRectCallback: getRectCallback, debugCheckContext: debugCheckContext, - statesController: statesController, child: child, ); } @@ -680,7 +665,6 @@ class _InkResponseStateWidget extends StatefulWidget { this.parentState, this.getRectCallback, required this.debugCheckContext, - this.statesController, }) : assert(containedInkWell != null), assert(highlightShape != null), assert(enableFeedback != null), @@ -718,7 +702,6 @@ class _InkResponseStateWidget extends StatefulWidget { final _ParentInkResponseState? parentState; final _GetRectCallback? getRectCallback; final _CheckContext debugCheckContext; - final MaterialStatesController? statesController; @override _InkResponseState createState() => _InkResponseState(); @@ -755,18 +738,16 @@ enum _HighlightType { } class _InkResponseState extends State<_InkResponseStateWidget> - with AutomaticKeepAliveClientMixin<_InkResponseStateWidget> - implements _ParentInkResponseState -{ + with AutomaticKeepAliveClientMixin<_InkResponseStateWidget> + implements _ParentInkResponseState { Set? _splashes; InteractiveInkFeature? _currentSplash; bool _hovering = false; final Map<_HighlightType, InkHighlight?> _highlights = <_HighlightType, InkHighlight?>{}; late final Map> _actionMap = >{ - ActivateIntent: CallbackAction(onInvoke: simulateTap), - ButtonActivateIntent: CallbackAction(onInvoke: simulateTap), + ActivateIntent: CallbackAction(onInvoke: _simulateTap), + ButtonActivateIntent: CallbackAction(onInvoke: _simulateTap), }; - MaterialStatesController? internalStatesController; bool get highlightsExist => _highlights.values.where((InkHighlight? highlight) => highlight != null).isNotEmpty; @@ -788,65 +769,38 @@ class _InkResponseState extends State<_InkResponseStateWidget> } bool get _anyChildInkResponsePressed => _activeChildren.isNotEmpty; - void simulateTap([Intent? intent]) { + void _simulateTap([Intent? intent]) { _startNewSplash(context: context); - handleTap(); + _handleTap(); } - void simulateLongPress() { + void _simulateLongPress() { _startNewSplash(context: context); - handleLongPress(); - } - - void handleStatesControllerChange() { - // Force a rebuild to resolve widget.overlayColor, widget.mouseCursor - setState(() { }); - } - - MaterialStatesController get statesController => widget.statesController ?? internalStatesController!; - - void initStatesController() { - if (widget.statesController == null) { - internalStatesController = MaterialStatesController(); - } - statesController.update(MaterialState.disabled, !enabled); - statesController.addListener(handleStatesControllerChange); + _handleLongPress(); } @override void initState() { super.initState(); - initStatesController(); - FocusManager.instance.addHighlightModeListener(handleFocusHighlightModeChange); + FocusManager.instance.addHighlightModeListener(_handleFocusHighlightModeChange); } @override void didUpdateWidget(_InkResponseStateWidget oldWidget) { super.didUpdateWidget(oldWidget); - if (widget.statesController != oldWidget.statesController) { - oldWidget.statesController?.removeListener(handleStatesControllerChange); - if (widget.statesController != null) { - internalStatesController?.dispose(); - internalStatesController = null; + if (_isWidgetEnabled(widget) != _isWidgetEnabled(oldWidget)) { + if (enabled) { + // Don't call widget.onHover because many widgets, including the button + // widgets, apply setState to an ancestor context from onHover. + updateHighlight(_HighlightType.hover, value: _hovering, callOnHover: false); } - initStatesController(); + _updateFocusHighlights(); } - if (enabled != isWidgetEnabled(oldWidget)) { - statesController.update(MaterialState.disabled, !enabled); - if (!enabled) { - statesController.update(MaterialState.pressed, false); - } - // Don't call widget.onHover because many widgets, including the button - // widgets, apply setState to an ancestor context from onHover. - updateHighlight(_HighlightType.hover, value: _hovering, callOnHover: false); - } - updateFocusHighlights(); } @override void dispose() { - FocusManager.instance.removeHighlightModeListener(handleFocusHighlightModeChange); - statesController.removeListener(handleStatesControllerChange); + FocusManager.instance.removeHighlightModeListener(_handleFocusHighlightModeChange); super.dispose(); } @@ -854,18 +808,21 @@ class _InkResponseState extends State<_InkResponseStateWidget> bool get wantKeepAlive => highlightsExist || (_splashes != null && _splashes!.isNotEmpty); Color getHighlightColorForType(_HighlightType type) { + const Set pressed = {MaterialState.pressed}; + const Set focused = {MaterialState.focused}; + const Set hovered = {MaterialState.hovered}; + final ThemeData theme = Theme.of(context); - final Color? resolvedOverlayColor = widget.overlayColor?.resolve(statesController.value); switch (type) { // The pressed state triggers a ripple (ink splash), per the current // Material Design spec. A separate highlight is no longer used. // See https://material.io/design/interaction/states.html#pressed case _HighlightType.pressed: - return resolvedOverlayColor ?? widget.highlightColor ?? theme.highlightColor; + return widget.overlayColor?.resolve(pressed) ?? widget.highlightColor ?? theme.highlightColor; case _HighlightType.focus: - return resolvedOverlayColor ?? widget.focusColor ?? theme.focusColor; + return widget.overlayColor?.resolve(focused) ?? widget.focusColor ?? theme.focusColor; case _HighlightType.hover: - return resolvedOverlayColor ?? widget.hoverColor ?? theme.hoverColor; + return widget.overlayColor?.resolve(hovered) ?? widget.hoverColor ?? theme.hoverColor; } } @@ -887,20 +844,6 @@ class _InkResponseState extends State<_InkResponseStateWidget> updateKeepAlive(); } - switch (type) { - case _HighlightType.pressed: - statesController.update(MaterialState.pressed, value); - break; - case _HighlightType.hover: - if (callOnHover) { - statesController.update(MaterialState.hovered, value); - } - break; - case _HighlightType.focus: - // see handleFocusUpdate() - break; - } - if (type == _HighlightType.pressed) { widget.parentState?.markChildInkResponsePressed(this, value); } @@ -950,7 +893,8 @@ class _InkResponseState extends State<_InkResponseStateWidget> final MaterialInkController inkController = Material.of(context)!; final RenderBox referenceBox = context.findRenderObject()! as RenderBox; final Offset position = referenceBox.globalToLocal(globalPosition); - final Color color = widget.overlayColor?.resolve(statesController.value) ?? widget.splashColor ?? Theme.of(context).splashColor; + const Set pressed = {MaterialState.pressed}; + final Color color = widget.overlayColor?.resolve(pressed) ?? widget.splashColor ?? Theme.of(context).splashColor; final RectCallback? rectCallback = widget.containedInkWell ? widget.getRectCallback!(referenceBox) : null; final BorderRadius? borderRadius = widget.borderRadius; final ShapeBorder? customBorder = widget.customBorder; @@ -984,12 +928,12 @@ class _InkResponseState extends State<_InkResponseStateWidget> return splash; } - void handleFocusHighlightModeChange(FocusHighlightMode mode) { + void _handleFocusHighlightModeChange(FocusHighlightMode mode) { if (!mounted) { return; } setState(() { - updateFocusHighlights(); + _updateFocusHighlights(); }); } @@ -1003,7 +947,7 @@ class _InkResponseState extends State<_InkResponseStateWidget> } } - void updateFocusHighlights() { + void _updateFocusHighlights() { final bool showFocus; switch (FocusManager.instance.highlightMode) { case FocusHighlightMode.touch: @@ -1017,18 +961,13 @@ class _InkResponseState extends State<_InkResponseStateWidget> } bool _hasFocus = false; - void handleFocusUpdate(bool hasFocus) { + void _handleFocusUpdate(bool hasFocus) { _hasFocus = hasFocus; - // Set here rather than updateHighlight because this widget's - // (MaterialState) states include MaterialState.focused if - // the InkWell _has_ the focus, rather than if it's showing - // the focus per FocusManager.instance.highlightMode. - statesController.update(MaterialState.focused, hasFocus); - updateFocusHighlights(); + _updateFocusHighlights(); widget.onFocusChange?.call(hasFocus); } - void handleTapDown(TapDownDetails details) { + void _handleTapDown(TapDownDetails details) { if (_anyChildInkResponsePressed) { return; } @@ -1036,7 +975,7 @@ class _InkResponseState extends State<_InkResponseStateWidget> widget.onTapDown?.call(details); } - void handleTapUp(TapUpDetails details) { + void _handleTapUp(TapUpDetails details) { widget.onTapUp?.call(details); } @@ -1051,7 +990,6 @@ class _InkResponseState extends State<_InkResponseStateWidget> } else { globalPosition = details!.globalPosition; } - statesController.update(MaterialState.pressed, true); // ... before creating the splash final InteractiveInkFeature splash = _createInkFeature(globalPosition); _splashes ??= HashSet(); _splashes!.add(splash); @@ -1061,7 +999,7 @@ class _InkResponseState extends State<_InkResponseStateWidget> updateHighlight(_HighlightType.pressed, value: true); } - void handleTap() { + void _handleTap() { _currentSplash?.confirm(); _currentSplash = null; updateHighlight(_HighlightType.pressed, value: false); @@ -1073,21 +1011,21 @@ class _InkResponseState extends State<_InkResponseStateWidget> } } - void handleTapCancel() { + void _handleTapCancel() { _currentSplash?.cancel(); _currentSplash = null; widget.onTapCancel?.call(); updateHighlight(_HighlightType.pressed, value: false); } - void handleDoubleTap() { + void _handleDoubleTap() { _currentSplash?.confirm(); _currentSplash = null; updateHighlight(_HighlightType.pressed, value: false); widget.onDoubleTap?.call(); } - void handleLongPress() { + void _handleLongPress() { _currentSplash?.confirm(); _currentSplash = null; if (widget.onLongPress != null) { @@ -1117,27 +1055,27 @@ class _InkResponseState extends State<_InkResponseStateWidget> super.deactivate(); } - bool isWidgetEnabled(_InkResponseStateWidget widget) { + bool _isWidgetEnabled(_InkResponseStateWidget widget) { return widget.onTap != null || widget.onDoubleTap != null || widget.onLongPress != null || widget.onTapDown != null; } - bool get enabled => isWidgetEnabled(widget); + bool get enabled => _isWidgetEnabled(widget); - void handleMouseEnter(PointerEnterEvent event) { + void _handleMouseEnter(PointerEnterEvent event) { _hovering = true; if (enabled) { - handleHoverChange(); + _handleHoverChange(); } } - void handleMouseExit(PointerExitEvent event) { + void _handleMouseExit(PointerExitEvent event) { _hovering = false; // If the exit occurs after we've been disabled, we still // want to take down the highlights and run widget.onHover. - handleHoverChange(); + _handleHoverChange(); } - void handleHoverChange() { + void _handleHoverChange() { updateHighlight(_HighlightType.hover, value: _hovering); } @@ -1159,11 +1097,16 @@ class _InkResponseState extends State<_InkResponseStateWidget> _highlights[type]?.color = getHighlightColorForType(type); } - _currentSplash?.color = widget.overlayColor?.resolve(statesController.value) ?? widget.splashColor ?? Theme.of(context).splashColor; + const Set pressed = {MaterialState.pressed}; + _currentSplash?.color = widget.overlayColor?.resolve(pressed) ?? widget.splashColor ?? Theme.of(context).splashColor; final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs( widget.mouseCursor ?? MaterialStateMouseCursor.clickable, - statesController.value, + { + if (!enabled) MaterialState.disabled, + if (_hovering && enabled) MaterialState.hovered, + if (_hasFocus) MaterialState.focused, + }, ); return _ParentInkResponseProvider( @@ -1173,22 +1116,22 @@ class _InkResponseState extends State<_InkResponseStateWidget> child: Focus( focusNode: widget.focusNode, canRequestFocus: _canRequestFocus, - onFocusChange: handleFocusUpdate, + onFocusChange: _handleFocusUpdate, autofocus: widget.autofocus, child: MouseRegion( cursor: effectiveMouseCursor, - onEnter: handleMouseEnter, - onExit: handleMouseExit, + onEnter: _handleMouseEnter, + onExit: _handleMouseExit, child: Semantics( - onTap: widget.excludeFromSemantics || widget.onTap == null ? null : simulateTap, - onLongPress: widget.excludeFromSemantics || widget.onLongPress == null ? null : simulateLongPress, + onTap: widget.excludeFromSemantics || widget.onTap == null ? null : _simulateTap, + onLongPress: widget.excludeFromSemantics || widget.onLongPress == null ? null : _simulateLongPress, child: GestureDetector( - onTapDown: enabled ? handleTapDown : null, - onTapUp: enabled ? handleTapUp : null, - onTap: enabled ? handleTap : null, - onTapCancel: enabled ? handleTapCancel : null, - onDoubleTap: widget.onDoubleTap != null ? handleDoubleTap : null, - onLongPress: widget.onLongPress != null ? handleLongPress : null, + onTapDown: enabled ? _handleTapDown : null, + onTapUp: enabled ? _handleTapUp : null, + onTap: enabled ? _handleTap : null, + onTapCancel: enabled ? _handleTapCancel : null, + onDoubleTap: widget.onDoubleTap != null ? _handleDoubleTap : null, + onLongPress: widget.onLongPress != null ? _handleLongPress : null, behavior: HitTestBehavior.opaque, excludeFromSemantics: true, child: widget.child, @@ -1313,7 +1256,6 @@ class InkWell extends InkResponse { super.canRequestFocus, super.onFocusChange, super.autofocus, - super.statesController, }) : super( containedInkWell: true, highlightShape: BoxShape.rectangle, diff --git a/packages/flutter/lib/src/material/material_state.dart b/packages/flutter/lib/src/material/material_state.dart index df0cb9f2d2..282dc561ce 100644 --- a/packages/flutter/lib/src/material/material_state.dart +++ b/packages/flutter/lib/src/material/material_state.dart @@ -686,26 +686,3 @@ class MaterialStatePropertyAll implements MaterialStateProperty { @override String toString() => 'MaterialStatePropertyAll($value)'; } - -/// Manages a set of [MaterialState]s and notifies listeners of changes. -/// -/// Used by widgets that expose their internal state for the sake of -/// extensions that add support for additional states. See -/// [TextButton.statesController] for example. -/// -/// The controller's [value] is its current set of states. Listeners -/// are notified whenever the [value] changes. The [value] should only be -/// changed with [update]; it should not be modified directly. -class MaterialStatesController extends ValueNotifier> { - /// Creates a MaterialStatesController. - MaterialStatesController([Set? value]) : super({...?value}); - - /// Adds [state] to [value] if [add] is true, and removes it otherwise, - /// and notifies listeners if [value] has changed. - void update(MaterialState state, bool add) { - final bool valueChanged = add ? value.add(state) : value.remove(state); - if (valueChanged) { - notifyListeners(); - } - } -} diff --git a/packages/flutter/lib/src/material/outlined_button.dart b/packages/flutter/lib/src/material/outlined_button.dart index 682d80180a..7e21de3a0b 100644 --- a/packages/flutter/lib/src/material/outlined_button.dart +++ b/packages/flutter/lib/src/material/outlined_button.dart @@ -76,7 +76,6 @@ class OutlinedButton extends ButtonStyleButton { super.focusNode, super.autofocus = false, super.clipBehavior = Clip.none, - super.statesController, required Widget super.child, }); diff --git a/packages/flutter/lib/src/material/text_button.dart b/packages/flutter/lib/src/material/text_button.dart index 00c8d3e9a3..e1936d501f 100644 --- a/packages/flutter/lib/src/material/text_button.dart +++ b/packages/flutter/lib/src/material/text_button.dart @@ -57,13 +57,6 @@ import 'theme_data.dart'; /// ** See code in examples/api/lib/material/text_button/text_button.0.dart ** /// {@end-tool} /// -/// {@tool dartpad} -/// This sample demonstrates using the [statesController] parameter to create a button -/// that adds support for [MaterialState.selected]. -/// -/// ** See code in examples/api/lib/material/text_button/text_button.1.dart ** -/// {@end-tool} -/// /// See also: /// /// * [OutlinedButton], a [TextButton] with a border outline. @@ -83,7 +76,6 @@ class TextButton extends ButtonStyleButton { super.focusNode, super.autofocus = false, super.clipBehavior = Clip.none, - super.statesController, required Widget super.child, }); diff --git a/packages/flutter/test/material/elevated_button_test.dart b/packages/flutter/test/material/elevated_button_test.dart index d004a5177a..d40b1efef8 100644 --- a/packages/flutter/test/material/elevated_button_test.dart +++ b/packages/flutter/test/material/elevated_button_test.dart @@ -1554,123 +1554,6 @@ void main() { expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.basic); }); - - testWidgets('ElevatedButton statesController', (WidgetTester tester) async { - int count = 0; - void valueChanged() { - count += 1; - } - final MaterialStatesController controller = MaterialStatesController(); - controller.addListener(valueChanged); - - await tester.pumpWidget( - MaterialApp( - home: Center( - child: ElevatedButton( - statesController: controller, - onPressed: () { }, - child: const Text('button'), - ), - ), - ), - ); - - expect(controller.value, {}); - expect(count, 0); - - final Offset center = tester.getCenter(find.byType(ElevatedButton)); - final TestGesture gesture = await tester.createGesture( - kind: PointerDeviceKind.mouse, - ); - await gesture.addPointer(); - await gesture.moveTo(center); - await tester.pumpAndSettle(); - - expect(controller.value, {MaterialState.hovered}); - expect(count, 1); - - await gesture.moveTo(Offset.zero); - await tester.pumpAndSettle(); - - expect(controller.value, {}); - expect(count, 2); - - await gesture.moveTo(center); - await tester.pumpAndSettle(); - - expect(controller.value, {MaterialState.hovered}); - expect(count, 3); - - await gesture.down(center); - await tester.pumpAndSettle(); - - expect(controller.value, {MaterialState.hovered, MaterialState.pressed}); - expect(count, 4); - - await gesture.up(); - await tester.pumpAndSettle(); - - expect(controller.value, {MaterialState.hovered}); - expect(count, 5); - - await gesture.moveTo(Offset.zero); - await tester.pumpAndSettle(); - - expect(controller.value, {}); - expect(count, 6); - - await gesture.down(center); - await tester.pumpAndSettle(); - expect(controller.value, {MaterialState.hovered, MaterialState.pressed}); - expect(count, 8); // adds hovered and pressed - two changes - - // If the button is rebuilt disabled, then the pressed state is - // removed. - await tester.pumpWidget( - MaterialApp( - home: Center( - child: ElevatedButton( - statesController: controller, - onPressed: null, - child: const Text('button'), - ), - ), - ), - ); - await tester.pumpAndSettle(); - expect(controller.value, {MaterialState.hovered, MaterialState.disabled}); - expect(count, 10); // removes pressed and adds disabled - two changes - - await gesture.moveTo(Offset.zero); - await tester.pumpAndSettle(); - expect(controller.value, {MaterialState.disabled}); - expect(count, 11); - - await gesture.removePointer(); - }); - - testWidgets('Disabled ElevatedButton statesController', (WidgetTester tester) async { - int count = 0; - void valueChanged() { - count += 1; - } - final MaterialStatesController controller = MaterialStatesController(); - controller.addListener(valueChanged); - - await tester.pumpWidget( - MaterialApp( - home: Center( - child: ElevatedButton( - statesController: controller, - onPressed: null, - child: const Text('button'), - ), - ), - ), - ); - expect(controller.value, {MaterialState.disabled}); - expect(count, 1); - }); } TextStyle _iconStyle(WidgetTester tester, IconData icon) { diff --git a/packages/flutter/test/material/ink_well_test.dart b/packages/flutter/test/material/ink_well_test.dart index a9db62f105..13d76d2ea0 100644 --- a/packages/flutter/test/material/ink_well_test.dart +++ b/packages/flutter/test/material/ink_well_test.dart @@ -1513,47 +1513,4 @@ void main() { final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); expect(inkFeatures, paintsExactlyCountTimes(#drawCircle, 0)); }); - - testWidgets('InkWell dispose statesController', (WidgetTester tester) async { - int tapCount = 0; - Widget buildFrame(MaterialStatesController? statesController) { - return MaterialApp( - home: Scaffold( - body: Center( - child: InkWell( - statesController: statesController, - onTap: () { tapCount += 1; }, - child: const Text('inkwell'), - ), - ), - ), - ); - } - - final MaterialStatesController controller = MaterialStatesController(); - int pressedCount = 0; - controller.addListener(() { - if (controller.value.contains(MaterialState.pressed)) { - pressedCount += 1; - } - }); - - await tester.pumpWidget(buildFrame(controller)); - await tester.tap(find.byType(InkWell)); - await tester.pumpAndSettle(); - expect(tapCount, 1); - expect(pressedCount, 1); - - await tester.pumpWidget(buildFrame(null)); - await tester.tap(find.byType(InkWell)); - await tester.pumpAndSettle(); - expect(tapCount, 2); - expect(pressedCount, 1); - - await tester.pumpWidget(buildFrame(controller)); - await tester.tap(find.byType(InkWell)); - await tester.pumpAndSettle(); - expect(tapCount, 3); - expect(pressedCount, 2); - }); } diff --git a/packages/flutter/test/material/material_states_controller_test.dart b/packages/flutter/test/material/material_states_controller_test.dart deleted file mode 100644 index 3449e975b6..0000000000 --- a/packages/flutter/test/material/material_states_controller_test.dart +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - test('MaterialStatesController constructor', () { - expect(MaterialStatesController().value, {}); - expect(MaterialStatesController({}).value, {}); - expect(MaterialStatesController({MaterialState.selected}).value, {MaterialState.selected}); - }); - - test('MaterialStatesController update, listener', () { - int count = 0; - void valueChanged() { - count += 1; - } - final MaterialStatesController controller = MaterialStatesController(); - controller.addListener(valueChanged); - - controller.update(MaterialState.selected, true); - expect(controller.value, {MaterialState.selected}); - expect(count, 1); - controller.update(MaterialState.selected, true); - expect(controller.value, {MaterialState.selected}); - expect(count, 1); - - controller.update(MaterialState.hovered, false); - expect(count, 1); - expect(controller.value, {MaterialState.selected}); - controller.update(MaterialState.selected, false); - expect(count, 2); - expect(controller.value, {}); - - controller.update(MaterialState.hovered, true); - expect(controller.value, {MaterialState.hovered}); - expect(count, 3); - controller.update(MaterialState.hovered, true); - expect(controller.value, {MaterialState.hovered}); - expect(count, 3); - controller.update(MaterialState.pressed, true); - expect(controller.value, {MaterialState.hovered, MaterialState.pressed}); - expect(count, 4); - controller.update(MaterialState.selected, true); - expect(controller.value, {MaterialState.hovered, MaterialState.pressed, MaterialState.selected}); - expect(count, 5); - controller.update(MaterialState.selected, false); - expect(controller.value, {MaterialState.hovered, MaterialState.pressed}); - expect(count, 6); - controller.update(MaterialState.selected, false); - expect(controller.value, {MaterialState.hovered, MaterialState.pressed}); - expect(count, 6); - controller.update(MaterialState.pressed, false); - expect(controller.value, {MaterialState.hovered}); - expect(count, 7); - controller.update(MaterialState.hovered, false); - expect(controller.value, {}); - expect(count, 8); - - controller.removeListener(valueChanged); - controller.update(MaterialState.selected, true); - expect(controller.value, {MaterialState.selected}); - expect(count, 8); - }); - - - test('MaterialStatesController const initial value', () { - int count = 0; - void valueChanged() { - count += 1; - } - final MaterialStatesController controller = MaterialStatesController(const {MaterialState.selected}); - controller.addListener(valueChanged); - - controller.update(MaterialState.selected, true); - expect(controller.value, {MaterialState.selected}); - expect(count, 0); - - controller.update(MaterialState.selected, false); - expect(controller.value, {}); - expect(count, 1); - }); -} diff --git a/packages/flutter/test/material/outlined_button_test.dart b/packages/flutter/test/material/outlined_button_test.dart index b1a78e0195..4f1b2a34db 100644 --- a/packages/flutter/test/material/outlined_button_test.dart +++ b/packages/flutter/test/material/outlined_button_test.dart @@ -1717,123 +1717,6 @@ void main() { expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.basic); }); - - testWidgets('OutlinedButton statesController', (WidgetTester tester) async { - int count = 0; - void valueChanged() { - count += 1; - } - final MaterialStatesController controller = MaterialStatesController(); - controller.addListener(valueChanged); - - await tester.pumpWidget( - MaterialApp( - home: Center( - child: OutlinedButton( - statesController: controller, - onPressed: () { }, - child: const Text('button'), - ), - ), - ), - ); - - expect(controller.value, {}); - expect(count, 0); - - final Offset center = tester.getCenter(find.byType(OutlinedButton)); - final TestGesture gesture = await tester.createGesture( - kind: PointerDeviceKind.mouse, - ); - await gesture.addPointer(); - await gesture.moveTo(center); - await tester.pumpAndSettle(); - - expect(controller.value, {MaterialState.hovered}); - expect(count, 1); - - await gesture.moveTo(Offset.zero); - await tester.pumpAndSettle(); - - expect(controller.value, {}); - expect(count, 2); - - await gesture.moveTo(center); - await tester.pumpAndSettle(); - - expect(controller.value, {MaterialState.hovered}); - expect(count, 3); - - await gesture.down(center); - await tester.pumpAndSettle(); - - expect(controller.value, {MaterialState.hovered, MaterialState.pressed}); - expect(count, 4); - - await gesture.up(); - await tester.pumpAndSettle(); - - expect(controller.value, {MaterialState.hovered}); - expect(count, 5); - - await gesture.moveTo(Offset.zero); - await tester.pumpAndSettle(); - - expect(controller.value, {}); - expect(count, 6); - - await gesture.down(center); - await tester.pumpAndSettle(); - expect(controller.value, {MaterialState.hovered, MaterialState.pressed}); - expect(count, 8); // adds hovered and pressed - two changes - - // If the button is rebuilt disabled, then the pressed state is - // removed. - await tester.pumpWidget( - MaterialApp( - home: Center( - child: OutlinedButton( - statesController: controller, - onPressed: null, - child: const Text('button'), - ), - ), - ), - ); - await tester.pumpAndSettle(); - expect(controller.value, {MaterialState.hovered, MaterialState.disabled}); - expect(count, 10); // removes pressed and adds disabled - two changes - - await gesture.moveTo(Offset.zero); - await tester.pumpAndSettle(); - expect(controller.value, {MaterialState.disabled}); - expect(count, 11); - - await gesture.removePointer(); - }); - - testWidgets('Disabled OutlinedButton statesController', (WidgetTester tester) async { - int count = 0; - void valueChanged() { - count += 1; - } - final MaterialStatesController controller = MaterialStatesController(); - controller.addListener(valueChanged); - - await tester.pumpWidget( - MaterialApp( - home: Center( - child: OutlinedButton( - statesController: controller, - onPressed: null, - child: const Text('button'), - ), - ), - ), - ); - expect(controller.value, {MaterialState.disabled}); - expect(count, 1); - }); } TextStyle _iconStyle(WidgetTester tester, IconData icon) { diff --git a/packages/flutter/test/material/text_button_test.dart b/packages/flutter/test/material/text_button_test.dart index d950ad1733..36f3bf1b80 100644 --- a/packages/flutter/test/material/text_button_test.dart +++ b/packages/flutter/test/material/text_button_test.dart @@ -441,6 +441,7 @@ void main() { ), ); + final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); await gesture.addPointer(); await gesture.moveTo(tester.getCenter(find.byType(TextButton))); @@ -1524,123 +1525,6 @@ void main() { expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.basic); }); - - testWidgets('TextButton statesController', (WidgetTester tester) async { - int count = 0; - void valueChanged() { - count += 1; - } - final MaterialStatesController controller = MaterialStatesController(); - controller.addListener(valueChanged); - - await tester.pumpWidget( - MaterialApp( - home: Center( - child: TextButton( - statesController: controller, - onPressed: () { }, - child: const Text('button'), - ), - ), - ), - ); - - expect(controller.value, {}); - expect(count, 0); - - final Offset center = tester.getCenter(find.byType(TextButton)); - final TestGesture gesture = await tester.createGesture( - kind: PointerDeviceKind.mouse, - ); - await gesture.addPointer(); - await gesture.moveTo(center); - await tester.pumpAndSettle(); - - expect(controller.value, {MaterialState.hovered}); - expect(count, 1); - - await gesture.moveTo(Offset.zero); - await tester.pumpAndSettle(); - - expect(controller.value, {}); - expect(count, 2); - - await gesture.moveTo(center); - await tester.pumpAndSettle(); - - expect(controller.value, {MaterialState.hovered}); - expect(count, 3); - - await gesture.down(center); - await tester.pumpAndSettle(); - - expect(controller.value, {MaterialState.hovered, MaterialState.pressed}); - expect(count, 4); - - await gesture.up(); - await tester.pumpAndSettle(); - - expect(controller.value, {MaterialState.hovered}); - expect(count, 5); - - await gesture.moveTo(Offset.zero); - await tester.pumpAndSettle(); - - expect(controller.value, {}); - expect(count, 6); - - await gesture.down(center); - await tester.pumpAndSettle(); - expect(controller.value, {MaterialState.hovered, MaterialState.pressed}); - expect(count, 8); // adds hovered and pressed - two changes - - // If the button is rebuilt disabled, then the pressed state is - // removed. - await tester.pumpWidget( - MaterialApp( - home: Center( - child: TextButton( - statesController: controller, - onPressed: null, - child: const Text('button'), - ), - ), - ), - ); - await tester.pumpAndSettle(); - expect(controller.value, {MaterialState.hovered, MaterialState.disabled}); - expect(count, 10); // removes pressed and adds disabled - two changes - - await gesture.moveTo(Offset.zero); - await tester.pumpAndSettle(); - expect(controller.value, {MaterialState.disabled}); - expect(count, 11); - - await gesture.removePointer(); - }); - - testWidgets('Disabled TextButton statesController', (WidgetTester tester) async { - int count = 0; - void valueChanged() { - count += 1; - } - final MaterialStatesController controller = MaterialStatesController(); - controller.addListener(valueChanged); - - await tester.pumpWidget( - MaterialApp( - home: Center( - child: TextButton( - statesController: controller, - onPressed: null, - child: const Text('button'), - ), - ), - ), - ); - expect(controller.value, {MaterialState.disabled}); - expect(count, 1); - }); } TextStyle? _iconStyle(WidgetTester tester, IconData icon) {