1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
// 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>{}));
}
}