Revert "Added MaterialStatesController, updated InkWell et al. (#103167)" (#105138) (#105316)
This reverts commit 180566f2e59928912c893cc0e8a12f97e653c206.
Co-authored-by: Hans Muller <hans.muller@gmail.com>
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 aa10c5a..0000000
--- 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<SelectableButton> createState() => _SelectableButtonState();
-
-}
-
-class _SelectableButtonState extends State<SelectableButton> {
- late final MaterialStatesController statesController;
-
- @override
- void initState() {
- super.initState();
- statesController = MaterialStatesController(<MaterialState>{
- 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<Home> createState() => _HomeState();
-}
-
-class _HomeState extends State<Home> {
- bool selected = false;
-
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- body: Center(
- child: SelectableButton(
- selected: selected,
- style: ButtonStyle(
- foregroundColor: MaterialStateProperty.resolveWith<Color?>(
- (Set<MaterialState> states) {
- if (states.contains(MaterialState.selected)) {
- return Colors.white;
- }
- return null; // defer to the defaults
- },
- ),
- backgroundColor: MaterialStateProperty.resolveWith<Color?>(
- (Set<MaterialState> 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 795e090..0000000
--- 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<example.SelectableButton>(button);
-
- Material buttonMaterial() {
- return tester.widget<Material>(
- 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 8b9da26..097aa3c 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 '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 @@
required this.focusNode,
required this.autofocus,
required this.clipBehavior,
- this.statesController,
required this.child,
}) : assert(autofocus != null),
assert(clipBehavior != null);
@@ -95,9 +95,6 @@
/// {@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 @@
/// * [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<ButtonStyleButton> 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<ButtonStyleButton> 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 @@
T? resolve<T>(MaterialStateProperty<T>? Function(ButtonStyle? style) getProperty) {
return effectiveValue(
- (ButtonStyle? style) {
- return getProperty(style)?.resolve(statesController.value);
- },
+ (ButtonStyle? style) => getProperty(style)?.resolve(materialStates),
);
}
@@ -284,7 +254,7 @@
final BorderSide? resolvedSide = resolve<BorderSide?>((ButtonStyle? style) => style?.side);
final OutlinedBorder? resolvedShape = resolve<OutlinedBorder?>((ButtonStyle? style) => style?.shape);
- final MaterialStateMouseCursor mouseCursor = _MouseCursor(
+ final MaterialStateMouseCursor resolvedMouseCursor = _MouseCursor(
(Set<MaterialState> states) => effectiveValue((ButtonStyle? style) => style?.mouseCursor?.resolve(states)),
);
@@ -339,16 +309,16 @@
// 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 @@
}
});
}
- 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 @@
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 f56a47b..cb57083 100644
--- a/packages/flutter/lib/src/material/elevated_button.dart
+++ b/packages/flutter/lib/src/material/elevated_button.dart
@@ -71,7 +71,6 @@
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 29dfe58..87ff2f8 100644
--- a/packages/flutter/lib/src/material/ink_well.dart
+++ b/packages/flutter/lib/src/material/ink_well.dart
@@ -319,7 +319,6 @@
this.canRequestFocus = true,
this.onFocusChange,
this.autofocus = false,
- this.statesController,
}) : assert(containedInkWell != null),
assert(highlightShape != null),
assert(enableFeedback != null),
@@ -582,19 +581,6 @@
/// 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 @@
parentState: parentState,
getRectCallback: getRectCallback,
debugCheckContext: debugCheckContext,
- statesController: statesController,
child: child,
);
}
@@ -680,7 +665,6 @@
this.parentState,
this.getRectCallback,
required this.debugCheckContext,
- this.statesController,
}) : assert(containedInkWell != null),
assert(highlightShape != null),
assert(enableFeedback != null),
@@ -718,7 +702,6 @@
final _ParentInkResponseState? parentState;
final _GetRectCallback? getRectCallback;
final _CheckContext debugCheckContext;
- final MaterialStatesController? statesController;
@override
_InkResponseState createState() => _InkResponseState();
@@ -755,18 +738,16 @@
}
class _InkResponseState extends State<_InkResponseStateWidget>
- with AutomaticKeepAliveClientMixin<_InkResponseStateWidget>
- implements _ParentInkResponseState
-{
+ with AutomaticKeepAliveClientMixin<_InkResponseStateWidget>
+ implements _ParentInkResponseState {
Set<InteractiveInkFeature>? _splashes;
InteractiveInkFeature? _currentSplash;
bool _hovering = false;
final Map<_HighlightType, InkHighlight?> _highlights = <_HighlightType, InkHighlight?>{};
late final Map<Type, Action<Intent>> _actionMap = <Type, Action<Intent>>{
- ActivateIntent: CallbackAction<ActivateIntent>(onInvoke: simulateTap),
- ButtonActivateIntent: CallbackAction<ButtonActivateIntent>(onInvoke: simulateTap),
+ ActivateIntent: CallbackAction<ActivateIntent>(onInvoke: _simulateTap),
+ ButtonActivateIntent: CallbackAction<ButtonActivateIntent>(onInvoke: _simulateTap),
};
- MaterialStatesController? internalStatesController;
bool get highlightsExist => _highlights.values.where((InkHighlight? highlight) => highlight != null).isNotEmpty;
@@ -788,65 +769,38 @@
}
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 @@
bool get wantKeepAlive => highlightsExist || (_splashes != null && _splashes!.isNotEmpty);
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);
- 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 @@
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 @@
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<MaterialState> pressed = <MaterialState>{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 @@
return splash;
}
- void handleFocusHighlightModeChange(FocusHighlightMode mode) {
+ void _handleFocusHighlightModeChange(FocusHighlightMode mode) {
if (!mounted) {
return;
}
setState(() {
- updateFocusHighlights();
+ _updateFocusHighlights();
});
}
@@ -1003,7 +947,7 @@
}
}
- void updateFocusHighlights() {
+ void _updateFocusHighlights() {
final bool showFocus;
switch (FocusManager.instance.highlightMode) {
case FocusHighlightMode.touch:
@@ -1017,18 +961,13 @@
}
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 @@
widget.onTapDown?.call(details);
}
- void handleTapUp(TapUpDetails details) {
+ void _handleTapUp(TapUpDetails details) {
widget.onTapUp?.call(details);
}
@@ -1051,7 +990,6 @@
} else {
globalPosition = details!.globalPosition;
}
- statesController.update(MaterialState.pressed, true); // ... before creating the splash
final InteractiveInkFeature splash = _createInkFeature(globalPosition);
_splashes ??= HashSet<InteractiveInkFeature>();
_splashes!.add(splash);
@@ -1061,7 +999,7 @@
updateHighlight(_HighlightType.pressed, value: true);
}
- void handleTap() {
+ void _handleTap() {
_currentSplash?.confirm();
_currentSplash = null;
updateHighlight(_HighlightType.pressed, value: false);
@@ -1073,21 +1011,21 @@
}
}
- 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 @@
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 @@
_highlights[type]?.color = getHighlightColorForType(type);
}
- _currentSplash?.color = widget.overlayColor?.resolve(statesController.value) ?? widget.splashColor ?? Theme.of(context).splashColor;
+ const Set<MaterialState> pressed = <MaterialState>{MaterialState.pressed};
+ _currentSplash?.color = widget.overlayColor?.resolve(pressed) ?? widget.splashColor ?? Theme.of(context).splashColor;
final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor>(
widget.mouseCursor ?? MaterialStateMouseCursor.clickable,
- statesController.value,
+ <MaterialState>{
+ if (!enabled) MaterialState.disabled,
+ if (_hovering && enabled) MaterialState.hovered,
+ if (_hasFocus) MaterialState.focused,
+ },
);
return _ParentInkResponseProvider(
@@ -1173,22 +1116,22 @@
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 @@
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 df0cb9f..282dc56 100644
--- a/packages/flutter/lib/src/material/material_state.dart
+++ b/packages/flutter/lib/src/material/material_state.dart
@@ -686,26 +686,3 @@
@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<Set<MaterialState>> {
- /// Creates a MaterialStatesController.
- MaterialStatesController([Set<MaterialState>? value]) : super(<MaterialState>{...?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 682d801..7e21de3 100644
--- a/packages/flutter/lib/src/material/outlined_button.dart
+++ b/packages/flutter/lib/src/material/outlined_button.dart
@@ -76,7 +76,6 @@
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 00c8d3e..e1936d5 100644
--- a/packages/flutter/lib/src/material/text_button.dart
+++ b/packages/flutter/lib/src/material/text_button.dart
@@ -57,13 +57,6 @@
/// ** 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 @@
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 d004a51..d40b1ef 100644
--- a/packages/flutter/test/material/elevated_button_test.dart
+++ b/packages/flutter/test/material/elevated_button_test.dart
@@ -1554,123 +1554,6 @@
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, <MaterialState>{});
- 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>{MaterialState.hovered});
- expect(count, 1);
-
- await gesture.moveTo(Offset.zero);
- await tester.pumpAndSettle();
-
- expect(controller.value, <MaterialState>{});
- expect(count, 2);
-
- await gesture.moveTo(center);
- await tester.pumpAndSettle();
-
- expect(controller.value, <MaterialState>{MaterialState.hovered});
- expect(count, 3);
-
- await gesture.down(center);
- await tester.pumpAndSettle();
-
- expect(controller.value, <MaterialState>{MaterialState.hovered, MaterialState.pressed});
- expect(count, 4);
-
- await gesture.up();
- await tester.pumpAndSettle();
-
- expect(controller.value, <MaterialState>{MaterialState.hovered});
- expect(count, 5);
-
- await gesture.moveTo(Offset.zero);
- await tester.pumpAndSettle();
-
- expect(controller.value, <MaterialState>{});
- expect(count, 6);
-
- await gesture.down(center);
- await tester.pumpAndSettle();
- expect(controller.value, <MaterialState>{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>{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>{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>{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 a9db62f..13d76d2 100644
--- a/packages/flutter/test/material/ink_well_test.dart
+++ b/packages/flutter/test/material/ink_well_test.dart
@@ -1513,47 +1513,4 @@
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 3449e97..0000000
--- 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, <MaterialState>{});
- expect(MaterialStatesController(<MaterialState>{}).value, <MaterialState>{});
- expect(MaterialStatesController(<MaterialState>{MaterialState.selected}).value, <MaterialState>{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>{MaterialState.selected});
- expect(count, 1);
- controller.update(MaterialState.selected, true);
- expect(controller.value, <MaterialState>{MaterialState.selected});
- expect(count, 1);
-
- controller.update(MaterialState.hovered, false);
- expect(count, 1);
- expect(controller.value, <MaterialState>{MaterialState.selected});
- controller.update(MaterialState.selected, false);
- expect(count, 2);
- expect(controller.value, <MaterialState>{});
-
- controller.update(MaterialState.hovered, true);
- expect(controller.value, <MaterialState>{MaterialState.hovered});
- expect(count, 3);
- controller.update(MaterialState.hovered, true);
- expect(controller.value, <MaterialState>{MaterialState.hovered});
- expect(count, 3);
- controller.update(MaterialState.pressed, true);
- expect(controller.value, <MaterialState>{MaterialState.hovered, MaterialState.pressed});
- expect(count, 4);
- controller.update(MaterialState.selected, true);
- expect(controller.value, <MaterialState>{MaterialState.hovered, MaterialState.pressed, MaterialState.selected});
- expect(count, 5);
- controller.update(MaterialState.selected, false);
- expect(controller.value, <MaterialState>{MaterialState.hovered, MaterialState.pressed});
- expect(count, 6);
- controller.update(MaterialState.selected, false);
- expect(controller.value, <MaterialState>{MaterialState.hovered, MaterialState.pressed});
- expect(count, 6);
- controller.update(MaterialState.pressed, false);
- expect(controller.value, <MaterialState>{MaterialState.hovered});
- expect(count, 7);
- controller.update(MaterialState.hovered, false);
- expect(controller.value, <MaterialState>{});
- expect(count, 8);
-
- controller.removeListener(valueChanged);
- controller.update(MaterialState.selected, true);
- expect(controller.value, <MaterialState>{MaterialState.selected});
- expect(count, 8);
- });
-
-
- test('MaterialStatesController const initial value', () {
- int count = 0;
- void valueChanged() {
- count += 1;
- }
- final MaterialStatesController controller = MaterialStatesController(const <MaterialState>{MaterialState.selected});
- controller.addListener(valueChanged);
-
- controller.update(MaterialState.selected, true);
- expect(controller.value, <MaterialState>{MaterialState.selected});
- expect(count, 0);
-
- controller.update(MaterialState.selected, false);
- expect(controller.value, <MaterialState>{});
- expect(count, 1);
- });
-}
diff --git a/packages/flutter/test/material/outlined_button_test.dart b/packages/flutter/test/material/outlined_button_test.dart
index b1a78e0..4f1b2a3 100644
--- a/packages/flutter/test/material/outlined_button_test.dart
+++ b/packages/flutter/test/material/outlined_button_test.dart
@@ -1717,123 +1717,6 @@
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, <MaterialState>{});
- 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>{MaterialState.hovered});
- expect(count, 1);
-
- await gesture.moveTo(Offset.zero);
- await tester.pumpAndSettle();
-
- expect(controller.value, <MaterialState>{});
- expect(count, 2);
-
- await gesture.moveTo(center);
- await tester.pumpAndSettle();
-
- expect(controller.value, <MaterialState>{MaterialState.hovered});
- expect(count, 3);
-
- await gesture.down(center);
- await tester.pumpAndSettle();
-
- expect(controller.value, <MaterialState>{MaterialState.hovered, MaterialState.pressed});
- expect(count, 4);
-
- await gesture.up();
- await tester.pumpAndSettle();
-
- expect(controller.value, <MaterialState>{MaterialState.hovered});
- expect(count, 5);
-
- await gesture.moveTo(Offset.zero);
- await tester.pumpAndSettle();
-
- expect(controller.value, <MaterialState>{});
- expect(count, 6);
-
- await gesture.down(center);
- await tester.pumpAndSettle();
- expect(controller.value, <MaterialState>{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>{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>{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>{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 d950ad1..36f3bf1 100644
--- a/packages/flutter/test/material/text_button_test.dart
+++ b/packages/flutter/test/material/text_button_test.dart
@@ -441,6 +441,7 @@
),
);
+
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer();
await gesture.moveTo(tester.getCenter(find.byType(TextButton)));
@@ -1524,123 +1525,6 @@
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, <MaterialState>{});
- 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>{MaterialState.hovered});
- expect(count, 1);
-
- await gesture.moveTo(Offset.zero);
- await tester.pumpAndSettle();
-
- expect(controller.value, <MaterialState>{});
- expect(count, 2);
-
- await gesture.moveTo(center);
- await tester.pumpAndSettle();
-
- expect(controller.value, <MaterialState>{MaterialState.hovered});
- expect(count, 3);
-
- await gesture.down(center);
- await tester.pumpAndSettle();
-
- expect(controller.value, <MaterialState>{MaterialState.hovered, MaterialState.pressed});
- expect(count, 4);
-
- await gesture.up();
- await tester.pumpAndSettle();
-
- expect(controller.value, <MaterialState>{MaterialState.hovered});
- expect(count, 5);
-
- await gesture.moveTo(Offset.zero);
- await tester.pumpAndSettle();
-
- expect(controller.value, <MaterialState>{});
- expect(count, 6);
-
- await gesture.down(center);
- await tester.pumpAndSettle();
- expect(controller.value, <MaterialState>{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>{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>{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>{MaterialState.disabled});
- expect(count, 1);
- });
}
TextStyle? _iconStyle(WidgetTester tester, IconData icon) {