| // 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/foundation.dart'; |
| import 'package:flutter/widgets.dart'; |
| |
| import 'material_state.dart'; |
| |
| /// Mixin for [State] classes that require knowledge of changing [MaterialState] |
| /// values for their child widgets. |
| /// |
| /// This mixin does nothing by mere application to a [State] class, but is |
| /// helpful when writing `build` methods that include child [InkWell], |
| /// [GestureDetector], [MouseRegion], or [Focus] widgets. Instead of manually |
| /// creating handlers for each type of user interaction, such [State] classes can |
| /// instead provide a `ValueChanged<bool>` function and allow [MaterialStateMixin] |
| /// to manage the set of active [MaterialState]s, and the calling of [setState] |
| /// as necessary. |
| /// |
| /// {@tool snippet} |
| /// This example shows how to write a [StatefulWidget] that uses the |
| /// [MaterialStateMixin] class to watch [MaterialState] values. |
| /// |
| /// ```dart |
| /// class MyWidget extends StatefulWidget { |
| /// const MyWidget({required this.color, required this.child, Key? key}) : super(key: key); |
| /// |
| /// final MaterialStateColor color; |
| /// final Widget child; |
| /// |
| /// @override |
| /// State<MyWidget> createState() => MyWidgetState(); |
| /// } |
| /// |
| /// class MyWidgetState extends State<MyWidget> with MaterialStateMixin<MyWidget> { |
| /// @override |
| /// Widget build(BuildContext context) { |
| /// return InkWell( |
| /// onFocusChange: updateMaterialState(MaterialState.focused), |
| /// child: Container( |
| /// color: widget.color.resolve(materialStates), |
| /// child: widget.child, |
| /// ), |
| /// ); |
| /// } |
| /// } |
| /// ``` |
| /// {@end-tool} |
| @optionalTypeArgs |
| mixin MaterialStateMixin<T extends StatefulWidget> on State<T> { |
| /// Managed set of active [MaterialState] values; designed to be passed to |
| /// [MaterialStateProperty.resolve] methods. |
| /// |
| /// To mutate and have [setState] called automatically for you, use |
| /// [setMaterialState], [addMaterialState], or [removeMaterialState]. Directly |
| /// mutating the set is possible, and may be necessary if you need to alter its |
| /// list without calling [setState] (and thus triggering a re-render). |
| /// |
| /// To check for a single condition, convenience getters [isPressed], [isHovered], |
| /// [isFocused], etc, are available for each [MaterialState] value. |
| @protected |
| Set<MaterialState> materialStates = <MaterialState>{}; |
| |
| /// Callback factory which accepts a [MaterialState] value and returns a |
| /// closure to mutate [materialStates] and call [setState]. |
| /// |
| /// Accepts an optional second named parameter, `onChanged`, which allows |
| /// arbitrary functionality to be wired through the [MaterialStateMixin]. |
| /// If supplied, the [onChanged] function is only called when child widgets |
| /// report events that make changes to the current set of [MaterialState]s. |
| /// |
| /// {@tool snippet} |
| /// This example shows how to use the [updateMaterialState] callback factory |
| /// in other widgets, including the optional [onChanged] callback. |
| /// |
| /// ```dart |
| /// class MyWidget extends StatefulWidget { |
| /// const MyWidget({this.onPressed, Key? key}) : super(key: key); |
| /// |
| /// /// Something important this widget must do when pressed. |
| /// final VoidCallback? onPressed; |
| /// |
| /// @override |
| /// State<MyWidget> createState() => MyWidgetState(); |
| /// } |
| /// |
| /// class MyWidgetState extends State<MyWidget> with MaterialStateMixin<MyWidget> { |
| /// @override |
| /// Widget build(BuildContext context) { |
| /// return Container( |
| /// color: isPressed ? Colors.black : Colors.white, |
| /// child: InkWell( |
| /// onHighlightChanged: updateMaterialState( |
| /// MaterialState.pressed, |
| /// onChanged: (bool val) { |
| /// if (val) { |
| /// widget.onPressed?.call(); |
| /// } |
| /// }, |
| /// ), |
| /// ), |
| /// ); |
| /// } |
| /// } |
| /// ``` |
| /// {@end-tool} |
| @protected |
| ValueChanged<bool> updateMaterialState(MaterialState key, {ValueChanged<bool>? onChanged}) { |
| return (bool value) { |
| if (materialStates.contains(key) == value) |
| return; |
| setMaterialState(key, value); |
| onChanged?.call(value); |
| }; |
| } |
| |
| /// Mutator to mark a [MaterialState] value as either active or inactive. |
| @protected |
| void setMaterialState(MaterialState _state, bool isSet) { |
| return isSet ? addMaterialState(_state) : removeMaterialState(_state); |
| } |
| |
| /// Mutator to mark a [MaterialState] value as active. |
| @protected |
| void addMaterialState(MaterialState _state) { |
| if (materialStates.add(_state)) |
| setState((){}); |
| } |
| |
| /// Mutator to mark a [MaterialState] value as inactive. |
| @protected |
| void removeMaterialState(MaterialState _state) { |
| if (materialStates.remove(_state)) |
| setState((){}); |
| } |
| |
| /// Getter for whether this class considers [MaterialState.disabled] to be active. |
| bool get isDisabled => materialStates.contains(MaterialState.disabled); |
| |
| /// Getter for whether this class considers [MaterialState.dragged] to be active. |
| bool get isDragged => materialStates.contains(MaterialState.dragged); |
| |
| /// Getter for whether this class considers [MaterialState.error] to be active. |
| bool get isErrored => materialStates.contains(MaterialState.error); |
| |
| /// Getter for whether this class considers [MaterialState.focused] to be active. |
| bool get isFocused => materialStates.contains(MaterialState.focused); |
| |
| /// Getter for whether this class considers [MaterialState.hovered] to be active. |
| bool get isHovered => materialStates.contains(MaterialState.hovered); |
| |
| /// Getter for whether this class considers [MaterialState.pressed] to be active. |
| bool get isPressed => materialStates.contains(MaterialState.pressed); |
| |
| /// Getter for whether this class considers [MaterialState.scrolledUnder] to be active. |
| bool get isScrolledUnder => materialStates.contains(MaterialState.scrolledUnder); |
| |
| /// Getter for whether this class considers [MaterialState.selected] to be active. |
| bool get isSelected => materialStates.contains(MaterialState.selected); |
| |
| @override |
| void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
| super.debugFillProperties(properties); |
| properties.add(DiagnosticsProperty<Set<MaterialState>>('materialStates', materialStates, defaultValue: <MaterialState>{})); |
| } |
| } |