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
14552a96
Unverified
Commit
14552a96
authored
Mar 02, 2021
by
Michael Goderbauer
Committed by
GitHub
Mar 02, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Toggable Refactor (#76745)
parent
fc99e7c0
Changes
8
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
1048 additions
and
1307 deletions
+1048
-1307
checkbox.dart
packages/flutter/lib/src/material/checkbox.dart
+116
-229
radio.dart
packages/flutter/lib/src/material/radio.dart
+62
-201
switch.dart
packages/flutter/lib/src/material/switch.dart
+304
-416
toggleable.dart
packages/flutter/lib/src/material/toggleable.dart
+430
-356
checkbox_test.dart
packages/flutter/test/material/checkbox_test.dart
+46
-35
radio_test.dart
packages/flutter/test/material/radio_test.dart
+26
-1
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
+63
-68
No files found.
packages/flutter/lib/src/material/checkbox.dart
View file @
14552a96
...
...
@@ -290,56 +290,39 @@ class Checkbox extends StatefulWidget {
_CheckboxState
createState
()
=>
_CheckboxState
();
}
class
_CheckboxState
extends
State
<
Checkbox
>
with
TickerProviderStateMixin
{
bool
get
enabled
=>
widget
.
onChanged
!=
null
;
late
Map
<
Type
,
Action
<
Intent
>>
_actionMap
;
class
_CheckboxState
extends
State
<
Checkbox
>
with
TickerProviderStateMixin
,
ToggleableStateMixin
{
final
_CheckboxPainter
_painter
=
_CheckboxPainter
()
;
bool
?
_previousValue
;
@override
void
initState
()
{
super
.
initState
();
_actionMap
=
<
Type
,
Action
<
Intent
>>{
ActivateIntent:
CallbackAction
<
ActivateIntent
>(
onInvoke:
_actionHandler
),
};
_previousValue
=
widget
.
value
;
}
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
;
}
@override
void
didUpdateWidget
(
Checkbox
oldWidget
)
{
super
.
didUpdateWidget
(
oldWidget
);
if
(
oldWidget
.
value
!=
widget
.
value
)
{
_previousValue
=
oldWidget
.
value
;
animateToValue
();
}
final
RenderObject
renderObject
=
context
.
findRenderObject
()!;
renderObject
.
sendSemanticsEvent
(
const
TapSemanticEvent
());
}
bool
_focused
=
false
;
void
_handleFocusHighlightChanged
(
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
;
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
,
};
@override
bool
get
tristate
=>
widget
.
tristate
;
@override
bool
?
get
value
=>
widget
.
value
;
MaterialStateProperty
<
Color
?>
get
_widgetFillColor
{
return
MaterialStateProperty
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
...
...
@@ -386,14 +369,17 @@ class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin {
break
;
}
size
+=
effectiveVisualDensity
.
baseSizeAdjustment
;
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
);
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
);
});
// 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
)
...
...
@@ -403,13 +389,13 @@ class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin {
??
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
)
...
...
@@ -426,194 +412,95 @@ class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin {
??
effectiveActiveColor
.
withAlpha
(
kRadialReactionAlpha
);
final
Color
effectiveCheckColor
=
widget
.
checkColor
??
themeData
.
checkboxTheme
.
checkColor
?.
resolve
(
_
states
)
??
themeData
.
checkboxTheme
.
checkColor
?.
resolve
(
states
)
??
const
Color
(
0xFFFFFFFF
);
return
FocusableActionDetector
(
actions:
_actionMap
,
return
Semantics
(
checked:
widget
.
value
==
true
,
child:
buildToggleable
(
mouseCursor:
effectiveMouseCursor
,
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
)),
),
);
},
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
,
),
);
}
}
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
);
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
;
@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
,
);
@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
,
);
class
_CheckboxPainter
extends
ToggleablePainter
{
Color
get
checkColor
=>
_checkColor
!;
Color
?
_checkColor
;
set
checkColor
(
Color
value
)
{
if
(
_checkColor
==
value
)
{
return
;
}
_checkColor
=
value
;
notifyListeners
();
}
bool
?
_oldValue
;
Color
checkColor
;
OutlinedBorder
shape
;
BorderSide
?
side
;
bool
?
get
value
=>
_value
;
bool
?
_value
;
set
value
(
bool
?
value
)
{
if
(
_value
==
value
)
{
return
;
}
_value
=
value
;
notifyListeners
();
}
@override
set
value
(
bool
?
newValue
)
{
if
(
newValue
==
value
)
bool
?
get
previousValue
=>
_previousValue
;
bool
?
_previousValue
;
set
previousValue
(
bool
?
value
)
{
if
(
_previousValue
==
value
)
{
return
;
}
_previousValue
=
value
;
notifyListeners
();
}
OutlinedBorder
get
shape
=>
_shape
!;
OutlinedBorder
?
_shape
;
set
shape
(
OutlinedBorder
value
)
{
if
(
_shape
==
value
)
{
return
;
_oldValue
=
value
;
super
.
value
=
newValue
;
}
_shape
=
value
;
notifyListeners
();
}
@override
void
describeSemanticsConfiguration
(
SemanticsConfiguration
config
)
{
super
.
describeSemanticsConfiguration
(
config
);
config
.
isChecked
=
value
==
true
;
BorderSide
?
get
side
=>
_side
;
BorderSide
?
_side
;
set
side
(
BorderSide
?
value
)
{
if
(
_side
==
value
)
{
return
;
}
_side
=
value
;
notifyListeners
();
}
// The square outer bounds of the checkbox at t, with the specified origin.
...
...
@@ -644,10 +531,11 @@ class _RenderCheckbox extends RenderToggleable {
void
_drawBorder
(
Canvas
canvas
,
Rect
outer
,
double
t
,
Paint
paint
)
{
assert
(
t
>=
0.0
&&
t
<=
0.5
);
OutlinedBorder
resolvedShape
=
shape
;
if
(
side
==
null
)
{
shape
=
s
hape
.
copyWith
(
side:
BorderSide
(
width:
2
,
color:
paint
.
color
));
resolvedShape
=
resolvedS
hape
.
copyWith
(
side:
BorderSide
(
width:
2
,
color:
paint
.
color
));
}
s
hape
.
copyWith
(
side:
side
).
paint
(
canvas
,
outer
);
resolvedS
hape
.
copyWith
(
side:
side
).
paint
(
canvas
,
outer
);
}
void
_drawCheck
(
Canvas
canvas
,
Offset
origin
,
double
t
,
Paint
paint
)
{
...
...
@@ -686,19 +574,18 @@ class _RenderCheckbox extends RenderToggleable {
}
@override
void
paint
(
PaintingContext
context
,
Offset
offset
)
{
final
Canvas
canvas
=
context
.
canvas
;
paintRadialReaction
(
canvas
,
offset
,
size
.
center
(
Offset
.
zero
));
void
paint
(
Canvas
canvas
,
Size
size
)
{
paintRadialReaction
(
canvas:
canvas
,
origin:
size
.
center
(
Offset
.
zero
));
final
Paint
strokePaint
=
_createStrokePaint
();
final
Offset
origin
=
offset
+
(
size
/
2.0
-
const
Size
.
square
(
_kEdgeSize
)
/
2.0
as
Offset
)
;
final
Offset
origin
=
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
(
_old
Value
==
false
||
value
==
false
)
{
if
(
previous
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
);
...
...
@@ -710,7 +597,7 @@ class _RenderCheckbox extends RenderToggleable {
canvas
.
drawPath
(
emptyCheckboxPath
,
paint
);
final
double
tShrink
=
(
t
-
0.5
)
*
2.0
;
if
(
_old
Value
==
null
||
value
==
null
)
if
(
previous
Value
==
null
||
value
==
null
)
_drawDash
(
canvas
,
origin
,
tShrink
,
strokePaint
);
else
_drawCheck
(
canvas
,
origin
,
tShrink
,
strokePaint
);
...
...
@@ -722,7 +609,7 @@ class _RenderCheckbox extends RenderToggleable {
if
(
tNormalized
<=
0.5
)
{
final
double
tShrink
=
1.0
-
tNormalized
*
2.0
;
if
(
_old
Value
==
true
)
if
(
previous
Value
==
true
)
_drawCheck
(
canvas
,
origin
,
tShrink
,
strokePaint
);
else
_drawDash
(
canvas
,
origin
,
tShrink
,
strokePaint
);
...
...
packages/flutter/lib/src/material/radio.dart
View file @
14552a96
...
...
@@ -353,64 +353,47 @@ 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
{
bool
get
enabled
=>
widget
.
onChanged
!=
null
;
late
Map
<
Type
,
Action
<
Intent
>>
_actionMap
;
class
_RadioState
<
T
>
extends
State
<
Radio
<
T
>>
with
TickerProviderStateMixin
,
ToggleableStateMixin
{
final
_RadioPainter
_painter
=
_RadioPainter
();
@override
void
initState
()
{
super
.
initState
();
_actionMap
=
<
Type
,
Action
<
Intent
>>{
ActivateIntent:
CallbackAction
<
ActivateIntent
>(
onInvoke:
_actionHandler
,
),
};
void
_handleChanged
(
bool
?
selected
)
{
if
(
selected
==
null
)
{
widget
.
onChanged
!(
null
);
return
;
}
void
_actionHandler
(
ActivateIntent
intent
)
{
if
(
widget
.
onChanged
!=
null
)
{
if
(
selected
)
{
widget
.
onChanged
!(
widget
.
value
);
}
final
RenderObject
renderObject
=
context
.
findRenderObject
()!;
renderObject
.
sendSemanticsEvent
(
const
TapSemanticEvent
());
}
bool
_focused
=
false
;
void
_handleHighlightChanged
(
bool
focused
)
{
if
(
_focused
!=
focused
)
{
setState
(()
{
_focused
=
focused
;
});
@override
void
didUpdateWidget
(
Radio
<
T
>
oldWidget
)
{
super
.
didUpdateWidget
(
oldWidget
);
if
(
widget
.
_selected
!=
oldWidget
.
_selected
)
{
animateToValue
();
}
}
bool
_hovering
=
false
;
void
_handleHoverChanged
(
bool
hovering
)
{
if
(
_hovering
!=
hovering
)
{
setState
(()
{
_hovering
=
hovering
;
});
}
@override
void
dispose
()
{
_painter
.
dispose
();
super
.
dispose
();
}
void
_handleChanged
(
bool
?
selected
)
{
if
(
selected
==
null
)
{
widget
.
onChanged
!(
null
);
return
;
}
if
(
selected
)
{
widget
.
onChanged
!(
widget
.
value
);
}
}
@override
ValueChanged
<
bool
?>?
get
onChanged
=>
widget
.
onChanged
!=
null
?
_handleChanged
:
null
;
bool
get
_selected
=>
widget
.
value
==
widget
.
groupValue
;
@override
bool
get
tristate
=>
widget
.
toggleable
;
Set
<
MaterialState
>
get
_states
=>
<
MaterialState
>{
if
(!
enabled
)
MaterialState
.
disabled
,
if
(
_hovering
)
MaterialState
.
hovered
,
if
(
_focused
)
MaterialState
.
focused
,
if
(
_selected
)
MaterialState
.
selected
,
};
@override
bool
?
get
value
=>
widget
.
_selected
;
MaterialStateProperty
<
Color
?>
get
_widgetFillColor
{
return
MaterialStateProperty
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
...
...
@@ -457,15 +440,17 @@ class _RadioState<T> extends State<Radio<T>> with TickerProviderStateMixin {
break
;
}
size
+=
effectiveVisualDensity
.
baseSizeAdjustment
;
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
);
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
);
});
// 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
)
...
...
@@ -475,13 +460,13 @@ class _RadioState<T> extends State<Radio<T>> with TickerProviderStateMixin {
??
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
)
...
...
@@ -497,156 +482,40 @@ class _RadioState<T> extends State<Radio<T>> with TickerProviderStateMixin {
??
themeData
.
radioTheme
.
overlayColor
?.
resolve
(
inactivePressedStates
)
??
effectiveActiveColor
.
withAlpha
(
kRadialReactionAlpha
);
return
FocusableActionDetector
(
actions:
_actionMap
,
return
Semantics
(
inMutuallyExclusiveGroup:
true
,
checked:
widget
.
_selected
,
child:
buildToggleable
(
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
,
);
},
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
),
);
}
}
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
;
class
_RadioPainter
extends
ToggleablePainter
{
@override
_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
,
);
void
paint
(
Canvas
canvas
,
Size
size
)
{
paintRadialReaction
(
canvas:
canvas
,
origin:
size
.
center
(
Offset
.
zero
));
@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
;
final
Offset
center
=
(
Offset
.
zero
&
size
).
center
;
// Outer circle
final
Paint
paint
=
Paint
()
...
...
@@ -661,12 +530,4 @@ class _RenderRadio extends RenderToggleable {
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 @
14552a96
...
...
@@ -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
ful
Widget
{
class
Switch
extends
State
less
Widget
{
/// Creates a material design switch.
///
/// The switch itself does not maintain any state. Instead, when the state of
...
...
@@ -359,8 +359,87 @@ class Switch extends StatefulWidget {
/// {@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
_SwitchState
createState
()
=>
_SwitchState
();
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
);
}
}
}
}
@override
void
debugFillProperties
(
DiagnosticPropertiesBuilder
properties
)
{
...
...
@@ -370,65 +449,95 @@ class Switch extends StatefulWidget {
}
}
class
_SwitchState
extends
State
<
Switch
>
with
TickerProviderStateMixin
{
late
Map
<
Type
,
Action
<
Intent
>>
_actionMap
;
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
;
@override
void
initState
()
{
super
.
initState
();
_actionMap
=
<
Type
,
Action
<
Intent
>>{
ActivateIntent:
CallbackAction
<
ActivateIntent
>(
onInvoke:
_actionHandler
),
};
}
State
<
StatefulWidget
>
createState
()
=>
_MaterialSwitchState
();
}
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
;
}
bool
_hovering
=
false
;
void
_handleHoverChanged
(
bool
hovering
)
{
if
(
hovering
!=
_hovering
)
{
setState
(()
{
_hovering
=
hovering
;
});
animateToValue
();
}
}
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
void
dispose
()
{
_painter
.
dispose
();
super
.
dispose
();
}
bool
get
enabled
=>
widget
.
onChanged
!=
null
;
@override
ValueChanged
<
bool
?>?
get
onChanged
=>
widget
.
onChanged
!=
null
?
_handleChanged
:
null
;
void
_didFinishDragging
()
{
// The user has finished dragging the thumb of this switch. Rebuild the switch
// to update the animation.
setState
(()
{});
}
@override
bool
get
tristate
=>
false
;
Set
<
MaterialState
>
get
_states
=>
<
MaterialState
>{
if
(!
enabled
)
MaterialState
.
disabled
,
if
(
_hovering
)
MaterialState
.
hovered
,
if
(
_focused
)
MaterialState
.
focused
,
if
(
widget
.
value
)
MaterialState
.
selected
,
};
@override
bool
?
get
value
=>
widget
.
value
;
MaterialStateProperty
<
Color
?>
get
_widgetThumbColor
{
return
MaterialStateProperty
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
...
...
@@ -487,14 +596,68 @@ class _SwitchState extends State<Switch> with TickerProviderStateMixin {
});
}
Widget
buildMaterialSwitch
(
BuildContext
context
)
{
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
)
{
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
)
...
...
@@ -512,14 +675,13 @@ class _SwitchState extends State<Switch> with TickerProviderStateMixin {
??
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
)
...
...
@@ -535,277 +697,65 @@ class _SwitchState extends State<Switch> with TickerProviderStateMixin {
??
theme
.
switchTheme
.
overlayColor
?.
resolve
(
inactivePressedStates
)
??
effectiveActiveThumbColor
.
withAlpha
(
kRadialReactionAlpha
);
final
MouseCursor
effectiveMouseCursor
=
MaterialStateProperty
.
resolveAs
<
MouseCursor
?>(
widget
.
mouseCursor
,
_states
)
??
theme
.
switchTheme
.
mouseCursor
?.
resolve
(
_states
)
??
MaterialStateProperty
.
resolveAs
<
MouseCursor
>(
MaterialStateMouseCursor
.
clickable
,
_states
);
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
);
});
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
(
return
Semantics
(
toggled:
widget
.
value
,
child:
GestureDetector
(
excludeFromSemantics:
true
,
onHorizontalDragStart:
_handleDragStart
,
onHorizontalDragUpdate:
_handleDragUpdate
,
onHorizontalDragEnd:
_handleDragEnd
,
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
,
);
},
),
);
}
Widget
buildCupertinoSwitch
(
BuildContext
context
)
{
final
Size
size
=
getSwitchSize
(
Theme
.
of
(
context
));
return
Focus
(
child:
buildToggleable
(
mouseCursor:
effectiveMouseCursor
,
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
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
),
),
);
}
@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
_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
;
}
class
_SwitchPainter
extends
ToggleablePainter
{
ImageProvider
?
get
activeThumbImage
=>
_activeThumbImage
;
ImageProvider
?
_activeThumbImage
;
set
activeThumbImage
(
ImageProvider
?
value
)
{
if
(
value
==
_activeThumbImage
)
return
;
_activeThumbImage
=
value
;
markNeedsPaint
();
notifyListeners
();
}
ImageErrorListener
?
get
onActiveThumbImageError
=>
_onActiveThumbImageError
;
...
...
@@ -815,7 +765,7 @@ class _RenderSwitch extends RenderToggleable {
return
;
}
_onActiveThumbImageError
=
value
;
markNeedsPaint
();
notifyListeners
();
}
ImageProvider
?
get
inactiveThumbImage
=>
_inactiveThumbImage
;
...
...
@@ -824,7 +774,7 @@ class _RenderSwitch extends RenderToggleable {
if
(
value
==
_inactiveThumbImage
)
return
;
_inactiveThumbImage
=
value
;
markNeedsPaint
();
notifyListeners
();
}
ImageErrorListener
?
get
onInactiveThumbImageError
=>
_onInactiveThumbImageError
;
...
...
@@ -834,132 +784,77 @@ class _RenderSwitch extends RenderToggleable {
return
;
}
_onInactiveThumbImageError
=
value
;
markNeedsPaint
();
notifyListeners
();
}
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
;
markNeedsPaint
();
notifyListeners
();
}
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
;
markNeedsPaint
();
notifyListeners
();
}
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
;
markNeedsPaint
();
notifyListeners
();
}
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
;
markNeedsPaint
();
}
DragStartBehavior
get
dragStartBehavior
=>
_drag
.
dragStartBehavior
;
set
dragStartBehavior
(
DragStartBehavior
value
)
{
assert
(
value
!=
null
);
if
(
_drag
.
dragStartBehavior
==
value
)
return
;
_drag
.
dragStartBehavior
=
value
;
notifyListeners
();
}
Color
get
surfaceColor
=>
_surfaceColor
;
Color
_surfaceColor
;
Color
get
surfaceColor
=>
_surfaceColor
!
;
Color
?
_surfaceColor
;
set
surfaceColor
(
Color
value
)
{
assert
(
value
!=
null
);
if
(
value
==
_surfaceColor
)
return
;
_surfaceColor
=
value
;
markNeedsPaint
();
notifyListeners
();
}
_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
();
}
}
@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
;
}
bool
get
isInteractive
=>
_isInteractive
!;
bool
?
_isInteractive
;
set
isInteractive
(
bool
value
)
{
if
(
value
==
_isInteractive
)
{
return
;
}
_isInteractive
=
value
;
notifyListeners
();
}
void
_handleDragEnd
(
DragEndDetails
details
)
{
_needsPositionAnimation
=
true
;
if
(
position
.
value
>=
0.5
!=
value
)
onChanged
!(!
value
!);
reactionController
.
reverse
();
state
.
_didFinishDragging
();
double
get
trackInnerLength
=>
_trackInnerLength
!;
double
?
_trackInnerLength
;
set
trackInnerLength
(
double
value
)
{
if
(
value
==
_trackInnerLength
)
{
return
;
}
@override
void
handleEvent
(
PointerEvent
event
,
BoxHitTestEntry
entry
)
{
assert
(
debugHandleEvent
(
event
,
entry
));
if
(
event
is
PointerDownEvent
&&
onChanged
!=
null
)
_drag
.
addPointer
(
event
);
super
.
handleEvent
(
event
,
entry
);
_trackInnerLength
=
value
;
notifyListeners
();
}
Color
?
_cachedThumbColor
;
...
...
@@ -984,19 +879,12 @@ class _RenderSwitch extends RenderToggleable {
// are already in the middle of painting. (In fact, doing so would trigger
// an assert).
if
(!
_isPainting
)
markNeedsPaint
();
}
@override
void
describeSemanticsConfiguration
(
SemanticsConfiguration
config
)
{
super
.
describeSemanticsConfiguration
(
config
);
config
.
isToggled
=
value
==
true
;
notifyListeners
();
}
@override
void
paint
(
PaintingContext
context
,
Offset
offset
)
{
final
Canvas
canvas
=
context
.
canvas
;
final
bool
isEnabled
=
onChanged
!=
null
;
void
paint
(
Canvas
canvas
,
Size
size
)
{
final
bool
isEnabled
=
isInteractive
;
final
double
currentValue
=
position
.
value
;
final
double
visualPosition
;
...
...
@@ -1029,8 +917,8 @@ class _RenderSwitch extends RenderToggleable {
..
color
=
trackColor
;
const
double
trackHorizontalPadding
=
kRadialReactionRadius
-
_kTrackRadius
;
final
Rect
trackRect
=
Rect
.
fromLTWH
(
offset
.
dx
+
trackHorizontalPadding
,
offset
.
dy
+
(
size
.
height
-
_kTrackHeight
)
/
2.0
,
trackHorizontalPadding
,
(
size
.
height
-
_kTrackHeight
)
/
2.0
,
size
.
width
-
2.0
*
trackHorizontalPadding
,
_kTrackHeight
,
);
...
...
@@ -1038,11 +926,11 @@ class _RenderSwitch extends RenderToggleable {
canvas
.
drawRRect
(
trackRRect
,
paint
);
final
Offset
thumbPosition
=
Offset
(
kRadialReactionRadius
+
visualPosition
*
_
trackInnerLength
,
kRadialReactionRadius
+
visualPosition
*
trackInnerLength
,
size
.
height
/
2.0
,
);
paintRadialReaction
(
canvas
,
offset
,
thumbPosition
);
paintRadialReaction
(
canvas
:
canvas
,
origin:
thumbPosition
);
try
{
_isPainting
=
true
;
...
...
@@ -1059,7 +947,7 @@ class _RenderSwitch extends RenderToggleable {
final
double
radius
=
_kThumbRadius
-
inset
;
thumbPainter
.
paint
(
canvas
,
thumbPosition
+
offset
-
Offset
(
radius
,
radius
),
thumbPosition
-
Offset
(
radius
,
radius
),
configuration
.
copyWith
(
size:
Size
.
fromRadius
(
radius
)),
);
}
finally
{
...
...
packages/flutter/lib/src/material/toggleable.dart
View file @
14552a96
...
...
@@ -6,9 +6,10 @@ import 'package:flutter/animation.dart';
import
'package:flutter/foundation.dart'
;
import
'package:flutter/gestures.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/
scheduler
.dart'
;
import
'package:flutter/
widgets
.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
);
...
...
@@ -16,92 +17,21 @@ 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 base class for material style toggleable controls with toggle animations.
/// A mixin for [StatefulWidget]s that implement material-themed toggleable
/// controls with toggle animations (e.g. [Switch]es, [Checkbox]es, and
/// [Radio]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
);
}
/// 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
>
{
/// Used by subclasses to manipulate the visual value of the control.
///
/// Some controls respond to user input by updating their visual value. For
...
...
@@ -109,16 +39,15 @@ abstract class RenderToggleable extends RenderConstrainedBox {
/// 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
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
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
/// 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
;
...
...
@@ -129,84 +58,66 @@ abstract class RenderToggleable extends RenderConstrainedBox {
/// Some controls have a radial ink reaction to user input. This animation
/// controller can be used to start or stop these ink reactions.
///
/// Subclasses should call [paintRadialReaction] to actually paint the radial
/// reaction.
@protected
/// To paint the actual radial reaction, [ToggleablePainter.paintRadialReaction]
/// may be used.
AnimationController
get
reactionController
=>
_reactionController
;
late
AnimationController
_reactionController
;
late
Animation
<
double
>
_reaction
;
/// Used by subclasses to control the radial reaction's opacity animation for
/// [hasFocus] changes.
/// The visual value of the radial reaction animation.
///
/// 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.
/// Some controls have a radial ink reaction to user input. This animation
/// controls the progress of these ink reactions.
///
/// Subclasses should call [paintRadialReaction] to actually paint the radial
/// reaction.
@protected
AnimationController
get
reactionFocusFadeController
=>
_reactionFocusFadeController
;
late
AnimationController
_reactionFocusFadeController
;
late
Animation
<
double
>
_reactionFocusFade
;
/// To paint the actual radial reaction, [ToggleablePainter.paintRadialReaction]
/// may be used.
Animation
<
double
>
get
reaction
=>
_reaction
;
late
Animation
<
double
>
_reaction
;
/// Used by subclasses to control the radial reaction's opacity animation for
/// [hovering] changes.
/// Controls the radial reaction's opacity animation for hover changes.
///
/// Some controls have a radial ink reaction to pointer hover. This animation
/// control
ler can be used to start or stop
these ink reaction fade-ins and
/// control
s
these ink reaction fade-ins and
/// fade-outs.
///
/// Subclasses should call [paintRadialReaction] to actually paint the radial
/// reaction.
@protected
AnimationController
get
reactionHoverFadeController
=>
_reactionHoverFadeController
;
late
AnimationController
_reactionHoverFadeController
;
/// To paint the actual radial reaction, [ToggleablePainter.paintRadialReaction]
/// may be used.
Animation
<
double
>
get
reactionHoverFade
=>
_reactionHoverFade
;
late
Animation
<
double
>
_reactionHoverFade
;
late
AnimationController
_reactionHoverFadeController
;
/// 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
();
}
/// 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
;
/// 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
();
}
/// 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
;
/// 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
);
}
late
final
Map
<
Type
,
Action
<
Intent
>>
_actionMap
=
<
Type
,
Action
<
Intent
>>{
ActivateIntent:
CallbackAction
<
ActivateIntent
>(
onInvoke:
_handleTap
),
};
/// 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
;
/// False if this control is "inactive" (not checked, off, or unselected).
///
...
...
@@ -217,17 +128,62 @@ abstract class RenderToggleable extends RenderConstrainedBox {
/// 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
=>
_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
;
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
()
{
if
(
tristate
)
{
if
(
value
==
null
)
_positionController
.
value
=
0.0
;
...
...
@@ -243,94 +199,235 @@ abstract class RenderToggleable extends RenderConstrainedBox {
}
}
/// If true, [value] can be true, false, or null, otherwise [value] must
/// be true or false.
@override
void
dispose
()
{
_positionController
.
dispose
();
_reactionController
.
dispose
();
_reactionHoverFadeController
.
dispose
();
_reactionFocusFadeController
.
dispose
();
super
.
dispose
();
}
/// The most recent [Offset] at which a pointer touched the Toggleable.
///
/// 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
)
/// 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
)
return
;
_tristate
=
value
;
markNeedsSemanticsUpdate
();
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
();
}
}
}
/// The color that should be used in the active state (i.e., when [value] is true).
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.
///
/// 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
;
_activeColor
=
value
;
markNeedsPaint
();
/// 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
,
),
),
),
);
}
}
/// The color that should be used in the inactive state (i.e., when [value] is false).
/// 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.
///
/// 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
)
/// Usually set to [ToggleableStateMixin.position].
Animation
<
double
>
get
position
=>
_position
!;
Animation
<
double
>?
_position
;
set
position
(
Animation
<
double
>
value
)
{
if
(
value
==
_position
)
{
return
;
_inactiveColor
=
value
;
markNeedsPaint
();
}
_position
?.
removeListener
(
notifyListeners
);
value
.
addListener
(
notifyListeners
);
_position
=
value
;
notifyListeners
();
}
/// The
color that should be used for the reaction when [hovering] is true
.
/// The
visual value of the radial reaction animation
.
///
/// Used when the toggleable needs to change the reaction color/transparency,
/// when it is being hovered over.
///
/// Defaults to the [activeColor] at alpha [kRadialReactionAlpha].
Color
get
hoverColor
=>
_hoverColor
;
Color
_hoverColor
;
set
hoverColor
(
Color
value
)
{
assert
(
value
!=
null
);
if
(
value
==
_hoverColor
)
/// Usually set to [ToggleableStateMixin.reaction].
Animation
<
double
>
get
reaction
=>
_reaction
!;
Animation
<
double
>?
_reaction
;
set
reaction
(
Animation
<
double
>
value
)
{
if
(
value
==
_reaction
)
{
return
;
_hoverColor
=
value
;
markNeedsPaint
();
}
_reaction
?.
removeListener
(
notifyListeners
);
value
.
addListener
(
notifyListeners
);
_reaction
=
value
;
notifyListeners
();
}
///
The color that should be used for the reaction when [hasFocus] is true
.
///
Controls the radial reaction's opacity animation for focus changes
.
///
/// Used when the toggleable needs to change the reaction color/transparency,
/// when it has focus.
/// 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.
///
/// Defaults to the [activeColor] at alpha [kRadialReactionAlpha].
Color
get
focusColor
=>
_focusColor
;
Color
_focusColor
;
set
focusColor
(
Color
value
)
{
assert
(
value
!=
null
);
if
(
value
==
_focusColor
)
/// Usually set to [ToggleableStateMixin.reactionHoverFade].
Animation
<
double
>
get
reactionHoverFade
=>
_reactionHoverFade
!;
Animation
<
double
>?
_reactionHoverFade
;
set
reactionHoverFade
(
Animation
<
double
>
value
)
{
if
(
value
==
_reactionHoverFade
)
{
return
;
_focusColor
=
value
;
markNeedsPaint
();
}
_reactionHoverFade
?.
removeListener
(
notifyListeners
);
value
.
addListener
(
notifyListeners
);
_reactionHoverFade
=
value
;
notifyListeners
();
}
/// The color that should be used
for the reaction when the toggleable is
///
active
.
/// The color that should be used
in the active state (i.e., when
///
[ToggleableStateMixin.value] is true)
.
///
/// Used when the toggleable needs to change the reaction color/transparency
/// that is displayed when the toggleable is active and tapped.
/// For example, a checkbox should use this color when checked.
Color
get
activeColor
=>
_activeColor
!;
Color
?
_activeColor
;
set
activeColor
(
Color
value
)
{
if
(
_activeColor
==
value
)
{
return
;
}
_activeColor
=
value
;
notifyListeners
();
}
/// The color that should be used in the inactive state (i.e., when
/// [ToggleableStateMixin.value] is false).
///
/// Defaults to the [activeColor] at alpha [kRadialReactionAlpha].
Color
?
get
reactionColor
=>
_reactionColor
;
Color
?
_reactionColor
;
set
reactionColor
(
Color
?
value
)
{
assert
(
value
!=
null
);
if
(
value
==
_reactionColor
)
/// For example, a checkbox should use this color when unchecked.
Color
get
inactiveColor
=>
_inactiveColor
!;
Color
?
_inactiveColor
;
set
inactiveColor
(
Color
value
)
{
if
(
_inactiveColor
==
value
)
{
return
;
_reactionColor
=
value
;
markNeedsPaint
();
}
_inactiveColor
=
value
;
notifyListeners
();
}
/// The color that should be used for the reaction when the toggleable is
...
...
@@ -338,184 +435,161 @@ abstract class RenderToggleable extends RenderConstrainedBox {
///
/// Used when the toggleable needs to change the reaction color/transparency
/// that is displayed when the toggleable is inactive and tapped.
///
/// Defaults to the [activeColor] at alpha [kRadialReactionAlpha].
Color
?
get
inactiveReactionColor
=>
_inactiveReactionColor
;
Color
get
inactiveReactionColor
=>
_inactiveReactionColor
!;
Color
?
_inactiveReactionColor
;
set
inactiveReactionColor
(
Color
?
value
)
{
assert
(
value
!=
null
);
if
(
value
==
_inactiveReactionColor
)
set
inactiveReactionColor
(
Color
value
)
{
if
(
value
==
_inactiveReactionColor
)
{
return
;
}
_inactiveReactionColor
=
value
;
markNeedsPaint
();
notifyListeners
();
}
/// The splash radius for the radial reaction.
double
get
splashRadius
=>
_splashRadius
;
double
_splashRadius
;
set
splashRadius
(
double
value
)
{
if
(
value
==
_splashRadius
)
/// 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
)
{
return
;
_splashRadius
=
value
;
markNeedsPaint
();
}
_reactionColor
=
value
;
notifyListeners
();
}
///
Called when the control changes val
ue.
///
The color that should be used for the reaction when [isHovered] is tr
ue.
///
/// 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
)
/// 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
)
{
return
;
final
bool
wasInteractive
=
isInteractive
;
_onChanged
=
value
;
if
(
wasInteractive
!=
isInteractive
)
{
markNeedsPaint
();
markNeedsSemanticsUpdate
();
}
_hoverColor
=
value
;
notifyListeners
();
}
///
Whether [value] of this control can be changed by user interaction
.
///
The color that should be used for the reaction when [isFocused] is true
.
///
/// 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
;
}
}
/// 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
;
}
@override
void
detach
()
{
_positionController
.
stop
();
_reactionController
.
stop
();
_reactionHoverFadeController
.
stop
();
_reactionFocusFadeController
.
stop
();
super
.
detach
();
_focusColor
=
value
;
notifyListeners
();
}
void
_handleTapDown
(
TapDownDetails
details
)
{
if
(
isInteractive
)
{
_downPosition
=
globalToLocal
(
details
.
globalPosition
);
_reactionController
.
forward
();
/// The splash radius for the radial reaction.
double
get
splashRadius
=>
_splashRadius
!;
double
?
_splashRadius
;
set
splashRadius
(
double
value
)
{
if
(
value
==
_splashRadius
)
{
return
;
}
_splashRadius
=
value
;
notifyListeners
();
}
void
_handleTap
()
{
if
(!
isInteractive
)
/// 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
;
switch
(
value
)
{
case
false
:
onChanged
!(
true
);
break
;
case
true
:
onChanged
!(
tristate
?
null
:
false
);
break
;
case
null
:
onChanged
!(
false
);
break
;
}
sendSemanticsEvent
(
const
TapSemanticEvent
());
_downPosition
=
value
;
notifyListeners
();
}
void
_handleTapUp
(
TapUpDetails
details
)
{
_downPosition
=
null
;
if
(
isInteractive
)
_reactionController
.
reverse
();
/// True if this toggleable has the input focus.
bool
get
isFocused
=>
_isFocused
!;
bool
?
_isFocused
;
set
isFocused
(
bool
?
value
)
{
if
(
value
==
_isFocused
)
{
return
;
}
void
_handleTapCancel
()
{
_downPosition
=
null
;
if
(
isInteractive
)
_reactionController
.
reverse
();
_isFocused
=
value
;
notifyListeners
();
}
@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
);
/// 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
();
}
/// 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
/// 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
)
{
/// [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
)
{
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
=
hasFocus
||
hovering
final
double
reactionRadius
=
isFocused
||
isHovered
?
splashRadius
:
radialReactionRadiusTween
.
evaluate
(
_
reaction
);
:
radialReactionRadiusTween
.
evaluate
(
reaction
);
if
(
reactionRadius
>
0.0
)
{
canvas
.
drawCircle
(
center
+
offset
,
reactionRadius
,
reactionPaint
);
}
}
}
@override
void
describeSemanticsConfiguration
(
SemanticsConfiguration
config
)
{
super
.
describeSemanticsConfiguration
(
config
);
config
.
isEnabled
=
isInteractive
;
if
(
isInteractive
)
config
.
onTap
=
_handleTap
;
@override
void
dispose
()
{
_position
?.
removeListener
(
notifyListeners
);
_reaction
?.
removeListener
(
notifyListeners
);
_reactionFocusFade
?.
removeListener
(
notifyListeners
);
_reactionHoverFade
?.
removeListener
(
notifyListeners
);
super
.
dispose
();
}
@override
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
));
}
bool
shouldRepaint
(
covariant
CustomPainter
oldDelegate
)
=>
true
;
@override
bool
?
hitTest
(
Offset
position
)
=>
null
;
@override
SemanticsBuilderCallback
?
get
semanticsBuilder
=>
null
;
@override
bool
shouldRebuildSemantics
(
covariant
CustomPainter
oldDelegate
)
=>
false
;
}
packages/flutter/test/material/checkbox_test.dart
View file @
14552a96
...
...
@@ -99,7 +99,7 @@ void main() {
),
));
expect
(
tester
.
getSemantics
(
find
.
by
WidgetPredicate
((
Widget
widget
)
=>
widget
.
runtimeType
.
toString
()
==
'_CheckboxRenderObjectWidget'
)),
matchesSemantics
(
expect
(
tester
.
getSemantics
(
find
.
by
Type
(
Checkbox
)),
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
WidgetPredicate
((
Widget
widget
)
=>
widget
.
runtimeType
.
toString
()
==
'_CheckboxRenderObjectWidget'
)),
matchesSemantics
(
expect
(
tester
.
getSemantics
(
find
.
by
Type
(
Checkbox
)),
matchesSemantics
(
hasCheckedState:
true
,
hasEnabledState:
true
,
));
...
...
@@ -120,7 +120,7 @@ void main() {
),
));
expect
(
tester
.
getSemantics
(
find
.
by
WidgetPredicate
((
Widget
widget
)
=>
widget
.
runtimeType
.
toString
()
==
'_CheckboxRenderObjectWidget'
)),
matchesSemantics
(
expect
(
tester
.
getSemantics
(
find
.
by
Type
(
Checkbox
)),
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
(
Focus
));
final
RenderObject
object
=
tester
.
firstRenderObject
(
find
.
byType
(
Checkbox
));
expect
(
checkboxValue
,
true
);
expect
(
semanticEvent
,
<
String
,
dynamic
>{
...
...
@@ -319,10 +319,8 @@ void main() {
);
}
RenderToggleable
getCheckboxRenderer
()
{
return
tester
.
renderObject
<
RenderToggleable
>(
find
.
byWidgetPredicate
((
Widget
widget
)
{
return
widget
.
runtimeType
.
toString
()
==
'_CheckboxRenderObjectWidget'
;
}));
RenderBox
getCheckboxRenderer
()
{
return
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
Checkbox
));
}
await
tester
.
pumpWidget
(
buildFrame
(
false
));
...
...
@@ -377,10 +375,8 @@ void main() {
);
}
RenderToggleable
getCheckboxRenderer
()
{
return
tester
.
renderObject
<
RenderToggleable
>(
find
.
byWidgetPredicate
((
Widget
widget
)
{
return
widget
.
runtimeType
.
toString
()
==
'_CheckboxRenderObjectWidget'
;
}));
RenderBox
getCheckboxRenderer
()
{
return
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
Checkbox
));
}
await
tester
.
pumpWidget
(
buildFrame
(
checkColor:
checkColor
));
...
...
@@ -454,10 +450,9 @@ void main() {
..
circle
(
color:
Colors
.
orange
[
500
])
..
drrect
(
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
))),
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
)),
),
);
// Check what happens when disabled.
...
...
@@ -470,10 +465,9 @@ void main() {
paints
..
drrect
(
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
))),
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
)),
),
);
});
...
...
@@ -825,10 +819,8 @@ void main() {
);
}
RenderToggleable
getCheckboxRenderer
()
{
return
tester
.
renderObject
<
RenderToggleable
>(
find
.
byWidgetPredicate
((
Widget
widget
)
{
return
widget
.
runtimeType
.
toString
()
==
'_CheckboxRenderObjectWidget'
;
}));
RenderBox
getCheckboxRenderer
()
{
return
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
Checkbox
));
}
await
tester
.
pumpWidget
(
buildFrame
(
enabled:
true
));
...
...
@@ -878,10 +870,8 @@ void main() {
);
}
RenderToggleable
getCheckboxRenderer
()
{
return
tester
.
renderObject
<
RenderToggleable
>(
find
.
byWidgetPredicate
((
Widget
widget
)
{
return
widget
.
runtimeType
.
toString
()
==
'_CheckboxRenderObjectWidget'
;
}));
RenderBox
getCheckboxRenderer
()
{
return
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
Checkbox
));
}
await
tester
.
pumpWidget
(
buildFrame
());
...
...
@@ -937,11 +927,9 @@ void main() {
paints
..
drrect
(
color:
const
Color
(
0xfff44336
),
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
)))
,
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
)),
),
);
});
...
...
@@ -1184,6 +1172,29 @@ 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 @
14552a96
...
...
@@ -308,7 +308,7 @@ void main() {
));
await
tester
.
tap
(
find
.
byKey
(
key
));
final
RenderObject
object
=
tester
.
firstRenderObject
(
find
.
by
Type
(
Focus
));
final
RenderObject
object
=
tester
.
firstRenderObject
(
find
.
by
Key
(
key
));
expect
(
radioValue
,
1
);
expect
(
semanticEvent
,
<
String
,
dynamic
>{
...
...
@@ -1078,4 +1078,29 @@ 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 @
14552a96
...
...
@@ -37,7 +37,7 @@ void main() {
expect
(
log
,
equals
(<
dynamic
>[
false
,
'-'
,
false
]));
});
testWidgets
(
'SwitchListTile
control
test'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'SwitchListTile
semantics
test'
,
(
WidgetTester
tester
)
async
{
final
SemanticsTester
semantics
=
SemanticsTester
(
tester
);
await
tester
.
pumpWidget
(
wrap
(
child:
Column
(
...
...
packages/flutter/test/material/switch_test.dart
View file @
14552a96
...
...
@@ -301,8 +301,7 @@ void main() {
paints
..
rrect
(
color:
const
Color
(
0x52000000
),
// Black with 32% opacity
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
)))
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
)))
..
circle
(
color:
const
Color
(
0x33000000
))
..
circle
(
color:
const
Color
(
0x24000000
))
..
circle
(
color:
const
Color
(
0x1f000000
))
...
...
@@ -317,8 +316,7 @@ void main() {
paints
..
rrect
(
color:
Colors
.
blue
[
600
]!.
withAlpha
(
0x80
),
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
)))
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
)))
..
circle
(
color:
const
Color
(
0x33000000
))
..
circle
(
color:
const
Color
(
0x24000000
))
..
circle
(
color:
const
Color
(
0x1f000000
))
...
...
@@ -351,8 +349,7 @@ void main() {
paints
..
rrect
(
color:
Colors
.
black12
,
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
)))
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
)))
..
circle
(
color:
const
Color
(
0x33000000
))
..
circle
(
color:
const
Color
(
0x24000000
))
..
circle
(
color:
const
Color
(
0x1f000000
))
...
...
@@ -383,8 +380,7 @@ void main() {
paints
..
rrect
(
color:
Colors
.
black12
,
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
)))
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
)))
..
circle
(
color:
const
Color
(
0x33000000
))
..
circle
(
color:
const
Color
(
0x24000000
))
..
circle
(
color:
const
Color
(
0x1f000000
))
...
...
@@ -427,8 +423,7 @@ void main() {
paints
..
rrect
(
color:
Colors
.
blue
[
500
],
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
)))
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
)))
..
circle
(
color:
const
Color
(
0x33000000
))
..
circle
(
color:
const
Color
(
0x24000000
))
..
circle
(
color:
const
Color
(
0x1f000000
))
...
...
@@ -442,8 +437,7 @@ void main() {
paints
..
rrect
(
color:
Colors
.
green
[
500
],
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
)))
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
)))
..
circle
(
color:
const
Color
(
0x33000000
))
..
circle
(
color:
const
Color
(
0x24000000
))
..
circle
(
color:
const
Color
(
0x1f000000
))
...
...
@@ -528,19 +522,19 @@ void main() {
await
gesture
.
up
();
await
tester
.
pump
();
expect
(
value
,
isFalse
);
final
RenderToggleable
renderObject
=
tester
.
renderObject
<
RenderToggleable
>(
final
ToggleableStateMixin
state
=
tester
.
state
<
ToggleableStateMixin
>(
find
.
descendant
(
of:
find
.
byType
(
Switch
),
matching:
find
.
byWidgetPredicate
(
(
Widget
widget
)
=>
widget
.
runtimeType
.
toString
()
==
'_
SwitchRenderObjectWidget
'
,
(
Widget
widget
)
=>
widget
.
runtimeType
.
toString
()
==
'_
MaterialSwitch
'
,
),
),
);
expect
(
renderObject
.
position
.
value
,
lessThan
(
0.5
));
expect
(
state
.
position
.
value
,
lessThan
(
0.5
));
await
tester
.
pump
();
await
tester
.
pumpAndSettle
();
expect
(
value
,
isFalse
);
expect
(
renderObject
.
position
.
value
,
0
);
expect
(
state
.
position
.
value
,
0
);
// Move past the middle.
gesture
=
await
tester
.
startGesture
(
tester
.
getRect
(
find
.
byType
(
Switch
)).
center
);
...
...
@@ -549,12 +543,12 @@ void main() {
await
gesture
.
up
();
await
tester
.
pump
();
expect
(
value
,
isTrue
);
expect
(
renderObject
.
position
.
value
,
greaterThan
(
0.5
));
expect
(
state
.
position
.
value
,
greaterThan
(
0.5
));
await
tester
.
pump
();
await
tester
.
pumpAndSettle
();
expect
(
value
,
isTrue
);
expect
(
renderObject
.
position
.
value
,
1.0
);
expect
(
state
.
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
);
...
...
@@ -563,12 +557,12 @@ void main() {
await
gesture
.
up
();
await
tester
.
pump
();
expect
(
value
,
isTrue
);
expect
(
renderObject
.
position
.
value
,
lessThan
(
0.5
));
expect
(
state
.
position
.
value
,
lessThan
(
0.5
));
await
tester
.
pump
();
await
tester
.
pumpAndSettle
();
expect
(
value
,
isTrue
);
expect
(
renderObject
.
position
.
value
,
1.0
);
expect
(
state
.
position
.
value
,
1.0
);
});
testWidgets
(
'switch has semantic events'
,
(
WidgetTester
tester
)
async
{
...
...
@@ -601,7 +595,7 @@ void main() {
),
);
await
tester
.
tap
(
find
.
byType
(
Switch
));
final
RenderObject
object
=
tester
.
firstRenderObject
(
find
.
byType
(
Focus
));
final
RenderObject
object
=
tester
.
firstRenderObject
(
find
.
byType
(
Switch
));
expect
(
value
,
true
);
expect
(
semanticEvent
,
<
String
,
dynamic
>{
...
...
@@ -750,8 +744,7 @@ void main() {
paints
..
rrect
(
color:
const
Color
(
0x801e88e5
),
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
)))
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
)))
..
circle
(
color:
Colors
.
orange
[
500
])
..
circle
(
color:
const
Color
(
0x33000000
))
..
circle
(
color:
const
Color
(
0x24000000
))
...
...
@@ -769,8 +762,7 @@ void main() {
paints
..
rrect
(
color:
const
Color
(
0x52000000
),
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
)))
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
)))
..
circle
(
color:
Colors
.
orange
[
500
])
..
circle
(
color:
const
Color
(
0x33000000
))
..
circle
(
color:
const
Color
(
0x24000000
))
...
...
@@ -788,8 +780,7 @@ void main() {
paints
..
rrect
(
color:
const
Color
(
0x1f000000
),
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
)))
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
)))
..
circle
(
color:
const
Color
(
0x33000000
))
..
circle
(
color:
const
Color
(
0x24000000
))
..
circle
(
color:
const
Color
(
0x1f000000
))
...
...
@@ -854,8 +845,7 @@ void main() {
paints
..
rrect
(
color:
const
Color
(
0x801e88e5
),
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
)))
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
)))
..
circle
(
color:
const
Color
(
0x33000000
))
..
circle
(
color:
const
Color
(
0x24000000
))
..
circle
(
color:
const
Color
(
0x1f000000
))
...
...
@@ -875,8 +865,7 @@ void main() {
paints
..
rrect
(
color:
const
Color
(
0x801e88e5
),
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
)))
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
)))
..
circle
(
color:
Colors
.
orange
[
500
])
..
circle
(
color:
const
Color
(
0x33000000
))
..
circle
(
color:
const
Color
(
0x24000000
))
...
...
@@ -892,8 +881,7 @@ void main() {
paints
..
rrect
(
color:
const
Color
(
0x1f000000
),
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
)))
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
)))
..
circle
(
color:
const
Color
(
0x33000000
))
..
circle
(
color:
const
Color
(
0x24000000
))
..
circle
(
color:
const
Color
(
0x1f000000
))
...
...
@@ -1070,8 +1058,7 @@ void main() {
),
);
final
RenderToggleable
oldSwitchRenderObject
=
tester
.
renderObject
(
find
.
byWidgetPredicate
((
Widget
widget
)
=>
widget
is
LeafRenderObjectWidget
));
final
ToggleableStateMixin
oldSwitchState
=
tester
.
state
(
find
.
byWidgetPredicate
((
Widget
widget
)
=>
widget
.
runtimeType
.
toString
()
==
'_MaterialSwitch'
));
stateSetter
(()
{
value
=
false
;
});
await
tester
.
pump
();
...
...
@@ -1079,14 +1066,12 @@ void main() {
stateSetter
(()
{
enabled
=
false
;
});
await
tester
.
pump
();
final
RenderToggleable
updatedSwitchRenderObject
=
tester
.
renderObject
(
find
.
byWidgetPredicate
((
Widget
widget
)
=>
widget
is
LeafRenderObjectWidget
));
final
ToggleableStateMixin
updatedSwitchState
=
tester
.
state
(
find
.
byWidgetPredicate
((
Widget
widget
)
=>
widget
.
runtimeType
.
toString
()
==
'_MaterialSwitch'
));
expect
(
updatedSwitchRenderObject
.
isInteractive
,
false
);
expect
(
updatedSwitchRenderObject
,
oldSwitchRenderObject
);
expect
(
updatedSwitchRenderObject
.
position
.
isCompleted
,
false
);
expect
(
updatedSwitchRenderObject
.
position
.
isDismissed
,
false
);
expect
(
updatedSwitchState
.
isInteractive
,
false
);
expect
(
updatedSwitchState
,
oldSwitchState
);
expect
(
updatedSwitchState
.
position
.
isCompleted
,
false
);
expect
(
updatedSwitchState
.
position
.
isDismissed
,
false
);
});
testWidgets
(
'Switch thumb color resolves in active/enabled states'
,
(
WidgetTester
tester
)
async
{
...
...
@@ -1137,8 +1122,7 @@ void main() {
paints
..
rrect
(
color:
Colors
.
black12
,
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
)))
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
)))
..
circle
(
color:
const
Color
(
0x33000000
))
..
circle
(
color:
const
Color
(
0x24000000
))
..
circle
(
color:
const
Color
(
0x1f000000
))
...
...
@@ -1154,8 +1138,7 @@ void main() {
paints
..
rrect
(
color:
Colors
.
black12
,
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
)))
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
)))
..
circle
(
color:
const
Color
(
0x33000000
))
..
circle
(
color:
const
Color
(
0x24000000
))
..
circle
(
color:
const
Color
(
0x1f000000
))
...
...
@@ -1171,8 +1154,7 @@ void main() {
paints
..
rrect
(
color:
const
Color
(
0x52000000
),
// Black with 32% opacity,
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
)))
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
)))
..
circle
(
color:
const
Color
(
0x33000000
))
..
circle
(
color:
const
Color
(
0x24000000
))
..
circle
(
color:
const
Color
(
0x1f000000
))
...
...
@@ -1188,8 +1170,7 @@ void main() {
paints
..
rrect
(
color:
Colors
.
black12
,
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
)))
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
)))
..
circle
(
color:
const
Color
(
0x33000000
))
..
circle
(
color:
const
Color
(
0x24000000
))
..
circle
(
color:
const
Color
(
0x1f000000
))
...
...
@@ -1246,8 +1227,7 @@ void main() {
paints
..
rrect
(
color:
const
Color
(
0x801e88e5
),
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
)))
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
)))
..
circle
(
color:
const
Color
(
0x1f000000
))
..
circle
(
color:
const
Color
(
0x33000000
))
..
circle
(
color:
const
Color
(
0x24000000
))
...
...
@@ -1268,8 +1248,7 @@ void main() {
paints
..
rrect
(
color:
const
Color
(
0x801e88e5
),
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
)))
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
)))
..
circle
(
color:
const
Color
(
0x1f000000
))
..
circle
(
color:
const
Color
(
0x33000000
))
..
circle
(
color:
const
Color
(
0x24000000
))
...
...
@@ -1327,8 +1306,7 @@ void main() {
paints
..
rrect
(
color:
inactiveDisabledTrackColor
,
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
))),
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
))),
reason:
'Inactive disabled switch track should use this value'
,
);
...
...
@@ -1340,8 +1318,7 @@ void main() {
paints
..
rrect
(
color:
activeDisabledTrackColor
,
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
))),
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
))),
reason:
'Active disabled switch should match these colors'
,
);
...
...
@@ -1353,8 +1330,7 @@ void main() {
paints
..
rrect
(
color:
inactiveEnabledTrackColor
,
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
))),
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
))),
reason:
'Inactive enabled switch should match these colors'
,
);
...
...
@@ -1366,8 +1342,7 @@ void main() {
paints
..
rrect
(
color:
inactiveDisabledTrackColor
,
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
))),
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
))),
reason:
'Inactive disabled switch should match these colors'
,
);
});
...
...
@@ -1420,8 +1395,7 @@ void main() {
paints
..
rrect
(
color:
focusedTrackColor
,
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
))),
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
))),
reason:
'Inactive enabled switch should match these colors'
,
);
...
...
@@ -1437,8 +1411,7 @@ void main() {
paints
..
rrect
(
color:
hoveredTrackColor
,
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
))),
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
))),
reason:
'Inactive enabled switch should match these colors'
,
);
});
...
...
@@ -1488,8 +1461,7 @@ void main() {
paints
..
rrect
(
color:
Colors
.
black12
,
rrect:
RRect
.
fromLTRBR
(
383.5
,
293.0
,
416.5
,
307.0
,
const
Radius
.
circular
(
7.0
)))
rrect:
RRect
.
fromLTRBR
(
13.0
,
17.0
,
46.0
,
31.0
,
const
Radius
.
circular
(
7.0
)))
..
circle
(
color:
const
Color
(
0x33000000
))
..
circle
(
color:
const
Color
(
0x24000000
))
..
circle
(
color:
const
Color
(
0x1f000000
))
...
...
@@ -1638,4 +1610,27 @@ 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