Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
328a262e
Unverified
Commit
328a262e
authored
Mar 02, 2021
by
Michael Goderbauer
Committed by
GitHub
Mar 02, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Revert "Toggable Refactor (#76745)" (#77068)
This reverts commit
14552a96
.
parent
9964e8fe
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
1307 additions
and
1048 deletions
+1307
-1048
checkbox.dart
packages/flutter/lib/src/material/checkbox.dart
+229
-116
radio.dart
packages/flutter/lib/src/material/radio.dart
+201
-62
switch.dart
packages/flutter/lib/src/material/switch.dart
+416
-304
toggleable.dart
packages/flutter/lib/src/material/toggleable.dart
+356
-430
checkbox_test.dart
packages/flutter/test/material/checkbox_test.dart
+35
-46
radio_test.dart
packages/flutter/test/material/radio_test.dart
+1
-26
switch_list_tile_test.dart
packages/flutter/test/material/switch_list_tile_test.dart
+1
-1
switch_test.dart
packages/flutter/test/material/switch_test.dart
+68
-63
No files found.
packages/flutter/lib/src/material/checkbox.dart
View file @
328a262e
...
...
@@ -290,39 +290,56 @@ class Checkbox extends StatefulWidget {
_CheckboxState
createState
()
=>
_CheckboxState
();
}
class
_CheckboxState
extends
State
<
Checkbox
>
with
TickerProviderStateMixin
,
ToggleableStateMixin
{
final
_CheckboxPainter
_painter
=
_CheckboxPainter
()
;
bool
?
_previousValue
;
class
_CheckboxState
extends
State
<
Checkbox
>
with
TickerProviderStateMixin
{
bool
get
enabled
=>
widget
.
onChanged
!=
null
;
late
Map
<
Type
,
Action
<
Intent
>>
_actionMap
;
@override
void
initState
()
{
super
.
initState
();
_previousValue
=
widget
.
value
;
_actionMap
=
<
Type
,
Action
<
Intent
>>{
ActivateIntent:
CallbackAction
<
ActivateIntent
>(
onInvoke:
_actionHandler
),
};
}
@override
void
didUpdateWidget
(
Checkbox
oldWidget
)
{
super
.
didUpdateWidget
(
oldWidget
);
if
(
oldWidget
.
value
!=
widget
.
value
)
{
_previousValue
=
oldWidget
.
value
;
animateToValue
();
void
_actionHandler
(
ActivateIntent
intent
)
{
if
(
widget
.
onChanged
!=
null
)
{
switch
(
widget
.
value
)
{
case
false
:
widget
.
onChanged
!(
true
);
break
;
case
true
:
widget
.
onChanged
!(
widget
.
tristate
?
null
:
false
);
break
;
case
null
:
widget
.
onChanged
!(
false
);
break
;
}
}
final
RenderObject
renderObject
=
context
.
findRenderObject
()!;
renderObject
.
sendSemanticsEvent
(
const
TapSemanticEvent
());
}
@override
void
dispose
()
{
_painter
.
dispose
();
super
.
dispose
();
bool
_focused
=
false
;
void
_handleFocusHighlightChanged
(
bool
focused
)
{
if
(
focused
!=
_focused
)
{
setState
(()
{
_focused
=
focused
;
});
}
}
@override
ValueChanged
<
bool
?>?
get
onChanged
=>
widget
.
onChanged
;
@override
bool
get
tristate
=>
widget
.
tristate
;
bool
_hovering
=
false
;
void
_handleHoverChanged
(
bool
hovering
)
{
if
(
hovering
!=
_hovering
)
{
setState
(()
{
_hovering
=
hovering
;
});
}
}
@override
bool
?
get
value
=>
widget
.
value
;
Set
<
MaterialState
>
get
_states
=>
<
MaterialState
>{
if
(!
enabled
)
MaterialState
.
disabled
,
if
(
_hovering
)
MaterialState
.
hovered
,
if
(
_focused
)
MaterialState
.
focused
,
if
(
widget
.
value
==
null
||
widget
.
value
!)
MaterialState
.
selected
,
};
MaterialStateProperty
<
Color
?>
get
_widgetFillColor
{
return
MaterialStateProperty
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
...
...
@@ -369,17 +386,14 @@ class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin, Togg
break
;
}
size
+=
effectiveVisualDensity
.
baseSizeAdjustment
;
final
MaterialStateProperty
<
MouseCursor
>
effectiveMouseCursor
=
MaterialStateProperty
.
resolveWith
<
MouseCursor
>((
Set
<
MaterialState
>
states
)
{
return
MaterialStateProperty
.
resolveAs
<
MouseCursor
?>(
widget
.
mouseCursor
,
states
)
??
themeData
.
checkboxTheme
.
mouseCursor
?.
resolve
(
states
)
??
MaterialStateMouseCursor
.
clickable
.
resolve
(
states
);
});
final
BoxConstraints
additionalConstraints
=
BoxConstraints
.
tight
(
size
);
final
MouseCursor
effectiveMouseCursor
=
MaterialStateProperty
.
resolveAs
<
MouseCursor
?>(
widget
.
mouseCursor
,
_states
)
??
themeData
.
checkboxTheme
.
mouseCursor
?.
resolve
(
_states
)
??
MaterialStateProperty
.
resolveAs
<
MouseCursor
>(
MaterialStateMouseCursor
.
clickable
,
_states
);
// Colors need to be resolved in selected and non selected states separately
// so that they can be lerped between.
final
Set
<
MaterialState
>
activeStates
=
states
..
add
(
MaterialState
.
selected
);
final
Set
<
MaterialState
>
inactiveStates
=
states
..
remove
(
MaterialState
.
selected
);
final
Set
<
MaterialState
>
activeStates
=
_
states
..
add
(
MaterialState
.
selected
);
final
Set
<
MaterialState
>
inactiveStates
=
_
states
..
remove
(
MaterialState
.
selected
);
final
Color
effectiveActiveColor
=
widget
.
fillColor
?.
resolve
(
activeStates
)
??
_widgetFillColor
.
resolve
(
activeStates
)
??
themeData
.
checkboxTheme
.
fillColor
?.
resolve
(
activeStates
)
...
...
@@ -389,13 +403,13 @@ class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin, Togg
??
themeData
.
checkboxTheme
.
fillColor
?.
resolve
(
inactiveStates
)
??
_defaultFillColor
.
resolve
(
inactiveStates
);
final
Set
<
MaterialState
>
focusedStates
=
states
..
add
(
MaterialState
.
focused
);
final
Set
<
MaterialState
>
focusedStates
=
_
states
..
add
(
MaterialState
.
focused
);
final
Color
effectiveFocusOverlayColor
=
widget
.
overlayColor
?.
resolve
(
focusedStates
)
??
widget
.
focusColor
??
themeData
.
checkboxTheme
.
overlayColor
?.
resolve
(
focusedStates
)
??
themeData
.
focusColor
;
final
Set
<
MaterialState
>
hoveredStates
=
states
..
add
(
MaterialState
.
hovered
);
final
Set
<
MaterialState
>
hoveredStates
=
_
states
..
add
(
MaterialState
.
hovered
);
final
Color
effectiveHoverOverlayColor
=
widget
.
overlayColor
?.
resolve
(
hoveredStates
)
??
widget
.
hoverColor
??
themeData
.
checkboxTheme
.
overlayColor
?.
resolve
(
hoveredStates
)
...
...
@@ -411,96 +425,195 @@ class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin, Togg
??
themeData
.
checkboxTheme
.
overlayColor
?.
resolve
(
inactivePressedStates
)
??
effectiveActiveColor
.
withAlpha
(
kRadialReactionAlpha
);
final
Color
effectiveCheckColor
=
widget
.
checkColor
??
themeData
.
checkboxTheme
.
checkColor
?.
resolve
(
states
)
final
Color
effectiveCheckColor
=
widget
.
checkColor
??
themeData
.
checkboxTheme
.
checkColor
?.
resolve
(
_
states
)
??
const
Color
(
0xFFFFFFFF
);
return
Semantics
(
checked:
widget
.
value
==
true
,
child:
buildToggleable
(
mouseCursor:
effectiveMouseCursor
,
focusNode:
widget
.
focusNode
,
autofocus:
widget
.
autofocus
,
size:
size
,
painter:
_painter
..
position
=
position
..
reaction
=
reaction
..
reactionFocusFade
=
reactionFocusFade
..
reactionHoverFade
=
reactionHoverFade
..
inactiveReactionColor
=
effectiveInactivePressedOverlayColor
..
reactionColor
=
effectiveActivePressedOverlayColor
..
hoverColor
=
effectiveHoverOverlayColor
..
focusColor
=
effectiveFocusOverlayColor
..
splashRadius
=
widget
.
splashRadius
??
themeData
.
checkboxTheme
.
splashRadius
??
kRadialReactionRadius
..
downPosition
=
downPosition
..
isFocused
=
states
.
contains
(
MaterialState
.
focused
)
..
isHovered
=
states
.
contains
(
MaterialState
.
hovered
)
..
activeColor
=
effectiveActiveColor
..
inactiveColor
=
effectiveInactiveColor
..
checkColor
=
effectiveCheckColor
..
value
=
value
..
previousValue
=
_previousValue
..
shape
=
widget
.
shape
??
themeData
.
checkboxTheme
.
shape
??
const
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
all
(
Radius
.
circular
(
1.0
))
)
..
side
=
widget
.
side
??
themeData
.
checkboxTheme
.
side
,
return
FocusableActionDetector
(
actions:
_actionMap
,
focusNode:
widget
.
focusNode
,
autofocus:
widget
.
autofocus
,
enabled:
enabled
,
onShowFocusHighlight:
_handleFocusHighlightChanged
,
onShowHoverHighlight:
_handleHoverChanged
,
mouseCursor:
effectiveMouseCursor
,
child:
Builder
(
builder:
(
BuildContext
context
)
{
return
_CheckboxRenderObjectWidget
(
value:
widget
.
value
,
tristate:
widget
.
tristate
,
activeColor:
effectiveActiveColor
,
checkColor:
effectiveCheckColor
,
inactiveColor:
effectiveInactiveColor
,
focusColor:
effectiveFocusOverlayColor
,
hoverColor:
effectiveHoverOverlayColor
,
reactionColor:
effectiveActivePressedOverlayColor
,
inactiveReactionColor:
effectiveInactivePressedOverlayColor
,
splashRadius:
widget
.
splashRadius
??
themeData
.
checkboxTheme
.
splashRadius
??
kRadialReactionRadius
,
onChanged:
widget
.
onChanged
,
additionalConstraints:
additionalConstraints
,
vsync:
this
,
hasFocus:
_focused
,
hovering:
_hovering
,
side:
widget
.
side
??
themeData
.
checkboxTheme
.
side
,
shape:
widget
.
shape
??
themeData
.
checkboxTheme
.
shape
??
const
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
all
(
Radius
.
circular
(
1.0
)),
),
);
},
),
);
}
}
const
double
_kEdgeSize
=
Checkbox
.
width
;
const
double
_kStrokeWidth
=
2.0
;
class
_CheckboxRenderObjectWidget
extends
LeafRenderObjectWidget
{
const
_CheckboxRenderObjectWidget
({
Key
?
key
,
required
this
.
value
,
required
this
.
tristate
,
required
this
.
activeColor
,
required
this
.
checkColor
,
required
this
.
inactiveColor
,
required
this
.
focusColor
,
required
this
.
hoverColor
,
required
this
.
reactionColor
,
required
this
.
inactiveReactionColor
,
required
this
.
splashRadius
,
required
this
.
onChanged
,
required
this
.
vsync
,
required
this
.
additionalConstraints
,
required
this
.
hasFocus
,
required
this
.
hovering
,
required
this
.
shape
,
required
this
.
side
,
})
:
assert
(
tristate
!=
null
),
assert
(
tristate
||
value
!=
null
),
assert
(
activeColor
!=
null
),
assert
(
inactiveColor
!=
null
),
assert
(
vsync
!=
null
),
super
(
key:
key
);
class
_CheckboxPainter
extends
ToggleablePainter
{
Color
get
checkColor
=>
_checkColor
!;
Color
?
_checkColor
;
set
checkColor
(
Color
value
)
{
if
(
_checkColor
==
value
)
{
return
;
}
_checkColor
=
value
;
notifyListeners
();
}
final
bool
?
value
;
final
bool
tristate
;
final
bool
hasFocus
;
final
bool
hovering
;
final
Color
activeColor
;
final
Color
checkColor
;
final
Color
inactiveColor
;
final
Color
focusColor
;
final
Color
hoverColor
;
final
Color
reactionColor
;
final
Color
inactiveReactionColor
;
final
double
splashRadius
;
final
ValueChanged
<
bool
?>?
onChanged
;
final
TickerProvider
vsync
;
final
BoxConstraints
additionalConstraints
;
final
OutlinedBorder
shape
;
final
BorderSide
?
side
;
bool
?
get
value
=>
_value
;
bool
?
_value
;
set
value
(
bool
?
value
)
{
if
(
_value
==
value
)
{
return
;
}
_value
=
value
;
notifyListeners
();
}
@override
_RenderCheckbox
createRenderObject
(
BuildContext
context
)
=>
_RenderCheckbox
(
value:
value
,
tristate:
tristate
,
activeColor:
activeColor
,
checkColor:
checkColor
,
inactiveColor:
inactiveColor
,
focusColor:
focusColor
,
hoverColor:
hoverColor
,
reactionColor:
reactionColor
,
inactiveReactionColor:
inactiveReactionColor
,
splashRadius:
splashRadius
,
onChanged:
onChanged
,
vsync:
vsync
,
additionalConstraints:
additionalConstraints
,
hasFocus:
hasFocus
,
hovering:
hovering
,
shape:
shape
,
side:
side
,
);
bool
?
get
previousValue
=>
_previousValue
;
bool
?
_previousValue
;
set
previousValue
(
bool
?
value
)
{
if
(
_previousValue
==
value
)
{
return
;
}
_previousValue
=
value
;
notifyListeners
();
@override
void
updateRenderObject
(
BuildContext
context
,
_RenderCheckbox
renderObject
)
{
renderObject
// The `tristate` must be changed before `value` due to the assertion at
// the beginning of `set value`.
..
tristate
=
tristate
..
value
=
value
..
activeColor
=
activeColor
..
checkColor
=
checkColor
..
inactiveColor
=
inactiveColor
..
focusColor
=
focusColor
..
hoverColor
=
hoverColor
..
reactionColor
=
reactionColor
..
inactiveReactionColor
=
inactiveReactionColor
..
splashRadius
=
splashRadius
..
onChanged
=
onChanged
..
additionalConstraints
=
additionalConstraints
..
vsync
=
vsync
..
hasFocus
=
hasFocus
..
hovering
=
hovering
..
shape
=
shape
..
side
=
side
;
}
}
const
double
_kEdgeSize
=
Checkbox
.
width
;
const
double
_kStrokeWidth
=
2.0
;
class
_RenderCheckbox
extends
RenderToggleable
{
_RenderCheckbox
({
bool
?
value
,
required
bool
tristate
,
required
Color
activeColor
,
required
this
.
checkColor
,
required
Color
inactiveColor
,
Color
?
focusColor
,
Color
?
hoverColor
,
Color
?
reactionColor
,
Color
?
inactiveReactionColor
,
required
double
splashRadius
,
required
BoxConstraints
additionalConstraints
,
ValueChanged
<
bool
?>?
onChanged
,
required
bool
hasFocus
,
required
bool
hovering
,
required
this
.
shape
,
required
this
.
side
,
required
TickerProvider
vsync
,
})
:
_oldValue
=
value
,
super
(
value:
value
,
tristate:
tristate
,
activeColor:
activeColor
,
inactiveColor:
inactiveColor
,
focusColor:
focusColor
,
hoverColor:
hoverColor
,
reactionColor:
reactionColor
,
inactiveReactionColor:
inactiveReactionColor
,
splashRadius:
splashRadius
,
onChanged:
onChanged
,
additionalConstraints:
additionalConstraints
,
vsync:
vsync
,
hasFocus:
hasFocus
,
hovering:
hovering
,
);
bool
?
_oldValue
;
Color
checkColor
;
OutlinedBorder
shape
;
BorderSide
?
side
;
OutlinedBorder
get
shape
=>
_shape
!;
OutlinedBorder
?
_shape
;
set
shape
(
OutlinedBorder
value
)
{
if
(
_shape
==
value
)
{
@override
set
value
(
bool
?
newValue
)
{
if
(
newValue
==
value
)
return
;
}
_shape
=
value
;
notifyListeners
();
_oldValue
=
value
;
super
.
value
=
newValue
;
}
BorderSide
?
get
side
=>
_side
;
BorderSide
?
_side
;
set
side
(
BorderSide
?
value
)
{
if
(
_side
==
value
)
{
return
;
}
_side
=
value
;
notifyListeners
();
@override
void
describeSemanticsConfiguration
(
SemanticsConfiguration
config
)
{
super
.
describeSemanticsConfiguration
(
config
);
config
.
isChecked
=
value
==
true
;
}
// The square outer bounds of the checkbox at t, with the specified origin.
...
...
@@ -531,11 +644,10 @@ class _CheckboxPainter extends ToggleablePainter {
void
_drawBorder
(
Canvas
canvas
,
Rect
outer
,
double
t
,
Paint
paint
)
{
assert
(
t
>=
0.0
&&
t
<=
0.5
);
OutlinedBorder
resolvedShape
=
shape
;
if
(
side
==
null
)
{
resolvedShape
=
resolvedS
hape
.
copyWith
(
side:
BorderSide
(
width:
2
,
color:
paint
.
color
));
shape
=
s
hape
.
copyWith
(
side:
BorderSide
(
width:
2
,
color:
paint
.
color
));
}
resolvedS
hape
.
copyWith
(
side:
side
).
paint
(
canvas
,
outer
);
s
hape
.
copyWith
(
side:
side
).
paint
(
canvas
,
outer
);
}
void
_drawCheck
(
Canvas
canvas
,
Offset
origin
,
double
t
,
Paint
paint
)
{
...
...
@@ -574,18 +686,19 @@ class _CheckboxPainter extends ToggleablePainter {
}
@override
void
paint
(
Canvas
canvas
,
Size
size
)
{
paintRadialReaction
(
canvas:
canvas
,
origin:
size
.
center
(
Offset
.
zero
));
void
paint
(
PaintingContext
context
,
Offset
offset
)
{
final
Canvas
canvas
=
context
.
canvas
;
paintRadialReaction
(
canvas
,
offset
,
size
.
center
(
Offset
.
zero
));
final
Paint
strokePaint
=
_createStrokePaint
();
final
Offset
origin
=
size
/
2.0
-
const
Size
.
square
(
_kEdgeSize
)
/
2.0
as
Offset
;
final
Offset
origin
=
offset
+
(
size
/
2.0
-
const
Size
.
square
(
_kEdgeSize
)
/
2.0
as
Offset
)
;
final
AnimationStatus
status
=
position
.
status
;
final
double
tNormalized
=
status
==
AnimationStatus
.
forward
||
status
==
AnimationStatus
.
completed
?
position
.
value
:
1.0
-
position
.
value
;
// Four cases: false to null, false to true, null to false, true to false
if
(
previous
Value
==
false
||
value
==
false
)
{
if
(
_old
Value
==
false
||
value
==
false
)
{
final
double
t
=
value
==
false
?
1.0
-
tNormalized
:
tNormalized
;
final
Rect
outer
=
_outerRectAt
(
origin
,
t
);
final
Path
emptyCheckboxPath
=
shape
.
copyWith
(
side:
side
).
getOuterPath
(
outer
);
...
...
@@ -597,7 +710,7 @@ class _CheckboxPainter extends ToggleablePainter {
canvas
.
drawPath
(
emptyCheckboxPath
,
paint
);
final
double
tShrink
=
(
t
-
0.5
)
*
2.0
;
if
(
previous
Value
==
null
||
value
==
null
)
if
(
_old
Value
==
null
||
value
==
null
)
_drawDash
(
canvas
,
origin
,
tShrink
,
strokePaint
);
else
_drawCheck
(
canvas
,
origin
,
tShrink
,
strokePaint
);
...
...
@@ -609,7 +722,7 @@ class _CheckboxPainter extends ToggleablePainter {
if
(
tNormalized
<=
0.5
)
{
final
double
tShrink
=
1.0
-
tNormalized
*
2.0
;
if
(
previous
Value
==
true
)
if
(
_old
Value
==
true
)
_drawCheck
(
canvas
,
origin
,
tShrink
,
strokePaint
);
else
_drawDash
(
canvas
,
origin
,
tShrink
,
strokePaint
);
...
...
packages/flutter/lib/src/material/radio.dart
View file @
328a262e
...
...
@@ -353,47 +353,64 @@ class Radio<T> extends StatefulWidget {
/// {@macro flutter.widgets.Focus.autofocus}
final
bool
autofocus
;
bool
get
_selected
=>
value
==
groupValue
;
@override
_RadioState
<
T
>
createState
()
=>
_RadioState
<
T
>();
}
class
_RadioState
<
T
>
extends
State
<
Radio
<
T
>>
with
TickerProviderStateMixin
,
ToggleableStateMixin
{
final
_RadioPainter
_painter
=
_RadioPainter
();
class
_RadioState
<
T
>
extends
State
<
Radio
<
T
>>
with
TickerProviderStateMixin
{
bool
get
enabled
=>
widget
.
onChanged
!=
null
;
late
Map
<
Type
,
Action
<
Intent
>>
_actionMap
;
void
_handleChanged
(
bool
?
selected
)
{
if
(
selected
==
null
)
{
widget
.
onChanged
!(
null
);
return
;
}
if
(
selected
)
{
@override
void
initState
()
{
super
.
initState
();
_actionMap
=
<
Type
,
Action
<
Intent
>>{
ActivateIntent:
CallbackAction
<
ActivateIntent
>(
onInvoke:
_actionHandler
,
),
};
}
void
_actionHandler
(
ActivateIntent
intent
)
{
if
(
widget
.
onChanged
!=
null
)
{
widget
.
onChanged
!(
widget
.
value
);
}
final
RenderObject
renderObject
=
context
.
findRenderObject
()!;
renderObject
.
sendSemanticsEvent
(
const
TapSemanticEvent
());
}
@override
void
didUpdateWidget
(
Radio
<
T
>
oldWidget
)
{
super
.
didUpdateWidget
(
oldWidget
);
if
(
widget
.
_selected
!=
oldWidget
.
_selected
)
{
animateToValue
();
bool
_focused
=
false
;
void
_handleHighlightChanged
(
bool
focused
)
{
if
(
_focused
!=
focused
)
{
setState
(()
{
_focused
=
focused
;
});
}
}
@override
void
dispose
()
{
_painter
.
dispose
();
super
.
dispose
();
bool
_hovering
=
false
;
void
_handleHoverChanged
(
bool
hovering
)
{
if
(
_hovering
!=
hovering
)
{
setState
(()
{
_hovering
=
hovering
;
});
}
}
@override
ValueChanged
<
bool
?>?
get
onChanged
=>
widget
.
onChanged
!=
null
?
_handleChanged
:
null
;
void
_handleChanged
(
bool
?
selected
)
{
if
(
selected
==
null
)
{
widget
.
onChanged
!(
null
);
return
;
}
if
(
selected
)
{
widget
.
onChanged
!(
widget
.
value
);
}
}
@override
bool
get
tristate
=>
widget
.
toggleable
;
bool
get
_selected
=>
widget
.
value
==
widget
.
groupValue
;
@override
bool
?
get
value
=>
widget
.
_selected
;
Set
<
MaterialState
>
get
_states
=>
<
MaterialState
>{
if
(!
enabled
)
MaterialState
.
disabled
,
if
(
_hovering
)
MaterialState
.
hovered
,
if
(
_focused
)
MaterialState
.
focused
,
if
(
_selected
)
MaterialState
.
selected
,
};
MaterialStateProperty
<
Color
?>
get
_widgetFillColor
{
return
MaterialStateProperty
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
...
...
@@ -440,17 +457,15 @@ class _RadioState<T> extends State<Radio<T>> with TickerProviderStateMixin, Togg
break
;
}
size
+=
effectiveVisualDensity
.
baseSizeAdjustment
;
final
MaterialStateProperty
<
MouseCursor
>
effectiveMouseCursor
=
MaterialStateProperty
.
resolveWith
<
MouseCursor
>((
Set
<
MaterialState
>
states
)
{
return
MaterialStateProperty
.
resolveAs
<
MouseCursor
?>(
widget
.
mouseCursor
,
states
)
??
themeData
.
radioTheme
.
mouseCursor
?.
resolve
(
states
)
??
MaterialStateProperty
.
resolveAs
<
MouseCursor
>(
MaterialStateMouseCursor
.
clickable
,
states
);
});
final
BoxConstraints
additionalConstraints
=
BoxConstraints
.
tight
(
size
);
final
MouseCursor
effectiveMouseCursor
=
MaterialStateProperty
.
resolveAs
<
MouseCursor
?>(
widget
.
mouseCursor
,
_states
)
??
themeData
.
radioTheme
.
mouseCursor
?.
resolve
(
_states
)
??
MaterialStateProperty
.
resolveAs
<
MouseCursor
>(
MaterialStateMouseCursor
.
clickable
,
_states
);
// Colors need to be resolved in selected and non selected states separately
// so that they can be lerped between.
final
Set
<
MaterialState
>
activeStates
=
states
..
add
(
MaterialState
.
selected
);
final
Set
<
MaterialState
>
inactiveStates
=
states
..
remove
(
MaterialState
.
selected
);
final
Set
<
MaterialState
>
activeStates
=
_
states
..
add
(
MaterialState
.
selected
);
final
Set
<
MaterialState
>
inactiveStates
=
_
states
..
remove
(
MaterialState
.
selected
);
final
Color
effectiveActiveColor
=
widget
.
fillColor
?.
resolve
(
activeStates
)
??
_widgetFillColor
.
resolve
(
activeStates
)
??
themeData
.
radioTheme
.
fillColor
?.
resolve
(
activeStates
)
...
...
@@ -460,13 +475,13 @@ class _RadioState<T> extends State<Radio<T>> with TickerProviderStateMixin, Togg
??
themeData
.
radioTheme
.
fillColor
?.
resolve
(
inactiveStates
)
??
_defaultFillColor
.
resolve
(
inactiveStates
);
final
Set
<
MaterialState
>
focusedStates
=
states
..
add
(
MaterialState
.
focused
);
final
Set
<
MaterialState
>
focusedStates
=
_
states
..
add
(
MaterialState
.
focused
);
final
Color
effectiveFocusOverlayColor
=
widget
.
overlayColor
?.
resolve
(
focusedStates
)
??
widget
.
focusColor
??
themeData
.
radioTheme
.
overlayColor
?.
resolve
(
focusedStates
)
??
themeData
.
focusColor
;
final
Set
<
MaterialState
>
hoveredStates
=
states
..
add
(
MaterialState
.
hovered
);
final
Set
<
MaterialState
>
hoveredStates
=
_
states
..
add
(
MaterialState
.
hovered
);
final
Color
effectiveHoverOverlayColor
=
widget
.
overlayColor
?.
resolve
(
hoveredStates
)
??
widget
.
hoverColor
??
themeData
.
radioTheme
.
overlayColor
?.
resolve
(
hoveredStates
)
...
...
@@ -482,40 +497,156 @@ class _RadioState<T> extends State<Radio<T>> with TickerProviderStateMixin, Togg
??
themeData
.
radioTheme
.
overlayColor
?.
resolve
(
inactivePressedStates
)
??
effectiveActiveColor
.
withAlpha
(
kRadialReactionAlpha
);
return
Semantics
(
inMutuallyExclusiveGroup:
true
,
checked:
widget
.
_selected
,
child:
buildToggleable
(
focusNode:
widget
.
focusNode
,
autofocus:
widget
.
autofocus
,
mouseCursor:
effectiveMouseCursor
,
size:
size
,
painter:
_painter
..
position
=
position
..
reaction
=
reaction
..
reactionFocusFade
=
reactionFocusFade
..
reactionHoverFade
=
reactionHoverFade
..
inactiveReactionColor
=
effectiveInactivePressedOverlayColor
..
reactionColor
=
effectiveActivePressedOverlayColor
..
hoverColor
=
effectiveHoverOverlayColor
..
focusColor
=
effectiveFocusOverlayColor
..
splashRadius
=
widget
.
splashRadius
??
themeData
.
radioTheme
.
splashRadius
??
kRadialReactionRadius
..
downPosition
=
downPosition
..
isFocused
=
states
.
contains
(
MaterialState
.
focused
)
..
isHovered
=
states
.
contains
(
MaterialState
.
hovered
)
..
activeColor
=
effectiveActiveColor
..
inactiveColor
=
effectiveInactiveColor
return
FocusableActionDetector
(
actions:
_actionMap
,
focusNode:
widget
.
focusNode
,
autofocus:
widget
.
autofocus
,
mouseCursor:
effectiveMouseCursor
,
enabled:
enabled
,
onShowFocusHighlight:
_handleHighlightChanged
,
onShowHoverHighlight:
_handleHoverChanged
,
child:
Builder
(
builder:
(
BuildContext
context
)
{
return
_RadioRenderObjectWidget
(
selected:
_selected
,
activeColor:
effectiveActiveColor
,
inactiveColor:
effectiveInactiveColor
,
focusColor:
effectiveFocusOverlayColor
,
hoverColor:
effectiveHoverOverlayColor
,
reactionColor:
effectiveActivePressedOverlayColor
,
inactiveReactionColor:
effectiveInactivePressedOverlayColor
,
splashRadius:
widget
.
splashRadius
??
themeData
.
radioTheme
.
splashRadius
??
kRadialReactionRadius
,
onChanged:
enabled
?
_handleChanged
:
null
,
toggleable:
widget
.
toggleable
,
additionalConstraints:
additionalConstraints
,
vsync:
this
,
hasFocus:
_focused
,
hovering:
_hovering
,
);
},
),
);
}
}
class
_RadioPainter
extends
ToggleablePainter
{
class
_RadioRenderObjectWidget
extends
LeafRenderObjectWidget
{
const
_RadioRenderObjectWidget
({
Key
?
key
,
required
this
.
selected
,
required
this
.
activeColor
,
required
this
.
inactiveColor
,
required
this
.
focusColor
,
required
this
.
hoverColor
,
required
this
.
reactionColor
,
required
this
.
inactiveReactionColor
,
required
this
.
additionalConstraints
,
this
.
onChanged
,
required
this
.
toggleable
,
required
this
.
vsync
,
required
this
.
hasFocus
,
required
this
.
hovering
,
required
this
.
splashRadius
,
})
:
assert
(
selected
!=
null
),
assert
(
activeColor
!=
null
),
assert
(
inactiveColor
!=
null
),
assert
(
vsync
!=
null
),
assert
(
toggleable
!=
null
),
super
(
key:
key
);
final
bool
selected
;
final
bool
hasFocus
;
final
bool
hovering
;
final
Color
inactiveColor
;
final
Color
activeColor
;
final
Color
focusColor
;
final
Color
hoverColor
;
final
Color
reactionColor
;
final
Color
inactiveReactionColor
;
final
double
splashRadius
;
final
ValueChanged
<
bool
?>?
onChanged
;
final
bool
toggleable
;
final
TickerProvider
vsync
;
final
BoxConstraints
additionalConstraints
;
@override
void
paint
(
Canvas
canvas
,
Size
size
)
{
paintRadialReaction
(
canvas:
canvas
,
origin:
size
.
center
(
Offset
.
zero
));
_RenderRadio
createRenderObject
(
BuildContext
context
)
=>
_RenderRadio
(
value:
selected
,
activeColor:
activeColor
,
inactiveColor:
inactiveColor
,
focusColor:
focusColor
,
hoverColor:
hoverColor
,
reactionColor:
reactionColor
,
inactiveReactionColor:
inactiveReactionColor
,
splashRadius:
splashRadius
,
onChanged:
onChanged
,
tristate:
toggleable
,
vsync:
vsync
,
additionalConstraints:
additionalConstraints
,
hasFocus:
hasFocus
,
hovering:
hovering
,
);
final
Offset
center
=
(
Offset
.
zero
&
size
).
center
;
@override
void
updateRenderObject
(
BuildContext
context
,
_RenderRadio
renderObject
)
{
renderObject
..
value
=
selected
..
activeColor
=
activeColor
..
inactiveColor
=
inactiveColor
..
focusColor
=
focusColor
..
hoverColor
=
hoverColor
..
reactionColor
=
reactionColor
..
inactiveReactionColor
=
inactiveReactionColor
..
splashRadius
=
splashRadius
..
onChanged
=
onChanged
..
tristate
=
toggleable
..
additionalConstraints
=
additionalConstraints
..
vsync
=
vsync
..
hasFocus
=
hasFocus
..
hovering
=
hovering
;
}
}
class
_RenderRadio
extends
RenderToggleable
{
_RenderRadio
({
required
bool
value
,
required
Color
activeColor
,
required
Color
inactiveColor
,
required
Color
focusColor
,
required
Color
hoverColor
,
required
Color
reactionColor
,
required
Color
inactiveReactionColor
,
required
double
splashRadius
,
required
ValueChanged
<
bool
?>?
onChanged
,
required
bool
tristate
,
required
BoxConstraints
additionalConstraints
,
required
TickerProvider
vsync
,
required
bool
hasFocus
,
required
bool
hovering
,
})
:
super
(
value:
value
,
activeColor:
activeColor
,
inactiveColor:
inactiveColor
,
focusColor:
focusColor
,
hoverColor:
hoverColor
,
reactionColor:
reactionColor
,
inactiveReactionColor:
inactiveReactionColor
,
splashRadius:
splashRadius
,
onChanged:
onChanged
,
tristate:
tristate
,
additionalConstraints:
additionalConstraints
,
vsync:
vsync
,
hasFocus:
hasFocus
,
hovering:
hovering
,
);
@override
void
paint
(
PaintingContext
context
,
Offset
offset
)
{
final
Canvas
canvas
=
context
.
canvas
;
paintRadialReaction
(
canvas
,
offset
,
size
.
center
(
Offset
.
zero
));
final
Offset
center
=
(
offset
&
size
).
center
;
// Outer circle
final
Paint
paint
=
Paint
()
...
...
@@ -530,4 +661,12 @@ class _RadioPainter extends ToggleablePainter {
canvas
.
drawCircle
(
center
,
_kInnerRadius
*
position
.
value
,
paint
);
}
}
@override
void
describeSemanticsConfiguration
(
SemanticsConfiguration
config
)
{
super
.
describeSemanticsConfiguration
(
config
);
config
..
isInMutuallyExclusiveGroup
=
true
..
isChecked
=
value
==
true
;
}
}
packages/flutter/lib/src/material/switch.dart
View file @
328a262e
...
...
@@ -51,7 +51,7 @@ enum _SwitchType { material, adaptive }
/// * [Radio], for selecting among a set of explicit values.
/// * [Slider], for selecting a value in a range.
/// * <https://material.io/design/components/selection-controls.html#switches>
class
Switch
extends
State
less
Widget
{
class
Switch
extends
State
ful
Widget
{
/// Creates a material design switch.
///
/// The switch itself does not maintain any state. Instead, when the state of
...
...
@@ -359,87 +359,8 @@ class Switch extends StatelessWidget {
/// {@macro flutter.widgets.Focus.autofocus}
final
bool
autofocus
;
Size
_getSwitchSize
(
ThemeData
theme
)
{
final
MaterialTapTargetSize
effectiveMaterialTapTargetSize
=
materialTapTargetSize
??
theme
.
switchTheme
.
materialTapTargetSize
??
theme
.
materialTapTargetSize
;
switch
(
effectiveMaterialTapTargetSize
)
{
case
MaterialTapTargetSize
.
padded
:
return
const
Size
(
_kSwitchWidth
,
_kSwitchHeight
);
case
MaterialTapTargetSize
.
shrinkWrap
:
return
const
Size
(
_kSwitchWidth
,
_kSwitchHeightCollapsed
);
}
}
Widget
_buildCupertinoSwitch
(
BuildContext
context
)
{
final
Size
size
=
_getSwitchSize
(
Theme
.
of
(
context
));
return
Focus
(
focusNode:
focusNode
,
autofocus:
autofocus
,
child:
Container
(
width:
size
.
width
,
// Same size as the Material switch.
height:
size
.
height
,
alignment:
Alignment
.
center
,
child:
CupertinoSwitch
(
dragStartBehavior:
dragStartBehavior
,
value:
value
,
onChanged:
onChanged
,
activeColor:
activeColor
,
trackColor:
inactiveTrackColor
),
),
);
}
Widget
_buildMaterialSwitch
(
BuildContext
context
)
{
return
_MaterialSwitch
(
value:
value
,
onChanged:
onChanged
,
size:
_getSwitchSize
(
Theme
.
of
(
context
)),
activeColor:
activeColor
,
activeTrackColor:
activeTrackColor
,
inactiveThumbColor:
inactiveThumbColor
,
inactiveTrackColor:
inactiveTrackColor
,
activeThumbImage:
activeThumbImage
,
onActiveThumbImageError:
onActiveThumbImageError
,
inactiveThumbImage:
inactiveThumbImage
,
onInactiveThumbImageError:
onInactiveThumbImageError
,
thumbColor:
thumbColor
,
trackColor:
trackColor
,
materialTapTargetSize:
materialTapTargetSize
,
dragStartBehavior:
dragStartBehavior
,
mouseCursor:
mouseCursor
,
focusColor:
focusColor
,
hoverColor:
hoverColor
,
overlayColor:
overlayColor
,
splashRadius:
splashRadius
,
focusNode:
focusNode
,
autofocus:
autofocus
,
);
}
@override
Widget
build
(
BuildContext
context
)
{
switch
(
_switchType
)
{
case
_SwitchType
.
material
:
return
_buildMaterialSwitch
(
context
);
case
_SwitchType
.
adaptive
:
{
final
ThemeData
theme
=
Theme
.
of
(
context
);
assert
(
theme
.
platform
!=
null
);
switch
(
theme
.
platform
)
{
case
TargetPlatform
.
android
:
case
TargetPlatform
.
fuchsia
:
case
TargetPlatform
.
linux
:
case
TargetPlatform
.
windows
:
return
_buildMaterialSwitch
(
context
);
case
TargetPlatform
.
iOS
:
case
TargetPlatform
.
macOS
:
return
_buildCupertinoSwitch
(
context
);
}
}
}
}
_SwitchState
createState
()
=>
_SwitchState
();
@override
void
debugFillProperties
(
DiagnosticPropertiesBuilder
properties
)
{
...
...
@@ -449,95 +370,65 @@ class Switch extends StatelessWidget {
}
}
class
_MaterialSwitch
extends
StatefulWidget
{
const
_MaterialSwitch
({
Key
?
key
,
required
this
.
value
,
required
this
.
onChanged
,
required
this
.
size
,
this
.
activeColor
,
this
.
activeTrackColor
,
this
.
inactiveThumbColor
,
this
.
inactiveTrackColor
,
this
.
activeThumbImage
,
this
.
onActiveThumbImageError
,
this
.
inactiveThumbImage
,
this
.
onInactiveThumbImageError
,
this
.
thumbColor
,
this
.
trackColor
,
this
.
materialTapTargetSize
,
this
.
dragStartBehavior
=
DragStartBehavior
.
start
,
this
.
mouseCursor
,
this
.
focusColor
,
this
.
hoverColor
,
this
.
overlayColor
,
this
.
splashRadius
,
this
.
focusNode
,
this
.
autofocus
=
false
,
})
:
assert
(
dragStartBehavior
!=
null
),
assert
(
activeThumbImage
!=
null
||
onActiveThumbImageError
==
null
),
assert
(
inactiveThumbImage
!=
null
||
onInactiveThumbImageError
==
null
),
super
(
key:
key
);
final
bool
value
;
final
ValueChanged
<
bool
>?
onChanged
;
final
Color
?
activeColor
;
final
Color
?
activeTrackColor
;
final
Color
?
inactiveThumbColor
;
final
Color
?
inactiveTrackColor
;
final
ImageProvider
?
activeThumbImage
;
final
ImageErrorListener
?
onActiveThumbImageError
;
final
ImageProvider
?
inactiveThumbImage
;
final
ImageErrorListener
?
onInactiveThumbImageError
;
final
MaterialStateProperty
<
Color
?>?
thumbColor
;
final
MaterialStateProperty
<
Color
?>?
trackColor
;
final
MaterialTapTargetSize
?
materialTapTargetSize
;
final
DragStartBehavior
dragStartBehavior
;
final
MouseCursor
?
mouseCursor
;
final
Color
?
focusColor
;
final
Color
?
hoverColor
;
final
MaterialStateProperty
<
Color
?>?
overlayColor
;
final
double
?
splashRadius
;
final
FocusNode
?
focusNode
;
final
bool
autofocus
;
final
Size
size
;
class
_SwitchState
extends
State
<
Switch
>
with
TickerProviderStateMixin
{
late
Map
<
Type
,
Action
<
Intent
>>
_actionMap
;
@override
State
<
StatefulWidget
>
createState
()
=>
_MaterialSwitchState
();
}
void
initState
()
{
super
.
initState
();
_actionMap
=
<
Type
,
Action
<
Intent
>>{
ActivateIntent:
CallbackAction
<
ActivateIntent
>(
onInvoke:
_actionHandler
),
};
}
void
_actionHandler
(
ActivateIntent
intent
)
{
if
(
widget
.
onChanged
!=
null
)
{
widget
.
onChanged
!(!
widget
.
value
);
}
final
RenderObject
renderObject
=
context
.
findRenderObject
()!;
renderObject
.
sendSemanticsEvent
(
const
TapSemanticEvent
());
}
class
_MaterialSwitchState
extends
State
<
_MaterialSwitch
>
with
TickerProviderStateMixin
,
ToggleableStateMixin
{
final
_SwitchPainter
_painter
=
_SwitchPainter
();
bool
_focused
=
false
;
void
_handleFocusHighlightChanged
(
bool
focused
)
{
if
(
focused
!=
_focused
)
{
setState
(()
{
_focused
=
focused
;
});
}
}
@override
void
didUpdateWidget
(
_MaterialSwitch
oldWidget
)
{
super
.
didUpdateWidget
(
oldWidget
);
if
(
oldWidget
.
value
!=
widget
.
value
)
{
// During a drag we may have modified the curve, reset it if its possible
// to do without visual discontinuation.
if
(
position
.
value
==
0.0
||
position
.
value
==
1.0
)
{
position
..
curve
=
Curves
.
easeIn
..
reverseCurve
=
Curves
.
easeOut
;
}
animateToValue
();
bool
_hovering
=
false
;
void
_handleHoverChanged
(
bool
hovering
)
{
if
(
hovering
!=
_hovering
)
{
setState
(()
{
_hovering
=
hovering
;
});
}
}
@override
void
dispose
()
{
_painter
.
dispose
();
super
.
dispose
();
Size
getSwitchSize
(
ThemeData
theme
)
{
final
MaterialTapTargetSize
effectiveMaterialTapTargetSize
=
widget
.
materialTapTargetSize
??
theme
.
switchTheme
.
materialTapTargetSize
??
theme
.
materialTapTargetSize
;
switch
(
effectiveMaterialTapTargetSize
)
{
case
MaterialTapTargetSize
.
padded
:
return
const
Size
(
_kSwitchWidth
,
_kSwitchHeight
);
case
MaterialTapTargetSize
.
shrinkWrap
:
return
const
Size
(
_kSwitchWidth
,
_kSwitchHeightCollapsed
);
}
}
@override
ValueChanged
<
bool
?>?
get
onChanged
=>
widget
.
onChanged
!=
null
?
_handleChanged
:
null
;
bool
get
enabled
=>
widget
.
onChanged
!=
null
;
@override
bool
get
tristate
=>
false
;
void
_didFinishDragging
()
{
// The user has finished dragging the thumb of this switch. Rebuild the switch
// to update the animation.
setState
(()
{});
}
@override
bool
?
get
value
=>
widget
.
value
;
Set
<
MaterialState
>
get
_states
=>
<
MaterialState
>{
if
(!
enabled
)
MaterialState
.
disabled
,
if
(
_hovering
)
MaterialState
.
hovered
,
if
(
_focused
)
MaterialState
.
focused
,
if
(
widget
.
value
)
MaterialState
.
selected
,
};
MaterialStateProperty
<
Color
?>
get
_widgetThumbColor
{
return
MaterialStateProperty
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
...
...
@@ -596,68 +487,14 @@ class _MaterialSwitchState extends State<_MaterialSwitch> with TickerProviderSta
});
}
double
get
_trackInnerLength
=>
widget
.
size
.
width
-
_kSwitchMinSize
;
void
_handleDragStart
(
DragStartDetails
details
)
{
if
(
isInteractive
)
reactionController
.
forward
();
}
void
_handleDragUpdate
(
DragUpdateDetails
details
)
{
if
(
isInteractive
)
{
position
..
curve
=
Curves
.
linear
..
reverseCurve
=
null
;
final
double
delta
=
details
.
primaryDelta
!
/
_trackInnerLength
;
switch
(
Directionality
.
of
(
context
))
{
case
TextDirection
.
rtl
:
positionController
.
value
-=
delta
;
break
;
case
TextDirection
.
ltr
:
positionController
.
value
+=
delta
;
break
;
}
}
}
bool
_needsPositionAnimation
=
false
;
void
_handleDragEnd
(
DragEndDetails
details
)
{
if
(
position
.
value
>=
0.5
!=
widget
.
value
)
{
widget
.
onChanged
!(!
widget
.
value
);
// Wait with finishing the animation until widget.value has changed to
// !widget.value as part of the widget.onChanged call above.
setState
(()
{
_needsPositionAnimation
=
true
;
});
}
else
{
animateToValue
();
}
reactionController
.
reverse
();
}
void
_handleChanged
(
bool
?
value
)
{
assert
(
value
!=
null
);
assert
(
widget
.
onChanged
!=
null
);
widget
.
onChanged
!(
value
!);
}
@override
Widget
build
(
BuildContext
context
)
{
Widget
buildMaterialSwitch
(
BuildContext
context
)
{
assert
(
debugCheckHasMaterial
(
context
));
if
(
_needsPositionAnimation
)
{
_needsPositionAnimation
=
false
;
animateToValue
();
}
final
ThemeData
theme
=
Theme
.
of
(
context
);
// Colors need to be resolved in selected and non selected states separately
// so that they can be lerped between.
final
Set
<
MaterialState
>
activeStates
=
states
..
add
(
MaterialState
.
selected
);
final
Set
<
MaterialState
>
inactiveStates
=
states
..
remove
(
MaterialState
.
selected
);
final
Set
<
MaterialState
>
activeStates
=
_
states
..
add
(
MaterialState
.
selected
);
final
Set
<
MaterialState
>
inactiveStates
=
_
states
..
remove
(
MaterialState
.
selected
);
final
Color
effectiveActiveThumbColor
=
widget
.
thumbColor
?.
resolve
(
activeStates
)
??
_widgetThumbColor
.
resolve
(
activeStates
)
??
theme
.
switchTheme
.
thumbColor
?.
resolve
(
activeStates
)
...
...
@@ -675,13 +512,14 @@ class _MaterialSwitchState extends State<_MaterialSwitch> with TickerProviderSta
??
theme
.
switchTheme
.
trackColor
?.
resolve
(
inactiveStates
)
??
_defaultTrackColor
.
resolve
(
inactiveStates
);
final
Set
<
MaterialState
>
focusedStates
=
states
..
add
(
MaterialState
.
focused
);
final
Set
<
MaterialState
>
focusedStates
=
_states
..
add
(
MaterialState
.
focused
);
final
Color
effectiveFocusOverlayColor
=
widget
.
overlayColor
?.
resolve
(
focusedStates
)
??
widget
.
focusColor
??
theme
.
switchTheme
.
overlayColor
?.
resolve
(
focusedStates
)
??
theme
.
focusColor
;
final
Set
<
MaterialState
>
hoveredStates
=
states
..
add
(
MaterialState
.
hovered
);
final
Set
<
MaterialState
>
hoveredStates
=
_
states
..
add
(
MaterialState
.
hovered
);
final
Color
effectiveHoverOverlayColor
=
widget
.
overlayColor
?.
resolve
(
hoveredStates
)
??
widget
.
hoverColor
??
theme
.
switchTheme
.
overlayColor
?.
resolve
(
hoveredStates
)
...
...
@@ -697,65 +535,277 @@ class _MaterialSwitchState extends State<_MaterialSwitch> with TickerProviderSta
??
theme
.
switchTheme
.
overlayColor
?.
resolve
(
inactivePressedStates
)
??
effectiveActiveThumbColor
.
withAlpha
(
kRadialReactionAlpha
);
final
MaterialStateProperty
<
MouseCursor
>
effectiveMouseCursor
=
MaterialStateProperty
.
resolveWith
<
MouseCursor
>((
Set
<
MaterialState
>
states
)
{
return
MaterialStateProperty
.
resolveAs
<
MouseCursor
?>(
widget
.
mouseCursor
,
states
)
??
theme
.
switchTheme
.
mouseCursor
?.
resolve
(
states
)
??
MaterialStateProperty
.
resolveAs
<
MouseCursor
>(
MaterialStateMouseCursor
.
clickable
,
states
);
});
final
MouseCursor
effectiveMouseCursor
=
MaterialStateProperty
.
resolveAs
<
MouseCursor
?>(
widget
.
mouseCursor
,
_states
)
??
theme
.
switchTheme
.
mouseCursor
?.
resolve
(
_states
)
??
MaterialStateProperty
.
resolveAs
<
MouseCursor
>(
MaterialStateMouseCursor
.
clickable
,
_states
);
return
FocusableActionDetector
(
actions:
_actionMap
,
focusNode:
widget
.
focusNode
,
autofocus:
widget
.
autofocus
,
enabled:
enabled
,
onShowFocusHighlight:
_handleFocusHighlightChanged
,
onShowHoverHighlight:
_handleHoverChanged
,
mouseCursor:
effectiveMouseCursor
,
child:
Builder
(
builder:
(
BuildContext
context
)
{
return
_SwitchRenderObjectWidget
(
dragStartBehavior:
widget
.
dragStartBehavior
,
value:
widget
.
value
,
activeColor:
effectiveActiveThumbColor
,
inactiveColor:
effectiveInactiveThumbColor
,
surfaceColor:
theme
.
colorScheme
.
surface
,
focusColor:
effectiveFocusOverlayColor
,
hoverColor:
effectiveHoverOverlayColor
,
reactionColor:
effectiveActivePressedOverlayColor
,
inactiveReactionColor:
effectiveInactivePressedOverlayColor
,
splashRadius:
widget
.
splashRadius
??
theme
.
switchTheme
.
splashRadius
??
kRadialReactionRadius
,
activeThumbImage:
widget
.
activeThumbImage
,
onActiveThumbImageError:
widget
.
onActiveThumbImageError
,
inactiveThumbImage:
widget
.
inactiveThumbImage
,
onInactiveThumbImageError:
widget
.
onInactiveThumbImageError
,
activeTrackColor:
effectiveActiveTrackColor
,
inactiveTrackColor:
effectiveInactiveTrackColor
,
configuration:
createLocalImageConfiguration
(
context
),
onChanged:
widget
.
onChanged
,
additionalConstraints:
BoxConstraints
.
tight
(
getSwitchSize
(
theme
)),
hasFocus:
_focused
,
hovering:
_hovering
,
state:
this
,
);
},
),
);
}
return
Semantics
(
toggled:
widget
.
value
,
child:
GestureDetector
(
excludeFromSemantics:
true
,
onHorizontalDragStart:
_handleDragStart
,
onHorizontalDragUpdate:
_handleDragUpdate
,
onHorizontalDragEnd:
_handleDragEnd
,
dragStartBehavior:
widget
.
dragStartBehavior
,
child:
buildToggleable
(
mouseCursor:
effectiveMouseCursor
,
focusNode:
widget
.
focusNode
,
autofocus:
widget
.
autofocus
,
size:
widget
.
size
,
painter:
_painter
..
position
=
position
..
reaction
=
reaction
..
reactionFocusFade
=
reactionFocusFade
..
reactionHoverFade
=
reactionHoverFade
..
inactiveReactionColor
=
effectiveInactivePressedOverlayColor
..
reactionColor
=
effectiveActivePressedOverlayColor
..
hoverColor
=
effectiveHoverOverlayColor
..
focusColor
=
effectiveFocusOverlayColor
..
splashRadius
=
widget
.
splashRadius
??
theme
.
switchTheme
.
splashRadius
??
kRadialReactionRadius
..
downPosition
=
downPosition
..
isFocused
=
states
.
contains
(
MaterialState
.
focused
)
..
isHovered
=
states
.
contains
(
MaterialState
.
hovered
)
..
activeColor
=
effectiveActiveThumbColor
..
inactiveColor
=
effectiveInactiveThumbColor
..
activeThumbImage
=
widget
.
activeThumbImage
..
onActiveThumbImageError
=
widget
.
onActiveThumbImageError
..
inactiveThumbImage
=
widget
.
inactiveThumbImage
..
onInactiveThumbImageError
=
widget
.
onInactiveThumbImageError
..
activeTrackColor
=
effectiveActiveTrackColor
..
inactiveTrackColor
=
effectiveInactiveTrackColor
..
configuration
=
createLocalImageConfiguration
(
context
)
..
isInteractive
=
isInteractive
..
trackInnerLength
=
_trackInnerLength
..
textDirection
=
Directionality
.
of
(
context
)
..
surfaceColor
=
theme
.
colorScheme
.
surface
Widget
buildCupertinoSwitch
(
BuildContext
context
)
{
final
Size
size
=
getSwitchSize
(
Theme
.
of
(
context
));
return
Focus
(
focusNode:
widget
.
focusNode
,
autofocus:
widget
.
autofocus
,
child:
Container
(
width:
size
.
width
,
// Same size as the Material switch.
height:
size
.
height
,
alignment:
Alignment
.
center
,
child:
CupertinoSwitch
(
dragStartBehavior:
widget
.
dragStartBehavior
,
value:
widget
.
value
,
onChanged:
widget
.
onChanged
,
activeColor:
widget
.
activeColor
,
trackColor:
widget
.
inactiveTrackColor
),
),
);
}
@override
Widget
build
(
BuildContext
context
)
{
switch
(
widget
.
_switchType
)
{
case
_SwitchType
.
material
:
return
buildMaterialSwitch
(
context
);
case
_SwitchType
.
adaptive
:
{
final
ThemeData
theme
=
Theme
.
of
(
context
);
assert
(
theme
.
platform
!=
null
);
switch
(
theme
.
platform
)
{
case
TargetPlatform
.
android
:
case
TargetPlatform
.
fuchsia
:
case
TargetPlatform
.
linux
:
case
TargetPlatform
.
windows
:
return
buildMaterialSwitch
(
context
);
case
TargetPlatform
.
iOS
:
case
TargetPlatform
.
macOS
:
return
buildCupertinoSwitch
(
context
);
}
}
}
}
}
class
_SwitchRenderObjectWidget
extends
LeafRenderObjectWidget
{
const
_SwitchRenderObjectWidget
({
Key
?
key
,
required
this
.
value
,
required
this
.
activeColor
,
required
this
.
inactiveColor
,
required
this
.
hoverColor
,
required
this
.
focusColor
,
required
this
.
reactionColor
,
required
this
.
inactiveReactionColor
,
required
this
.
splashRadius
,
required
this
.
activeThumbImage
,
required
this
.
onActiveThumbImageError
,
required
this
.
inactiveThumbImage
,
required
this
.
onInactiveThumbImageError
,
required
this
.
activeTrackColor
,
required
this
.
inactiveTrackColor
,
required
this
.
configuration
,
required
this
.
onChanged
,
required
this
.
additionalConstraints
,
required
this
.
dragStartBehavior
,
required
this
.
hasFocus
,
required
this
.
hovering
,
required
this
.
state
,
required
this
.
surfaceColor
,
})
:
super
(
key:
key
);
final
bool
value
;
final
Color
activeColor
;
final
Color
inactiveColor
;
final
Color
hoverColor
;
final
Color
focusColor
;
final
Color
reactionColor
;
final
Color
inactiveReactionColor
;
final
double
splashRadius
;
final
ImageProvider
?
activeThumbImage
;
final
ImageErrorListener
?
onActiveThumbImageError
;
final
ImageProvider
?
inactiveThumbImage
;
final
ImageErrorListener
?
onInactiveThumbImageError
;
final
Color
activeTrackColor
;
final
Color
inactiveTrackColor
;
final
ImageConfiguration
configuration
;
final
ValueChanged
<
bool
>?
onChanged
;
final
BoxConstraints
additionalConstraints
;
final
DragStartBehavior
dragStartBehavior
;
final
bool
hasFocus
;
final
bool
hovering
;
final
_SwitchState
state
;
final
Color
surfaceColor
;
@override
_RenderSwitch
createRenderObject
(
BuildContext
context
)
{
return
_RenderSwitch
(
dragStartBehavior:
dragStartBehavior
,
value:
value
,
activeColor:
activeColor
,
inactiveColor:
inactiveColor
,
hoverColor:
hoverColor
,
focusColor:
focusColor
,
reactionColor:
reactionColor
,
inactiveReactionColor:
inactiveReactionColor
,
splashRadius:
splashRadius
,
activeThumbImage:
activeThumbImage
,
onActiveThumbImageError:
onActiveThumbImageError
,
inactiveThumbImage:
inactiveThumbImage
,
onInactiveThumbImageError:
onInactiveThumbImageError
,
activeTrackColor:
activeTrackColor
,
inactiveTrackColor:
inactiveTrackColor
,
configuration:
configuration
,
onChanged:
onChanged
!=
null
?
_handleValueChanged
:
null
,
textDirection:
Directionality
.
of
(
context
),
additionalConstraints:
additionalConstraints
,
hasFocus:
hasFocus
,
hovering:
hovering
,
state:
state
,
surfaceColor:
surfaceColor
,
);
}
@override
void
updateRenderObject
(
BuildContext
context
,
_RenderSwitch
renderObject
)
{
renderObject
..
value
=
value
..
activeColor
=
activeColor
..
inactiveColor
=
inactiveColor
..
hoverColor
=
hoverColor
..
focusColor
=
focusColor
..
reactionColor
=
reactionColor
..
inactiveReactionColor
=
inactiveReactionColor
..
splashRadius
=
splashRadius
..
activeThumbImage
=
activeThumbImage
..
onActiveThumbImageError
=
onActiveThumbImageError
..
inactiveThumbImage
=
inactiveThumbImage
..
onInactiveThumbImageError
=
onInactiveThumbImageError
..
activeTrackColor
=
activeTrackColor
..
inactiveTrackColor
=
inactiveTrackColor
..
configuration
=
configuration
..
onChanged
=
onChanged
!=
null
?
_handleValueChanged
:
null
..
textDirection
=
Directionality
.
of
(
context
)
..
additionalConstraints
=
additionalConstraints
..
dragStartBehavior
=
dragStartBehavior
..
hasFocus
=
hasFocus
..
hovering
=
hovering
..
vsync
=
state
..
surfaceColor
=
surfaceColor
;
}
void
_handleValueChanged
(
bool
?
value
)
{
// Wrap the onChanged callback because the RenderToggleable supports tri-state
// values (i.e. value can be null), but the Switch doesn't. We pass false
// for the tristate param to RenderToggleable, so value should never
// be null.
assert
(
value
!=
null
);
if
(
onChanged
!=
null
)
{
onChanged
!(
value
!);
}
}
}
class
_SwitchPainter
extends
ToggleablePainter
{
class
_RenderSwitch
extends
RenderToggleable
{
_RenderSwitch
({
required
bool
value
,
required
Color
activeColor
,
required
Color
inactiveColor
,
required
Color
hoverColor
,
required
Color
focusColor
,
required
Color
reactionColor
,
required
Color
inactiveReactionColor
,
required
double
splashRadius
,
required
ImageProvider
?
activeThumbImage
,
required
ImageErrorListener
?
onActiveThumbImageError
,
required
ImageProvider
?
inactiveThumbImage
,
required
ImageErrorListener
?
onInactiveThumbImageError
,
required
Color
activeTrackColor
,
required
Color
inactiveTrackColor
,
required
ImageConfiguration
configuration
,
required
BoxConstraints
additionalConstraints
,
required
TextDirection
textDirection
,
required
ValueChanged
<
bool
?>?
onChanged
,
required
DragStartBehavior
dragStartBehavior
,
required
bool
hasFocus
,
required
bool
hovering
,
required
this
.
state
,
required
Color
surfaceColor
,
})
:
assert
(
textDirection
!=
null
),
_activeThumbImage
=
activeThumbImage
,
_onActiveThumbImageError
=
onActiveThumbImageError
,
_inactiveThumbImage
=
inactiveThumbImage
,
_onInactiveThumbImageError
=
onInactiveThumbImageError
,
_activeTrackColor
=
activeTrackColor
,
_inactiveTrackColor
=
inactiveTrackColor
,
_configuration
=
configuration
,
_textDirection
=
textDirection
,
_surfaceColor
=
surfaceColor
,
super
(
value:
value
,
tristate:
false
,
activeColor:
activeColor
,
inactiveColor:
inactiveColor
,
hoverColor:
hoverColor
,
focusColor:
focusColor
,
reactionColor:
reactionColor
,
inactiveReactionColor:
inactiveReactionColor
,
splashRadius:
splashRadius
,
onChanged:
onChanged
,
additionalConstraints:
additionalConstraints
,
hasFocus:
hasFocus
,
hovering:
hovering
,
vsync:
state
,
)
{
_drag
=
HorizontalDragGestureRecognizer
()
..
onStart
=
_handleDragStart
..
onUpdate
=
_handleDragUpdate
..
onEnd
=
_handleDragEnd
..
dragStartBehavior
=
dragStartBehavior
;
}
ImageProvider
?
get
activeThumbImage
=>
_activeThumbImage
;
ImageProvider
?
_activeThumbImage
;
set
activeThumbImage
(
ImageProvider
?
value
)
{
if
(
value
==
_activeThumbImage
)
return
;
_activeThumbImage
=
value
;
notifyListeners
();
markNeedsPaint
();
}
ImageErrorListener
?
get
onActiveThumbImageError
=>
_onActiveThumbImageError
;
...
...
@@ -765,7 +815,7 @@ class _SwitchPainter extends ToggleablePainter {
return
;
}
_onActiveThumbImageError
=
value
;
notifyListeners
();
markNeedsPaint
();
}
ImageProvider
?
get
inactiveThumbImage
=>
_inactiveThumbImage
;
...
...
@@ -774,7 +824,7 @@ class _SwitchPainter extends ToggleablePainter {
if
(
value
==
_inactiveThumbImage
)
return
;
_inactiveThumbImage
=
value
;
notifyListeners
();
markNeedsPaint
();
}
ImageErrorListener
?
get
onInactiveThumbImageError
=>
_onInactiveThumbImageError
;
...
...
@@ -784,77 +834,132 @@ class _SwitchPainter extends ToggleablePainter {
return
;
}
_onInactiveThumbImageError
=
value
;
notifyListeners
();
markNeedsPaint
();
}
Color
get
activeTrackColor
=>
_activeTrackColor
!
;
Color
?
_activeTrackColor
;
Color
get
activeTrackColor
=>
_activeTrackColor
;
Color
_activeTrackColor
;
set
activeTrackColor
(
Color
value
)
{
assert
(
value
!=
null
);
if
(
value
==
_activeTrackColor
)
return
;
_activeTrackColor
=
value
;
notifyListeners
();
markNeedsPaint
();
}
Color
get
inactiveTrackColor
=>
_inactiveTrackColor
!
;
Color
?
_inactiveTrackColor
;
Color
get
inactiveTrackColor
=>
_inactiveTrackColor
;
Color
_inactiveTrackColor
;
set
inactiveTrackColor
(
Color
value
)
{
assert
(
value
!=
null
);
if
(
value
==
_inactiveTrackColor
)
return
;
_inactiveTrackColor
=
value
;
notifyListeners
();
markNeedsPaint
();
}
ImageConfiguration
get
configuration
=>
_configuration
!
;
ImageConfiguration
?
_configuration
;
ImageConfiguration
get
configuration
=>
_configuration
;
ImageConfiguration
_configuration
;
set
configuration
(
ImageConfiguration
value
)
{
assert
(
value
!=
null
);
if
(
value
==
_configuration
)
return
;
_configuration
=
value
;
notifyListeners
();
markNeedsPaint
();
}
TextDirection
get
textDirection
=>
_textDirection
!
;
TextDirection
?
_textDirection
;
TextDirection
get
textDirection
=>
_textDirection
;
TextDirection
_textDirection
;
set
textDirection
(
TextDirection
value
)
{
assert
(
value
!=
null
);
if
(
_textDirection
==
value
)
return
;
_textDirection
=
value
;
notifyListeners
();
markNeedsPaint
();
}
Color
get
surfaceColor
=>
_surfaceColor
!;
Color
?
_surfaceColor
;
DragStartBehavior
get
dragStartBehavior
=>
_drag
.
dragStartBehavior
;
set
dragStartBehavior
(
DragStartBehavior
value
)
{
assert
(
value
!=
null
);
if
(
_drag
.
dragStartBehavior
==
value
)
return
;
_drag
.
dragStartBehavior
=
value
;
}
Color
get
surfaceColor
=>
_surfaceColor
;
Color
_surfaceColor
;
set
surfaceColor
(
Color
value
)
{
assert
(
value
!=
null
);
if
(
value
==
_surfaceColor
)
return
;
_surfaceColor
=
value
;
notifyListeners
();
markNeedsPaint
();
}
bool
get
isInteractive
=>
_isInteractive
!;
bool
?
_isInteractive
;
set
isInteractive
(
bool
value
)
{
if
(
value
==
_isInteractive
)
{
return
;
_SwitchState
state
;
@override
set
value
(
bool
?
newValue
)
{
assert
(
value
!=
null
);
super
.
value
=
newValue
;
// The widget is rebuilt and we have pending position animation to play.
if
(
_needsPositionAnimation
)
{
_needsPositionAnimation
=
false
;
position
.
reverseCurve
=
null
;
if
(
newValue
!)
positionController
.
forward
();
else
positionController
.
reverse
();
}
_isInteractive
=
value
;
notifyListeners
();
}
double
get
trackInnerLength
=>
_trackInnerLength
!;
double
?
_trackInnerLength
;
set
trackInnerLength
(
double
value
)
{
if
(
value
==
_trackInnerLength
)
{
return
;
@override
void
detach
()
{
_cachedThumbPainter
?.
dispose
();
_cachedThumbPainter
=
null
;
super
.
detach
();
}
double
get
_trackInnerLength
=>
size
.
width
-
_kSwitchMinSize
;
late
HorizontalDragGestureRecognizer
_drag
;
bool
_needsPositionAnimation
=
false
;
void
_handleDragStart
(
DragStartDetails
details
)
{
if
(
isInteractive
)
reactionController
.
forward
();
}
void
_handleDragUpdate
(
DragUpdateDetails
details
)
{
if
(
isInteractive
)
{
position
.
reverseCurve
=
null
;
final
double
delta
=
details
.
primaryDelta
!
/
_trackInnerLength
;
switch
(
textDirection
)
{
case
TextDirection
.
rtl
:
positionController
.
value
-=
delta
;
break
;
case
TextDirection
.
ltr
:
positionController
.
value
+=
delta
;
break
;
}
}
_trackInnerLength
=
value
;
notifyListeners
();
}
void
_handleDragEnd
(
DragEndDetails
details
)
{
_needsPositionAnimation
=
true
;
if
(
position
.
value
>=
0.5
!=
value
)
onChanged
!(!
value
!);
reactionController
.
reverse
();
state
.
_didFinishDragging
();
}
@override
void
handleEvent
(
PointerEvent
event
,
BoxHitTestEntry
entry
)
{
assert
(
debugHandleEvent
(
event
,
entry
));
if
(
event
is
PointerDownEvent
&&
onChanged
!=
null
)
_drag
.
addPointer
(
event
);
super
.
handleEvent
(
event
,
entry
);
}
Color
?
_cachedThumbColor
;
...
...
@@ -879,12 +984,19 @@ class _SwitchPainter extends ToggleablePainter {
// are already in the middle of painting. (In fact, doing so would trigger
// an assert).
if
(!
_isPainting
)
notifyListeners
();
markNeedsPaint
();
}
@override
void
describeSemanticsConfiguration
(
SemanticsConfiguration
config
)
{
super
.
describeSemanticsConfiguration
(
config
);
config
.
isToggled
=
value
==
true
;
}
@override
void
paint
(
Canvas
canvas
,
Size
size
)
{
final
bool
isEnabled
=
isInteractive
;
void
paint
(
PaintingContext
context
,
Offset
offset
)
{
final
Canvas
canvas
=
context
.
canvas
;
final
bool
isEnabled
=
onChanged
!=
null
;
final
double
currentValue
=
position
.
value
;
final
double
visualPosition
;
...
...
@@ -917,8 +1029,8 @@ class _SwitchPainter extends ToggleablePainter {
..
color
=
trackColor
;
const
double
trackHorizontalPadding
=
kRadialReactionRadius
-
_kTrackRadius
;
final
Rect
trackRect
=
Rect
.
fromLTWH
(
trackHorizontalPadding
,
(
size
.
height
-
_kTrackHeight
)
/
2.0
,
offset
.
dx
+
trackHorizontalPadding
,
offset
.
dy
+
(
size
.
height
-
_kTrackHeight
)
/
2.0
,
size
.
width
-
2.0
*
trackHorizontalPadding
,
_kTrackHeight
,
);
...
...
@@ -926,11 +1038,11 @@ class _SwitchPainter extends ToggleablePainter {
canvas
.
drawRRect
(
trackRRect
,
paint
);
final
Offset
thumbPosition
=
Offset
(
kRadialReactionRadius
+
visualPosition
*
trackInnerLength
,
kRadialReactionRadius
+
visualPosition
*
_
trackInnerLength
,
size
.
height
/
2.0
,
);
paintRadialReaction
(
canvas
:
canvas
,
origin:
thumbPosition
);
paintRadialReaction
(
canvas
,
offset
,
thumbPosition
);
try
{
_isPainting
=
true
;
...
...
@@ -947,7 +1059,7 @@ class _SwitchPainter extends ToggleablePainter {
final
double
radius
=
_kThumbRadius
-
inset
;
thumbPainter
.
paint
(
canvas
,
thumbPosition
-
Offset
(
radius
,
radius
),
thumbPosition
+
offset
-
Offset
(
radius
,
radius
),
configuration
.
copyWith
(
size:
Size
.
fromRadius
(
radius
)),
);
}
finally
{
...
...
packages/flutter/lib/src/material/toggleable.dart
View file @
328a262e
...
...
@@ -6,10 +6,9 @@ import 'package:flutter/animation.dart';
import
'package:flutter/foundation.dart'
;
import
'package:flutter/gestures.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/
widgets
.dart'
;
import
'package:flutter/
scheduler
.dart'
;
import
'constants.dart'
;
import
'material_state.dart'
;
// Duration of the animation that moves the toggle from one state to another.
const
Duration
_kToggleDuration
=
Duration
(
milliseconds:
200
);
...
...
@@ -17,21 +16,92 @@ const Duration _kToggleDuration = Duration(milliseconds: 200);
// Duration of the fade animation for the reaction when focus and hover occur.
const
Duration
_kReactionFadeDuration
=
Duration
(
milliseconds:
50
);
/// A mixin for [StatefulWidget]s that implement material-themed toggleable
/// controls with toggle animations (e.g. [Switch]es, [Checkbox]es, and
/// [Radio]s).
/// A base class for material style toggleable controls with toggle animations.
///
/// The mixin implements the logic for toggling the control (e.g. when tapped)
/// and provides a series of animation controllers to transition the control
/// from one state to another. It does not have any opinion about the visual
/// representation of the toggleable widget. The visuals are defined by a
/// [CustomPainter] passed to the [buildToggleable]. [State] objects using this
/// mixin should call that method from their [build] method.
///
/// This mixin is used to implement the material components for [Switch],
/// [Checkbox], and [Radio] controls.
@optionalTypeArgs
mixin
ToggleableStateMixin
<
S
extends
StatefulWidget
>
on
TickerProviderStateMixin
<
S
>
{
/// This class handles storing the current value, dispatching ValueChanged on a
/// tap gesture and driving a changed animation. Subclasses are responsible for
/// painting.
abstract
class
RenderToggleable
extends
RenderConstrainedBox
{
/// Creates a toggleable render object.
///
/// The [activeColor], and [inactiveColor] arguments must not be
/// null. The [value] can only be null if tristate is true.
RenderToggleable
({
required
bool
?
value
,
bool
tristate
=
false
,
required
Color
activeColor
,
required
Color
inactiveColor
,
Color
?
hoverColor
,
Color
?
focusColor
,
Color
?
reactionColor
,
Color
?
inactiveReactionColor
,
required
double
splashRadius
,
ValueChanged
<
bool
?>?
onChanged
,
required
BoxConstraints
additionalConstraints
,
required
TickerProvider
vsync
,
bool
hasFocus
=
false
,
bool
hovering
=
false
,
})
:
assert
(
tristate
!=
null
),
assert
(
tristate
||
value
!=
null
),
assert
(
activeColor
!=
null
),
assert
(
inactiveColor
!=
null
),
assert
(
vsync
!=
null
),
_value
=
value
,
_tristate
=
tristate
,
_activeColor
=
activeColor
,
_inactiveColor
=
inactiveColor
,
_hoverColor
=
hoverColor
??
activeColor
.
withAlpha
(
kRadialReactionAlpha
),
_focusColor
=
focusColor
??
activeColor
.
withAlpha
(
kRadialReactionAlpha
),
_reactionColor
=
reactionColor
??
activeColor
.
withAlpha
(
kRadialReactionAlpha
),
_inactiveReactionColor
=
inactiveReactionColor
??
activeColor
.
withAlpha
(
kRadialReactionAlpha
),
_splashRadius
=
splashRadius
,
_onChanged
=
onChanged
,
_hasFocus
=
hasFocus
,
_hovering
=
hovering
,
_vsync
=
vsync
,
super
(
additionalConstraints:
additionalConstraints
)
{
_tap
=
TapGestureRecognizer
()
..
onTapDown
=
_handleTapDown
..
onTap
=
_handleTap
..
onTapUp
=
_handleTapUp
..
onTapCancel
=
_handleTapCancel
;
_positionController
=
AnimationController
(
duration:
_kToggleDuration
,
value:
value
==
false
?
0.0
:
1.0
,
vsync:
vsync
,
);
_position
=
CurvedAnimation
(
parent:
_positionController
,
curve:
Curves
.
linear
,
)..
addListener
(
markNeedsPaint
);
_reactionController
=
AnimationController
(
duration:
kRadialReactionDuration
,
vsync:
vsync
,
);
_reaction
=
CurvedAnimation
(
parent:
_reactionController
,
curve:
Curves
.
fastOutSlowIn
,
)..
addListener
(
markNeedsPaint
);
_reactionHoverFadeController
=
AnimationController
(
duration:
_kReactionFadeDuration
,
value:
hovering
||
hasFocus
?
1.0
:
0.0
,
vsync:
vsync
,
);
_reactionHoverFade
=
CurvedAnimation
(
parent:
_reactionHoverFadeController
,
curve:
Curves
.
fastOutSlowIn
,
)..
addListener
(
markNeedsPaint
);
_reactionFocusFadeController
=
AnimationController
(
duration:
_kReactionFadeDuration
,
value:
hovering
||
hasFocus
?
1.0
:
0.0
,
vsync:
vsync
,
);
_reactionFocusFade
=
CurvedAnimation
(
parent:
_reactionFocusFadeController
,
curve:
Curves
.
fastOutSlowIn
,
)..
addListener
(
markNeedsPaint
);
}
/// Used by subclasses to manipulate the visual value of the control.
///
/// Some controls respond to user input by updating their visual value. For
...
...
@@ -39,15 +109,16 @@ mixin ToggleableStateMixin<S extends StatefulWidget> on TickerProviderStateMixin
/// dragged. These controls manipulate this animation controller to update
/// their [position] and eventually trigger an [onChanged] callback when the
/// animation reaches either 0.0 or 1.0.
@protected
AnimationController
get
positionController
=>
_positionController
;
late
AnimationController
_positionController
;
/// The visual value of the control.
///
/// When the control is inactive, the [value] is false and this animation has
/// the value 0.0. When the control is active, the value
is either true or
///
tristate is true and the value is null. When the control is active the
///
animation
has a value of 1.0. When the control is changing from inactive
/// the value 0.0. When the control is active, the value
either true or tristate
///
is true and the value is null. When the control is active the animation
/// has a value of 1.0. When the control is changing from inactive
/// to active (or vice versa), [value] is the target value and this animation
/// gradually updates from 0.0 to 1.0 (or vice versa).
CurvedAnimation
get
position
=>
_position
;
...
...
@@ -58,66 +129,84 @@ mixin ToggleableStateMixin<S extends StatefulWidget> on TickerProviderStateMixin
/// Some controls have a radial ink reaction to user input. This animation
/// controller can be used to start or stop these ink reactions.
///
/// To paint the actual radial reaction, [ToggleablePainter.paintRadialReaction]
/// may be used.
/// Subclasses should call [paintRadialReaction] to actually paint the radial
/// reaction.
@protected
AnimationController
get
reactionController
=>
_reactionController
;
late
AnimationController
_reactionController
;
late
Animation
<
double
>
_reaction
;
/// The visual value of the radial reaction animation.
/// Used by subclasses to control the radial reaction's opacity animation for
/// [hasFocus] changes.
///
/// Some controls have a radial ink reaction to user input. This animation
/// controls the progress of these ink reactions.
/// Some controls have a radial ink reaction to focus. This animation
/// controller can be used to start or stop these ink reaction fade-ins and
/// fade-outs.
///
/// To paint the actual radial reaction, [ToggleablePainter.paintRadialReaction]
/// may be used.
Animation
<
double
>
get
reaction
=>
_reaction
;
late
Animation
<
double
>
_reaction
;
/// Subclasses should call [paintRadialReaction] to actually paint the radial
/// reaction.
@protected
AnimationController
get
reactionFocusFadeController
=>
_reactionFocusFadeController
;
late
AnimationController
_reactionFocusFadeController
;
late
Animation
<
double
>
_reactionFocusFade
;
/// Controls the radial reaction's opacity animation for hover changes.
/// Used by subclasses to control the radial reaction's opacity animation for
/// [hovering] changes.
///
/// Some controls have a radial ink reaction to pointer hover. This animation
/// control
s
these ink reaction fade-ins and
/// control
ler can be used to start or stop
these ink reaction fade-ins and
/// fade-outs.
///
///
To paint the actual radial reaction, [ToggleablePainter.paintRadialReaction]
///
may be used
.
Animation
<
double
>
get
reactionHoverFade
=>
_reactionHoverFade
;
late
Animation
<
double
>
_reactionHoverFade
;
///
Subclasses should call [paintRadialReaction] to actually paint the radial
///
reaction
.
@protected
AnimationController
get
reactionHoverFadeController
=>
_reactionHoverFadeController
;
late
AnimationController
_reactionHoverFadeController
;
late
Animation
<
double
>
_reactionHoverFade
;
/// Controls the radial reaction's opacity animation for focus changes.
///
/// Some controls have a radial ink reaction to focus. This animation
/// controls these ink reaction fade-ins and fade-outs.
///
/// To paint the actual radial reaction, [ToggleablePainter.paintRadialReaction]
/// may be used.
Animation
<
double
>
get
reactionFocusFade
=>
_reactionFocusFade
;
late
Animation
<
double
>
_reactionFocusFade
;
late
AnimationController
_reactionFocusFadeController
;
/// Whether [value] of this control can be changed by user interaction.
///
/// The control is considered interactive if the [onChanged] callback is
/// non-null. If the callback is null, then the control is disabled, and
/// non-interactive. A disabled checkbox, for example, is displayed using a
/// grey color and its value cannot be changed.
bool
get
isInteractive
=>
onChanged
!=
null
;
/// True if this toggleable has the input focus.
bool
get
hasFocus
=>
_hasFocus
;
bool
_hasFocus
;
set
hasFocus
(
bool
value
)
{
assert
(
value
!=
null
);
if
(
value
==
_hasFocus
)
return
;
_hasFocus
=
value
;
if
(
_hasFocus
)
{
_reactionFocusFadeController
.
forward
();
}
else
{
_reactionFocusFadeController
.
reverse
();
}
markNeedsPaint
();
}
late
final
Map
<
Type
,
Action
<
Intent
>>
_actionMap
=
<
Type
,
Action
<
Intent
>>{
ActivateIntent:
CallbackAction
<
ActivateIntent
>(
onInvoke:
_handleTap
),
};
/// True if this toggleable is being hovered over by a pointer.
bool
get
hovering
=>
_hovering
;
bool
_hovering
;
set
hovering
(
bool
value
)
{
assert
(
value
!=
null
);
if
(
value
==
_hovering
)
return
;
_hovering
=
value
;
if
(
_hovering
)
{
_reactionHoverFadeController
.
forward
();
}
else
{
_reactionHoverFadeController
.
reverse
();
}
markNeedsPaint
();
}
/// Called when the control changes value.
///
/// If the control is tapped, [onChanged] is called immediately with the new
/// value.
///
/// The control is considered interactive (see [isInteractive]) if this
/// callback is non-null. If the callback is null, then the control is
/// disabled, and non-interactive. A disabled checkbox, for example, is
/// displayed using a grey color and its value cannot be changed.
ValueChanged
<
bool
?>?
get
onChanged
;
/// The [TickerProvider] for the [AnimationController]s that run the animations.
TickerProvider
get
vsync
=>
_vsync
;
TickerProvider
_vsync
;
set
vsync
(
TickerProvider
value
)
{
assert
(
value
!=
null
);
if
(
value
==
_vsync
)
return
;
_vsync
=
value
;
positionController
.
resync
(
vsync
);
reactionController
.
resync
(
vsync
);
}
/// False if this control is "inactive" (not checked, off, or unselected).
///
...
...
@@ -128,62 +217,17 @@ mixin ToggleableStateMixin<S extends StatefulWidget> on TickerProviderStateMixin
/// When the value changes, this object starts the [positionController] and
/// [position] animations to animate the visual appearance of the control to
/// the new value.
bool
?
get
value
;
/// If true, [value] can be true, false, or null, otherwise [value] must
/// be true or false.
///
/// When [tristate] is true and [value] is null, then the control is
/// considered to be in its third or "indeterminate" state.
bool
get
tristate
;
@override
void
initState
()
{
super
.
initState
();
_positionController
=
AnimationController
(
duration:
_kToggleDuration
,
value:
value
==
false
?
0.0
:
1.0
,
vsync:
this
,
);
_position
=
CurvedAnimation
(
parent:
_positionController
,
curve:
Curves
.
easeIn
,
reverseCurve:
Curves
.
easeOut
,
);
_reactionController
=
AnimationController
(
duration:
kRadialReactionDuration
,
vsync:
this
,
);
_reaction
=
CurvedAnimation
(
parent:
_reactionController
,
curve:
Curves
.
fastOutSlowIn
,
);
_reactionHoverFadeController
=
AnimationController
(
duration:
_kReactionFadeDuration
,
value:
_hovering
||
_focused
?
1.0
:
0.0
,
vsync:
this
,
);
_reactionHoverFade
=
CurvedAnimation
(
parent:
_reactionHoverFadeController
,
curve:
Curves
.
fastOutSlowIn
,
);
_reactionFocusFadeController
=
AnimationController
(
duration:
_kReactionFadeDuration
,
value:
_hovering
||
_focused
?
1.0
:
0.0
,
vsync:
this
,
);
_reactionFocusFade
=
CurvedAnimation
(
parent:
_reactionFocusFadeController
,
curve:
Curves
.
fastOutSlowIn
,
);
}
/// Runs the [position] animation to transition the Toggleable's appearance
/// to match [value].
///
/// This method must be called whenever [value] changes to ensure that the
/// visual representation of the Toggleable matches the current [value].
void
animateToValue
()
{
bool
?
get
value
=>
_value
;
bool
?
_value
;
set
value
(
bool
?
value
)
{
assert
(
tristate
||
value
!=
null
);
if
(
value
==
_value
)
return
;
_value
=
value
;
markNeedsSemanticsUpdate
();
_position
..
curve
=
Curves
.
easeIn
..
reverseCurve
=
Curves
.
easeOut
;
if
(
tristate
)
{
if
(
value
==
null
)
_positionController
.
value
=
0.0
;
...
...
@@ -199,235 +243,94 @@ mixin ToggleableStateMixin<S extends StatefulWidget> on TickerProviderStateMixin
}
}
@override
void
dispose
()
{
_positionController
.
dispose
();
_reactionController
.
dispose
();
_reactionHoverFadeController
.
dispose
();
_reactionFocusFadeController
.
dispose
();
super
.
dispose
();
}
/// The most recent [Offset] at which a pointer touched the Toggleable.
/// If true, [value] can be true, false, or null, otherwise [value] must
/// be true or false.
///
/// This is null if currently no pointer is touching the Toggleable or if
/// [isInteractive] is false.
Offset
?
get
downPosition
=>
_downPosition
;
Offset
?
_downPosition
;
void
_handleTapDown
(
TapDownDetails
details
)
{
if
(
isInteractive
)
{
setState
(()
{
_downPosition
=
details
.
localPosition
;
});
_reactionController
.
forward
();
}
}
void
_handleTap
([
Intent
?
_
])
{
if
(!
isInteractive
)
/// When [tristate] is true and [value] is null, then the control is
/// considered to be in its third or "indeterminate" state.
bool
get
tristate
=>
_tristate
;
bool
_tristate
;
set
tristate
(
bool
value
)
{
assert
(
tristate
!=
null
);
if
(
value
==
_tristate
)
return
;
switch
(
value
)
{
case
false
:
onChanged
!(
true
);
break
;
case
true
:
onChanged
!(
tristate
?
null
:
false
);
break
;
case
null
:
onChanged
!(
false
);
break
;
}
context
.
findRenderObject
()!.
sendSemanticsEvent
(
const
TapSemanticEvent
());
}
void
_handleTapEnd
([
TapUpDetails
?
_
])
{
if
(
_downPosition
!=
null
)
{
setState
(()
{
_downPosition
=
null
;
});
}
_reactionController
.
reverse
();
}
bool
_focused
=
false
;
void
_handleFocusHighlightChanged
(
bool
focused
)
{
if
(
focused
!=
_focused
)
{
setState
(()
{
_focused
=
focused
;
});
if
(
focused
)
{
_reactionFocusFadeController
.
forward
();
}
else
{
_reactionFocusFadeController
.
reverse
();
}
}
}
bool
_hovering
=
false
;
void
_handleHoverChanged
(
bool
hovering
)
{
if
(
hovering
!=
_hovering
)
{
setState
(()
{
_hovering
=
hovering
;
});
if
(
hovering
)
{
_reactionHoverFadeController
.
forward
();
}
else
{
_reactionHoverFadeController
.
reverse
();
}
}
}
/// Describes the current [MaterialState] of the Toggleable.
///
/// The returned set will include:
///
/// * [MaterialState.disabled], if [isInteractive] is false
/// * [MaterialState.hovered], if a pointer is hovering over the Toggleable
/// * [MaterialState.focused], if the Toggleable has input focus
/// * [MaterialState.selected], if [value] is true or null
Set
<
MaterialState
>
get
states
=>
<
MaterialState
>{
if
(!
isInteractive
)
MaterialState
.
disabled
,
if
(
_hovering
)
MaterialState
.
hovered
,
if
(
_focused
)
MaterialState
.
focused
,
if
(
value
!=
false
)
MaterialState
.
selected
,
};
/// Typically wraps a `painter` that draws the actual visuals of the
/// Toggleable with logic to toggle it.
///
/// Consider providing a subclass of [ToggleablePainter] as a `painter`, which
/// implements logic to draw a radial ink reaction for this control. The
/// painter is usually configured with the [reaction], [position],
/// [reactionHoverFade], and [reactionFocusFade] animation provided by this
/// mixin. It is expected to draw the visuals of the Toggleable based on the
/// current value of these animations. The animations are triggered by
/// this mixin to transition the Toggleable from one state to another.
///
/// This method must be called from the [build] method of the [State] class
/// that uses this mixin. The returned [Widget] must be returned from the
/// build method - potentially after wrapping it in other widgets.
Widget
buildToggleable
({
FocusNode
?
focusNode
,
bool
autofocus
=
false
,
required
MaterialStateProperty
<
MouseCursor
>
mouseCursor
,
required
Size
size
,
required
CustomPainter
painter
,
})
{
return
FocusableActionDetector
(
actions:
_actionMap
,
focusNode:
focusNode
,
autofocus:
autofocus
,
enabled:
isInteractive
,
onShowFocusHighlight:
_handleFocusHighlightChanged
,
onShowHoverHighlight:
_handleHoverChanged
,
mouseCursor:
mouseCursor
.
resolve
(
states
),
child:
GestureDetector
(
excludeFromSemantics:
!
isInteractive
,
onTapDown:
_handleTapDown
,
onTap:
_handleTap
,
onTapUp:
_handleTapEnd
,
onTapCancel:
_handleTapEnd
,
child:
Semantics
(
enabled:
isInteractive
,
child:
CustomPaint
(
size:
size
,
painter:
painter
,
),
),
),
);
_tristate
=
value
;
markNeedsSemanticsUpdate
();
}
}
/// A base class for a [CustomPainter] that may be passed to
/// [ToggleableStateMixin.buildToggleable] to draw the visual representation of
/// a Toggleable.
///
/// Subclasses must implement the [paint] method to draw the actual visuals of
/// the Toggleable. In their [paint] method subclasses may call
/// [paintRadialReaction] to draw a radial ink reaction for this control.
abstract
class
ToggleablePainter
extends
ChangeNotifier
implements
CustomPainter
{
/// The visual value of the control.
/// The color that should be used in the active state (i.e., when [value] is true).
///
/// Usually set to [ToggleableStateMixin.position].
Animation
<
double
>
get
position
=>
_position
!;
Animation
<
double
>?
_position
;
set
position
(
Animation
<
double
>
value
)
{
if
(
value
==
_position
)
{
/// For example, a checkbox should use this color when checked.
Color
get
activeColor
=>
_activeColor
;
Color
_activeColor
;
set
activeColor
(
Color
value
)
{
assert
(
value
!=
null
);
if
(
value
==
_activeColor
)
return
;
}
_position
?.
removeListener
(
notifyListeners
);
value
.
addListener
(
notifyListeners
);
_position
=
value
;
notifyListeners
();
_activeColor
=
value
;
markNeedsPaint
();
}
/// The
visual value of the radial reaction animation
.
/// The
color that should be used in the inactive state (i.e., when [value] is false)
.
///
/// Usually set to [ToggleableStateMixin.reaction].
Animation
<
double
>
get
reaction
=>
_reaction
!;
Animation
<
double
>?
_reaction
;
set
reaction
(
Animation
<
double
>
value
)
{
if
(
value
==
_reaction
)
{
/// For example, a checkbox should use this color when unchecked.
Color
get
inactiveColor
=>
_inactiveColor
;
Color
_inactiveColor
;
set
inactiveColor
(
Color
value
)
{
assert
(
value
!=
null
);
if
(
value
==
_inactiveColor
)
return
;
}
_reaction
?.
removeListener
(
notifyListeners
);
value
.
addListener
(
notifyListeners
);
_reaction
=
value
;
notifyListeners
();
_inactiveColor
=
value
;
markNeedsPaint
();
}
///
Controls the radial reaction's opacity animation for focus changes
.
///
The color that should be used for the reaction when [hovering] is true
.
///
/// Usually set to [ToggleableStateMixin.reactionFocusFade].
Animation
<
double
>
get
reactionFocusFade
=>
_reactionFocusFade
!;
Animation
<
double
>?
_reactionFocusFade
;
set
reactionFocusFade
(
Animation
<
double
>
value
)
{
if
(
value
==
_reactionFocusFade
)
{
return
;
}
_reactionFocusFade
?.
removeListener
(
notifyListeners
);
value
.
addListener
(
notifyListeners
);
_reactionFocusFade
=
value
;
notifyListeners
();
}
/// Controls the radial reaction's opacity animation for hover changes.
/// Used when the toggleable needs to change the reaction color/transparency,
/// when it is being hovered over.
///
/// Usually set to [ToggleableStateMixin.reactionHoverFade].
Animation
<
double
>
get
reactionHoverFade
=>
_reactionHoverFade
!;
Animation
<
double
>?
_reactionHoverFade
;
set
reactionHoverFade
(
Animation
<
double
>
value
)
{
if
(
value
==
_reactionHoverFade
)
{
/// Defaults to the [activeColor] at alpha [kRadialReactionAlpha].
Color
get
hoverColor
=>
_hoverColor
;
Color
_hoverColor
;
set
hoverColor
(
Color
value
)
{
assert
(
value
!=
null
);
if
(
value
==
_hoverColor
)
return
;
}
_reactionHoverFade
?.
removeListener
(
notifyListeners
);
value
.
addListener
(
notifyListeners
);
_reactionHoverFade
=
value
;
notifyListeners
();
_hoverColor
=
value
;
markNeedsPaint
();
}
/// The color that should be used in the active state (i.e., when
/// [ToggleableStateMixin.value] is true).
/// The color that should be used for the reaction when [hasFocus] is true.
///
/// For example, a checkbox should use this color when checked.
Color
get
activeColor
=>
_activeColor
!;
Color
?
_activeColor
;
set
activeColor
(
Color
value
)
{
if
(
_activeColor
==
value
)
{
/// Used when the toggleable needs to change the reaction color/transparency,
/// when it has focus.
///
/// Defaults to the [activeColor] at alpha [kRadialReactionAlpha].
Color
get
focusColor
=>
_focusColor
;
Color
_focusColor
;
set
focusColor
(
Color
value
)
{
assert
(
value
!=
null
);
if
(
value
==
_focusColor
)
return
;
}
_activeColor
=
value
;
notifyListeners
();
_focusColor
=
value
;
markNeedsPaint
();
}
/// The color that should be used
in the inactive state (i.e., when
///
[ToggleableStateMixin.value] is false)
.
/// The color that should be used
for the reaction when the toggleable is
///
active
.
///
/// For example, a checkbox should use this color when unchecked.
Color
get
inactiveColor
=>
_inactiveColor
!;
Color
?
_inactiveColor
;
set
inactiveColor
(
Color
value
)
{
if
(
_inactiveColor
==
value
)
{
/// Used when the toggleable needs to change the reaction color/transparency
/// that is displayed when the toggleable is active and tapped.
///
/// Defaults to the [activeColor] at alpha [kRadialReactionAlpha].
Color
?
get
reactionColor
=>
_reactionColor
;
Color
?
_reactionColor
;
set
reactionColor
(
Color
?
value
)
{
assert
(
value
!=
null
);
if
(
value
==
_reactionColor
)
return
;
}
_inactiveColor
=
value
;
notifyListeners
();
_reactionColor
=
value
;
markNeedsPaint
();
}
/// The color that should be used for the reaction when the toggleable is
...
...
@@ -435,161 +338,184 @@ abstract class ToggleablePainter extends ChangeNotifier implements CustomPainter
///
/// Used when the toggleable needs to change the reaction color/transparency
/// that is displayed when the toggleable is inactive and tapped.
Color
get
inactiveReactionColor
=>
_inactiveReactionColor
!;
///
/// Defaults to the [activeColor] at alpha [kRadialReactionAlpha].
Color
?
get
inactiveReactionColor
=>
_inactiveReactionColor
;
Color
?
_inactiveReactionColor
;
set
inactiveReactionColor
(
Color
value
)
{
if
(
value
==
_inactiveReactionColor
)
{
set
inactiveReactionColor
(
Color
?
value
)
{
assert
(
value
!=
null
);
if
(
value
==
_inactiveReactionColor
)
return
;
}
_inactiveReactionColor
=
value
;
notifyListeners
();
markNeedsPaint
();
}
/// The color that should be used for the reaction when the toggleable is
/// active.
///
/// Used when the toggleable needs to change the reaction color/transparency
/// that is displayed when the toggleable is active and tapped.
Color
get
reactionColor
=>
_reactionColor
!;
Color
?
_reactionColor
;
set
reactionColor
(
Color
value
)
{
if
(
value
==
_reactionColor
)
{
/// The splash radius for the radial reaction.
double
get
splashRadius
=>
_splashRadius
;
double
_splashRadius
;
set
splashRadius
(
double
value
)
{
if
(
value
==
_splashRadius
)
return
;
}
_reactionColor
=
value
;
notifyListeners
();
_splashRadius
=
value
;
markNeedsPaint
();
}
///
The color that should be used for the reaction when [isHovered] is tr
ue.
///
Called when the control changes val
ue.
///
/// Used when the toggleable needs to change the reaction color/transparency,
/// when it is being hovered over.
Color
get
hoverColor
=>
_hoverColor
!;
Color
?
_hoverColor
;
set
hoverColor
(
Color
value
)
{
if
(
value
==
_hoverColor
)
{
/// If the control is tapped, [onChanged] is called immediately with the new
/// value.
///
/// The control is considered interactive (see [isInteractive]) if this
/// callback is non-null. If the callback is null, then the control is
/// disabled, and non-interactive. A disabled checkbox, for example, is
/// displayed using a grey color and its value cannot be changed.
ValueChanged
<
bool
?>?
get
onChanged
=>
_onChanged
;
ValueChanged
<
bool
?>?
_onChanged
;
set
onChanged
(
ValueChanged
<
bool
?>?
value
)
{
if
(
value
==
_onChanged
)
return
;
final
bool
wasInteractive
=
isInteractive
;
_onChanged
=
value
;
if
(
wasInteractive
!=
isInteractive
)
{
markNeedsPaint
();
markNeedsSemanticsUpdate
();
}
_hoverColor
=
value
;
notifyListeners
();
}
///
The color that should be used for the reaction when [isFocused] is true
.
///
Whether [value] of this control can be changed by user interaction
.
///
/// Used when the toggleable needs to change the reaction color/transparency,
/// when it has focus.
Color
get
focusColor
=>
_focusColor
!;
Color
?
_focusColor
;
set
focusColor
(
Color
value
)
{
if
(
value
==
_focusColor
)
{
return
;
/// The control is considered interactive if the [onChanged] callback is
/// non-null. If the callback is null, then the control is disabled, and
/// non-interactive. A disabled checkbox, for example, is displayed using a
/// grey color and its value cannot be changed.
bool
get
isInteractive
=>
onChanged
!=
null
;
late
TapGestureRecognizer
_tap
;
Offset
?
_downPosition
;
@override
void
attach
(
PipelineOwner
owner
)
{
super
.
attach
(
owner
);
if
(
value
==
false
)
_positionController
.
reverse
();
else
_positionController
.
forward
();
if
(
isInteractive
)
{
switch
(
_reactionController
.
status
)
{
case
AnimationStatus
.
forward
:
_reactionController
.
forward
();
break
;
case
AnimationStatus
.
reverse
:
_reactionController
.
reverse
();
break
;
case
AnimationStatus
.
dismissed
:
case
AnimationStatus
.
completed
:
// nothing to do
break
;
}
}
_focusColor
=
value
;
notifyListeners
();
}
/// The splash radius for the radial reaction.
double
get
splashRadius
=>
_splashRadius
!;
double
?
_splashRadius
;
set
splashRadius
(
double
value
)
{
if
(
value
==
_splashRadius
)
{
return
;
}
_splashRadius
=
value
;
notifyListeners
();
@override
void
detach
()
{
_positionController
.
stop
();
_reactionController
.
stop
();
_reactionHoverFadeController
.
stop
();
_reactionFocusFadeController
.
stop
();
super
.
detach
();
}
/// The [Offset] within the Toggleable at which a pointer touched the Toggleable.
///
/// This is null if currently no pointer is touching the Toggleable.
///
/// Usually set to [ToggleableStateMixin.downPosition].
Offset
?
get
downPosition
=>
_downPosition
;
Offset
?
_downPosition
;
set
downPosition
(
Offset
?
value
)
{
if
(
value
==
_downPosition
)
{
return
;
void
_handleTapDown
(
TapDownDetails
details
)
{
if
(
isInteractive
)
{
_downPosition
=
globalToLocal
(
details
.
globalPosition
);
_reactionController
.
forward
();
}
_downPosition
=
value
;
notifyListeners
();
}
/// True if this toggleable has the input focus.
bool
get
isFocused
=>
_isFocused
!;
bool
?
_isFocused
;
set
isFocused
(
bool
?
value
)
{
if
(
value
==
_isFocused
)
{
void
_handleTap
()
{
if
(!
isInteractive
)
return
;
switch
(
value
)
{
case
false
:
onChanged
!(
true
);
break
;
case
true
:
onChanged
!(
tristate
?
null
:
false
);
break
;
case
null
:
onChanged
!(
false
);
break
;
}
_isFocused
=
value
;
notifyListeners
();
sendSemanticsEvent
(
const
TapSemanticEvent
());
}
/// True if this toggleable is being hovered over by a pointer.
bool
get
isHovered
=>
_isHovered
!;
bool
?
_isHovered
;
set
isHovered
(
bool
?
value
)
{
if
(
value
==
_isHovered
)
{
return
;
}
_isHovered
=
value
;
notifyListeners
();
void
_handleTapUp
(
TapUpDetails
details
)
{
_downPosition
=
null
;
if
(
isInteractive
)
_reactionController
.
reverse
();
}
void
_handleTapCancel
()
{
_downPosition
=
null
;
if
(
isInteractive
)
_reactionController
.
reverse
();
}
@override
bool
hitTestSelf
(
Offset
position
)
=>
true
;
@override
void
handleEvent
(
PointerEvent
event
,
BoxHitTestEntry
entry
)
{
assert
(
debugHandleEvent
(
event
,
entry
));
if
(
event
is
PointerDownEvent
&&
isInteractive
)
_tap
.
addPointer
(
event
);
}
/// Used by subclasses to paint the radial ink reaction for this control.
///
/// The reaction is painted on the given canvas at the given offset. The
/// origin is the center point of the reaction (usually distinct from the
/// [downPosition] at which the user interacted with the control).
void
paintRadialReaction
({
required
Canvas
canvas
,
Offset
offset
=
Offset
.
zero
,
required
Offset
origin
,
})
{
if
(!
reaction
.
isDismissed
||
!
reactionFocusFade
.
isDismissed
||
!
reactionHoverFade
.
isDismissed
)
{
/// point at which the user interacted with the control, which is handled
/// automatically).
void
paintRadialReaction
(
Canvas
canvas
,
Offset
offset
,
Offset
origin
)
{
if
(!
_reaction
.
isDismissed
||
!
_reactionFocusFade
.
isDismissed
||
!
_reactionHoverFade
.
isDismissed
)
{
final
Paint
reactionPaint
=
Paint
()
..
color
=
Color
.
lerp
(
Color
.
lerp
(
Color
.
lerp
(
inactiveReactionColor
,
reactionColor
,
position
.
value
),
Color
.
lerp
(
inactiveReactionColor
,
reactionColor
,
_
position
.
value
),
hoverColor
,
reactionHoverFade
.
value
,
_
reactionHoverFade
.
value
,
),
focusColor
,
reactionFocusFade
.
value
,
_
reactionFocusFade
.
value
,
)!;
final
Offset
center
=
Offset
.
lerp
(
downPosition
??
origin
,
origin
,
reaction
.
value
)!;
final
Offset
center
=
Offset
.
lerp
(
_downPosition
??
origin
,
origin
,
_
reaction
.
value
)!;
final
Animatable
<
double
>
radialReactionRadiusTween
=
Tween
<
double
>(
begin:
0.0
,
end:
splashRadius
,
);
final
double
reactionRadius
=
isFocused
||
isHovered
final
double
reactionRadius
=
hasFocus
||
hovering
?
splashRadius
:
radialReactionRadiusTween
.
evaluate
(
reaction
);
:
radialReactionRadiusTween
.
evaluate
(
_
reaction
);
if
(
reactionRadius
>
0.0
)
{
canvas
.
drawCircle
(
center
+
offset
,
reactionRadius
,
reactionPaint
);
}
}
}
@override
void
dispose
()
{
_position
?.
removeListener
(
notifyListeners
);
_reaction
?.
removeListener
(
notifyListeners
);
_reactionFocusFade
?.
removeListener
(
notifyListeners
);
_reactionHoverFade
?.
removeListener
(
notifyListeners
);
super
.
dispose
();
}
@override
bool
shouldRepaint
(
covariant
CustomPainter
oldDelegate
)
=>
true
;
@override
bool
?
hitTest
(
Offset
position
)
=>
null
;
void
describeSemanticsConfiguration
(
SemanticsConfiguration
config
)
{
super
.
describeSemanticsConfiguration
(
config
);
@override
SemanticsBuilderCallback
?
get
semanticsBuilder
=>
null
;
config
.
isEnabled
=
isInteractive
;
if
(
isInteractive
)
config
.
onTap
=
_handleTap
;
}
@override
bool
shouldRebuildSemantics
(
covariant
CustomPainter
oldDelegate
)
=>
false
;
void
debugFillProperties
(
DiagnosticPropertiesBuilder
properties
)
{
super
.
debugFillProperties
(
properties
);
properties
.
add
(
FlagProperty
(
'value'
,
value:
value
,
ifTrue:
'checked'
,
ifFalse:
'unchecked'
,
showName:
true
));
properties
.
add
(
FlagProperty
(
'isInteractive'
,
value:
isInteractive
,
ifTrue:
'enabled'
,
ifFalse:
'disabled'
,
defaultValue:
true
));
}
}
packages/flutter/test/material/checkbox_test.dart
View file @
328a262e
...
...
@@ -99,7 +99,7 @@ void main() {
),
));
expect
(
tester
.
getSemantics
(
find
.
by
Type
(
Checkbox
)),
matchesSemantics
(
expect
(
tester
.
getSemantics
(
find
.
by
WidgetPredicate
((
Widget
widget
)
=>
widget
.
runtimeType
.
toString
()
==
'_CheckboxRenderObjectWidget'
)),
matchesSemantics
(
hasCheckedState:
true
,
hasEnabledState:
true
,
// isFocusable is delayed by 1 frame.
...
...
@@ -108,7 +108,7 @@ void main() {
await
tester
.
pump
();
// isFocusable should be false now after the 1 frame delay.
expect
(
tester
.
getSemantics
(
find
.
by
Type
(
Checkbox
)),
matchesSemantics
(
expect
(
tester
.
getSemantics
(
find
.
by
WidgetPredicate
((
Widget
widget
)
=>
widget
.
runtimeType
.
toString
()
==
'_CheckboxRenderObjectWidget'
)),
matchesSemantics
(
hasCheckedState:
true
,
hasEnabledState:
true
,
));
...
...
@@ -120,7 +120,7 @@ void main() {
),
));
expect
(
tester
.
getSemantics
(
find
.
by
Type
(
Checkbox
)),
matchesSemantics
(
expect
(
tester
.
getSemantics
(
find
.
by
WidgetPredicate
((
Widget
widget
)
=>
widget
.
runtimeType
.
toString
()
==
'_CheckboxRenderObjectWidget'
)),
matchesSemantics
(
hasCheckedState:
true
,
hasEnabledState:
true
,
isChecked:
true
,
...
...
@@ -290,7 +290,7 @@ void main() {
);
await
tester
.
tap
(
find
.
byType
(
Checkbox
));
final
RenderObject
object
=
tester
.
firstRenderObject
(
find
.
byType
(
Checkbox
));
final
RenderObject
object
=
tester
.
firstRenderObject
(
find
.
byType
(
Focus
));
expect
(
checkboxValue
,
true
);
expect
(
semanticEvent
,
<
String
,
dynamic
>{
...
...
@@ -319,8 +319,10 @@ void main() {
);
}
RenderBox
getCheckboxRenderer
()
{
return
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
Checkbox
));
RenderToggleable
getCheckboxRenderer
()
{
return
tester
.
renderObject
<
RenderToggleable
>(
find
.
byWidgetPredicate
((
Widget
widget
)
{
return
widget
.
runtimeType
.
toString
()
==
'_CheckboxRenderObjectWidget'
;
}));
}
await
tester
.
pumpWidget
(
buildFrame
(
false
));
...
...
@@ -375,8 +377,10 @@ void main() {
);
}
RenderBox
getCheckboxRenderer
()
{
return
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
Checkbox
));
RenderToggleable
getCheckboxRenderer
()
{
return
tester
.
renderObject
<
RenderToggleable
>(
find
.
byWidgetPredicate
((
Widget
widget
)
{
return
widget
.
runtimeType
.
toString
()
==
'_CheckboxRenderObjectWidget'
;
}));
}
await
tester
.
pumpWidget
(
buildFrame
(
checkColor:
checkColor
));
...
...
@@ -449,10 +453,11 @@ void main() {
paints
..
circle
(
color:
Colors
.
orange
[
500
])
..
drrect
(
color:
const
Color
(
0x8a000000
),
outer:
RRect
.
fromLTRBR
(
15.0
,
15.0
,
33.0
,
33.0
,
const
Radius
.
circular
(
1.0
)),
inner:
RRect
.
fromLTRBR
(
17.0
,
17.0
,
31.0
,
31.0
,
const
Radius
.
circular
(-
1.0
)),
),
color:
const
Color
(
0x8a000000
),
outer:
RRect
.
fromLTRBR
(
391.0
,
291.0
,
409.0
,
309.0
,
const
Radius
.
circular
(
1.0
)),
inner:
RRect
.
fromLTRBR
(
393.0
,
293.0
,
407.0
,
307.0
,
const
Radius
.
circular
(-
1.0
))),
);
// Check what happens when disabled.
...
...
@@ -464,10 +469,11 @@ void main() {
Material
.
of
(
tester
.
element
(
find
.
byType
(
Checkbox
))),
paints
..
drrect
(
color:
const
Color
(
0x61000000
),
outer:
RRect
.
fromLTRBR
(
15.0
,
15.0
,
33.0
,
33.0
,
const
Radius
.
circular
(
1.0
)),
inner:
RRect
.
fromLTRBR
(
17.0
,
17.0
,
31.0
,
31.0
,
const
Radius
.
circular
(-
1.0
)),
),
color:
const
Color
(
0x61000000
),
outer:
RRect
.
fromLTRBR
(
391.0
,
291.0
,
409.0
,
309.0
,
const
Radius
.
circular
(
1.0
)),
inner:
RRect
.
fromLTRBR
(
393.0
,
293.0
,
407.0
,
307.0
,
const
Radius
.
circular
(-
1.0
))),
);
});
...
...
@@ -819,8 +825,10 @@ void main() {
);
}
RenderBox
getCheckboxRenderer
()
{
return
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
Checkbox
));
RenderToggleable
getCheckboxRenderer
()
{
return
tester
.
renderObject
<
RenderToggleable
>(
find
.
byWidgetPredicate
((
Widget
widget
)
{
return
widget
.
runtimeType
.
toString
()
==
'_CheckboxRenderObjectWidget'
;
}));
}
await
tester
.
pumpWidget
(
buildFrame
(
enabled:
true
));
...
...
@@ -870,8 +878,10 @@ void main() {
);
}
RenderBox
getCheckboxRenderer
()
{
return
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
Checkbox
));
RenderToggleable
getCheckboxRenderer
()
{
return
tester
.
renderObject
<
RenderToggleable
>(
find
.
byWidgetPredicate
((
Widget
widget
)
{
return
widget
.
runtimeType
.
toString
()
==
'_CheckboxRenderObjectWidget'
;
}));
}
await
tester
.
pumpWidget
(
buildFrame
());
...
...
@@ -927,9 +937,11 @@ void main() {
paints
..
drrect
(
color:
const
Color
(
0xfff44336
),
outer:
RRect
.
fromLTRBR
(
15.0
,
15.0
,
33.0
,
33.0
,
const
Radius
.
circular
(
5
)),
inner:
RRect
.
fromLTRBR
(
19.0
,
19.0
,
29.0
,
29.0
,
const
Radius
.
circular
(
1
)),
),
outer:
RRect
.
fromLTRBR
(
391.0
,
291.0
,
409.0
,
309.0
,
const
Radius
.
circular
(
5
)),
inner:
RRect
.
fromLTRBR
(
395.0
,
295.0
,
405.0
,
305.0
,
const
Radius
.
circular
(
1
)))
,
);
});
...
...
@@ -1172,29 +1184,6 @@ void main() {
await
gesture
.
up
();
});
testWidgets
(
'Do not crash when widget disappears while pointer is down'
,
(
WidgetTester
tester
)
async
{
Widget
buildCheckbox
(
bool
show
)
{
return
MaterialApp
(
home:
Material
(
child:
Center
(
child:
show
?
Checkbox
(
value:
true
,
onChanged:
(
_
)
{
})
:
Container
(),
),
),
);
}
await
tester
.
pumpWidget
(
buildCheckbox
(
true
));
final
Offset
center
=
tester
.
getCenter
(
find
.
byType
(
Checkbox
));
// Put a pointer down on the screen.
final
TestGesture
gesture
=
await
tester
.
startGesture
(
center
);
await
tester
.
pump
();
// While the pointer is down, the widget disappears.
await
tester
.
pumpWidget
(
buildCheckbox
(
false
));
expect
(
find
.
byType
(
Checkbox
),
findsNothing
);
// Release pointer after widget disappeared.
gesture
.
up
();
});
}
class
_SelectedGrabMouseCursor
extends
MaterialStateMouseCursor
{
...
...
packages/flutter/test/material/radio_test.dart
View file @
328a262e
...
...
@@ -308,7 +308,7 @@ void main() {
));
await
tester
.
tap
(
find
.
byKey
(
key
));
final
RenderObject
object
=
tester
.
firstRenderObject
(
find
.
by
Key
(
key
));
final
RenderObject
object
=
tester
.
firstRenderObject
(
find
.
by
Type
(
Focus
));
expect
(
radioValue
,
1
);
expect
(
semanticEvent
,
<
String
,
dynamic
>{
...
...
@@ -1078,29 +1078,4 @@ void main() {
reason:
'Hovered Radio should use overlay color
$hoverOverlayColor
over
$hoverColor
'
,
);
});
testWidgets
(
'Do not crash when widget disappears while pointer is down'
,
(
WidgetTester
tester
)
async
{
final
Key
key
=
UniqueKey
();
Widget
buildRadio
(
bool
show
)
{
return
MaterialApp
(
home:
Material
(
child:
Center
(
child:
show
?
Radio
<
bool
>(
key:
key
,
value:
true
,
groupValue:
false
,
onChanged:
(
_
)
{
})
:
Container
(),
)
),
);
}
await
tester
.
pumpWidget
(
buildRadio
(
true
));
final
Offset
center
=
tester
.
getCenter
(
find
.
byKey
(
key
));
// Put a pointer down on the screen.
final
TestGesture
gesture
=
await
tester
.
startGesture
(
center
);
await
tester
.
pump
();
// While the pointer is down, the widget disappears.
await
tester
.
pumpWidget
(
buildRadio
(
false
));
expect
(
find
.
byKey
(
key
),
findsNothing
);
// Release pointer after widget disappeared.
gesture
.
up
();
});
}
packages/flutter/test/material/switch_list_tile_test.dart
View file @
328a262e
...
...
@@ -37,7 +37,7 @@ void main() {
expect
(
log
,
equals
(<
dynamic
>[
false
,
'-'
,
false
]));
});
testWidgets
(
'SwitchListTile
semantics
test'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'SwitchListTile
control
test'
,
(
WidgetTester
tester
)
async
{
final
SemanticsTester
semantics
=
SemanticsTester
(
tester
);
await
tester
.
pumpWidget
(
wrap
(
child:
Column
(
...
...
packages/flutter/test/material/switch_test.dart
View file @
328a262e
...
...
@@ -301,7 +301,8 @@ void main() {
paints
..
rrect
(
color:
const
Color
(
0x52000000
),
// Black with 32% opacity
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
)))
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
)))
..
circle
(
color:
const
Color
(
0x33000000
))
..
circle
(
color:
const
Color
(
0x24000000
))
..
circle
(
color:
const
Color
(
0x1f000000
))
...
...
@@ -316,7 +317,8 @@ void main() {
paints
..
rrect
(
color:
Colors
.
blue
[
600
]!.
withAlpha
(
0x80
),
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
)))
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
)))
..
circle
(
color:
const
Color
(
0x33000000
))
..
circle
(
color:
const
Color
(
0x24000000
))
..
circle
(
color:
const
Color
(
0x1f000000
))
...
...
@@ -349,7 +351,8 @@ void main() {
paints
..
rrect
(
color:
Colors
.
black12
,
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
)))
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
)))
..
circle
(
color:
const
Color
(
0x33000000
))
..
circle
(
color:
const
Color
(
0x24000000
))
..
circle
(
color:
const
Color
(
0x1f000000
))
...
...
@@ -380,7 +383,8 @@ void main() {
paints
..
rrect
(
color:
Colors
.
black12
,
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
)))
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
)))
..
circle
(
color:
const
Color
(
0x33000000
))
..
circle
(
color:
const
Color
(
0x24000000
))
..
circle
(
color:
const
Color
(
0x1f000000
))
...
...
@@ -423,7 +427,8 @@ void main() {
paints
..
rrect
(
color:
Colors
.
blue
[
500
],
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
)))
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
)))
..
circle
(
color:
const
Color
(
0x33000000
))
..
circle
(
color:
const
Color
(
0x24000000
))
..
circle
(
color:
const
Color
(
0x1f000000
))
...
...
@@ -437,7 +442,8 @@ void main() {
paints
..
rrect
(
color:
Colors
.
green
[
500
],
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
)))
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
)))
..
circle
(
color:
const
Color
(
0x33000000
))
..
circle
(
color:
const
Color
(
0x24000000
))
..
circle
(
color:
const
Color
(
0x1f000000
))
...
...
@@ -522,19 +528,19 @@ void main() {
await
gesture
.
up
();
await
tester
.
pump
();
expect
(
value
,
isFalse
);
final
ToggleableStateMixin
state
=
tester
.
state
<
ToggleableStateMixin
>(
final
RenderToggleable
renderObject
=
tester
.
renderObject
<
RenderToggleable
>(
find
.
descendant
(
of:
find
.
byType
(
Switch
),
matching:
find
.
byWidgetPredicate
(
(
Widget
widget
)
=>
widget
.
runtimeType
.
toString
()
==
'_
MaterialSwitch
'
,
(
Widget
widget
)
=>
widget
.
runtimeType
.
toString
()
==
'_
SwitchRenderObjectWidget
'
,
),
),
);
expect
(
state
.
position
.
value
,
lessThan
(
0.5
));
expect
(
renderObject
.
position
.
value
,
lessThan
(
0.5
));
await
tester
.
pump
();
await
tester
.
pumpAndSettle
();
expect
(
value
,
isFalse
);
expect
(
state
.
position
.
value
,
0
);
expect
(
renderObject
.
position
.
value
,
0
);
// Move past the middle.
gesture
=
await
tester
.
startGesture
(
tester
.
getRect
(
find
.
byType
(
Switch
)).
center
);
...
...
@@ -543,12 +549,12 @@ void main() {
await
gesture
.
up
();
await
tester
.
pump
();
expect
(
value
,
isTrue
);
expect
(
state
.
position
.
value
,
greaterThan
(
0.5
));
expect
(
renderObject
.
position
.
value
,
greaterThan
(
0.5
));
await
tester
.
pump
();
await
tester
.
pumpAndSettle
();
expect
(
value
,
isTrue
);
expect
(
state
.
position
.
value
,
1.0
);
expect
(
renderObject
.
position
.
value
,
1.0
);
// Now move back to the left, the revert animation should play.
gesture
=
await
tester
.
startGesture
(
tester
.
getRect
(
find
.
byType
(
Switch
)).
center
);
...
...
@@ -557,12 +563,12 @@ void main() {
await
gesture
.
up
();
await
tester
.
pump
();
expect
(
value
,
isTrue
);
expect
(
state
.
position
.
value
,
lessThan
(
0.5
));
expect
(
renderObject
.
position
.
value
,
lessThan
(
0.5
));
await
tester
.
pump
();
await
tester
.
pumpAndSettle
();
expect
(
value
,
isTrue
);
expect
(
state
.
position
.
value
,
1.0
);
expect
(
renderObject
.
position
.
value
,
1.0
);
});
testWidgets
(
'switch has semantic events'
,
(
WidgetTester
tester
)
async
{
...
...
@@ -595,7 +601,7 @@ void main() {
),
);
await
tester
.
tap
(
find
.
byType
(
Switch
));
final
RenderObject
object
=
tester
.
firstRenderObject
(
find
.
byType
(
Switch
));
final
RenderObject
object
=
tester
.
firstRenderObject
(
find
.
byType
(
Focus
));
expect
(
value
,
true
);
expect
(
semanticEvent
,
<
String
,
dynamic
>{
...
...
@@ -744,7 +750,8 @@ void main() {
paints
..
rrect
(
color:
const
Color
(
0x801e88e5
),
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
)))
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
)))
..
circle
(
color:
Colors
.
orange
[
500
])
..
circle
(
color:
const
Color
(
0x33000000
))
..
circle
(
color:
const
Color
(
0x24000000
))
...
...
@@ -762,7 +769,8 @@ void main() {
paints
..
rrect
(
color:
const
Color
(
0x52000000
),
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
)))
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
)))
..
circle
(
color:
Colors
.
orange
[
500
])
..
circle
(
color:
const
Color
(
0x33000000
))
..
circle
(
color:
const
Color
(
0x24000000
))
...
...
@@ -780,7 +788,8 @@ void main() {
paints
..
rrect
(
color:
const
Color
(
0x1f000000
),
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
)))
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
)))
..
circle
(
color:
const
Color
(
0x33000000
))
..
circle
(
color:
const
Color
(
0x24000000
))
..
circle
(
color:
const
Color
(
0x1f000000
))
...
...
@@ -845,7 +854,8 @@ void main() {
paints
..
rrect
(
color:
const
Color
(
0x801e88e5
),
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
)))
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
)))
..
circle
(
color:
const
Color
(
0x33000000
))
..
circle
(
color:
const
Color
(
0x24000000
))
..
circle
(
color:
const
Color
(
0x1f000000
))
...
...
@@ -865,7 +875,8 @@ void main() {
paints
..
rrect
(
color:
const
Color
(
0x801e88e5
),
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
)))
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
)))
..
circle
(
color:
Colors
.
orange
[
500
])
..
circle
(
color:
const
Color
(
0x33000000
))
..
circle
(
color:
const
Color
(
0x24000000
))
...
...
@@ -881,7 +892,8 @@ void main() {
paints
..
rrect
(
color:
const
Color
(
0x1f000000
),
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
)))
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
)))
..
circle
(
color:
const
Color
(
0x33000000
))
..
circle
(
color:
const
Color
(
0x24000000
))
..
circle
(
color:
const
Color
(
0x1f000000
))
...
...
@@ -1058,7 +1070,8 @@ void main() {
),
);
final
ToggleableStateMixin
oldSwitchState
=
tester
.
state
(
find
.
byWidgetPredicate
((
Widget
widget
)
=>
widget
.
runtimeType
.
toString
()
==
'_MaterialSwitch'
));
final
RenderToggleable
oldSwitchRenderObject
=
tester
.
renderObject
(
find
.
byWidgetPredicate
((
Widget
widget
)
=>
widget
is
LeafRenderObjectWidget
));
stateSetter
(()
{
value
=
false
;
});
await
tester
.
pump
();
...
...
@@ -1066,12 +1079,14 @@ void main() {
stateSetter
(()
{
enabled
=
false
;
});
await
tester
.
pump
();
final
ToggleableStateMixin
updatedSwitchState
=
tester
.
state
(
find
.
byWidgetPredicate
((
Widget
widget
)
=>
widget
.
runtimeType
.
toString
()
==
'_MaterialSwitch'
));
final
RenderToggleable
updatedSwitchRenderObject
=
tester
.
renderObject
(
find
.
byWidgetPredicate
((
Widget
widget
)
=>
widget
is
LeafRenderObjectWidget
));
expect
(
updatedSwitchState
.
isInteractive
,
false
);
expect
(
updatedSwitchState
,
oldSwitchState
);
expect
(
updatedSwitchState
.
position
.
isCompleted
,
false
);
expect
(
updatedSwitchState
.
position
.
isDismissed
,
false
);
expect
(
updatedSwitchRenderObject
.
isInteractive
,
false
);
expect
(
updatedSwitchRenderObject
,
oldSwitchRenderObject
);
expect
(
updatedSwitchRenderObject
.
position
.
isCompleted
,
false
);
expect
(
updatedSwitchRenderObject
.
position
.
isDismissed
,
false
);
});
testWidgets
(
'Switch thumb color resolves in active/enabled states'
,
(
WidgetTester
tester
)
async
{
...
...
@@ -1122,7 +1137,8 @@ void main() {
paints
..
rrect
(
color:
Colors
.
black12
,
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
)))
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
)))
..
circle
(
color:
const
Color
(
0x33000000
))
..
circle
(
color:
const
Color
(
0x24000000
))
..
circle
(
color:
const
Color
(
0x1f000000
))
...
...
@@ -1138,7 +1154,8 @@ void main() {
paints
..
rrect
(
color:
Colors
.
black12
,
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
)))
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
)))
..
circle
(
color:
const
Color
(
0x33000000
))
..
circle
(
color:
const
Color
(
0x24000000
))
..
circle
(
color:
const
Color
(
0x1f000000
))
...
...
@@ -1154,7 +1171,8 @@ void main() {
paints
..
rrect
(
color:
const
Color
(
0x52000000
),
// Black with 32% opacity,
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
)))
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
)))
..
circle
(
color:
const
Color
(
0x33000000
))
..
circle
(
color:
const
Color
(
0x24000000
))
..
circle
(
color:
const
Color
(
0x1f000000
))
...
...
@@ -1170,7 +1188,8 @@ void main() {
paints
..
rrect
(
color:
Colors
.
black12
,
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
)))
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
)))
..
circle
(
color:
const
Color
(
0x33000000
))
..
circle
(
color:
const
Color
(
0x24000000
))
..
circle
(
color:
const
Color
(
0x1f000000
))
...
...
@@ -1227,7 +1246,8 @@ void main() {
paints
..
rrect
(
color:
const
Color
(
0x801e88e5
),
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
)))
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
)))
..
circle
(
color:
const
Color
(
0x1f000000
))
..
circle
(
color:
const
Color
(
0x33000000
))
..
circle
(
color:
const
Color
(
0x24000000
))
...
...
@@ -1248,7 +1268,8 @@ void main() {
paints
..
rrect
(
color:
const
Color
(
0x801e88e5
),
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
)))
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
)))
..
circle
(
color:
const
Color
(
0x1f000000
))
..
circle
(
color:
const
Color
(
0x33000000
))
..
circle
(
color:
const
Color
(
0x24000000
))
...
...
@@ -1306,7 +1327,8 @@ void main() {
paints
..
rrect
(
color:
inactiveDisabledTrackColor
,
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
))),
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
))),
reason:
'Inactive disabled switch track should use this value'
,
);
...
...
@@ -1318,7 +1340,8 @@ void main() {
paints
..
rrect
(
color:
activeDisabledTrackColor
,
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
))),
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
))),
reason:
'Active disabled switch should match these colors'
,
);
...
...
@@ -1330,7 +1353,8 @@ void main() {
paints
..
rrect
(
color:
inactiveEnabledTrackColor
,
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
))),
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
))),
reason:
'Inactive enabled switch should match these colors'
,
);
...
...
@@ -1342,7 +1366,8 @@ void main() {
paints
..
rrect
(
color:
inactiveDisabledTrackColor
,
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
))),
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
))),
reason:
'Inactive disabled switch should match these colors'
,
);
});
...
...
@@ -1395,7 +1420,8 @@ void main() {
paints
..
rrect
(
color:
focusedTrackColor
,
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
))),
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
))),
reason:
'Inactive enabled switch should match these colors'
,
);
...
...
@@ -1411,7 +1437,8 @@ void main() {
paints
..
rrect
(
color:
hoveredTrackColor
,
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
))),
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
))),
reason:
'Inactive enabled switch should match these colors'
,
);
});
...
...
@@ -1461,7 +1488,8 @@ void main() {
paints
..
rrect
(
color:
Colors
.
black12
,
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
)))
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
)))
..
circle
(
color:
const
Color
(
0x33000000
))
..
circle
(
color:
const
Color
(
0x24000000
))
..
circle
(
color:
const
Color
(
0x1f000000
))
...
...
@@ -1610,27 +1638,4 @@ void main() {
reason:
'Hovered Switch should use overlay color
$hoverOverlayColor
over
$hoverColor
'
,
);
});
testWidgets
(
'Do not crash when widget disappears while pointer is down'
,
(
WidgetTester
tester
)
async
{
Widget
buildSwitch
(
bool
show
)
{
return
MaterialApp
(
home:
Material
(
child:
Center
(
child:
show
?
Switch
(
value:
true
,
onChanged:
(
_
)
{
})
:
Container
(),
),
),
);
}
await
tester
.
pumpWidget
(
buildSwitch
(
true
));
final
Offset
center
=
tester
.
getCenter
(
find
.
byType
(
Switch
));
// Put a pointer down on the screen.
final
TestGesture
gesture
=
await
tester
.
startGesture
(
center
);
await
tester
.
pump
();
// While the pointer is down, the widget disappears.
await
tester
.
pumpWidget
(
buildSwitch
(
false
));
expect
(
find
.
byType
(
Switch
),
findsNothing
);
// Release pointer after widget disappeared.
gesture
.
up
();
});
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment