Unverified Commit 236acb52 authored by Natalie Sampsell's avatar Natalie Sampsell Committed by GitHub

Segmented control animation (#18811)

Added animation to segmented control widget.
parent 03a1f4ac
...@@ -23,6 +23,10 @@ const double _kMinSegmentedControlHeight = 28.0; ...@@ -23,6 +23,10 @@ const double _kMinSegmentedControlHeight = 28.0;
// press or drag. // press or drag.
const Color _kPressedBackground = const Color(0x33007aff); const Color _kPressedBackground = const Color(0x33007aff);
// The duration of the fade animation used to transition when a new widget
// is selected.
const Duration _kFadeDuration = const Duration(milliseconds: 165);
/// An iOS-style segmented control. /// An iOS-style segmented control.
/// ///
/// Displays the widgets provided in the [Map] of [children] in a /// Displays the widgets provided in the [Map] of [children] in a
...@@ -147,20 +151,69 @@ class SegmentedControl<T> extends StatefulWidget { ...@@ -147,20 +151,69 @@ class SegmentedControl<T> extends StatefulWidget {
_SegmentedControlState<T> createState() => _SegmentedControlState<T>(); _SegmentedControlState<T> createState() => _SegmentedControlState<T>();
} }
class _SegmentedControlState<T> extends State<SegmentedControl<T>> { class _SegmentedControlState<T> extends State<SegmentedControl<T>>
with TickerProviderStateMixin<SegmentedControl<T>> {
T _pressedKey; T _pressedKey;
void _onTapDown(T currentKey) { final List<AnimationController> _selectionControllers = <AnimationController>[];
final List<ColorTween> _childTweens = <ColorTween>[];
static final ColorTween forwardBackgroundColorTween = new ColorTween(
begin: _kPressedBackground,
end: CupertinoColors.activeBlue,
);
static final ColorTween reverseBackgroundColorTween = new ColorTween(
begin: CupertinoColors.white,
end: CupertinoColors.activeBlue,
);
static final ColorTween textColorTween = new ColorTween(
begin: CupertinoColors.activeBlue,
end: CupertinoColors.white,
);
@override
void initState() {
super.initState();
for (T key in widget.children.keys) {
final AnimationController animationController = createAnimationController();
if (widget.groupValue == key) {
_childTweens.add(reverseBackgroundColorTween);
animationController.value = 1.0;
} else {
_childTweens.add(forwardBackgroundColorTween);
}
_selectionControllers.add(animationController);
}
}
AnimationController createAnimationController() {
return new AnimationController(
duration: _kFadeDuration,
vsync: this,
)..addListener(() {
setState(() { setState(() {
_pressedKey = currentKey; // State of background/text colors has changed
});
}); });
} }
void _onTapUp(TapUpDetails event) { @override
void dispose() {
for (AnimationController animationController in _selectionControllers) {
animationController.dispose();
}
super.dispose();
}
void _onTapDown(T currentKey) {
if (_pressedKey == null && currentKey != widget.groupValue) {
setState(() { setState(() {
_pressedKey = null; _pressedKey = currentKey;
}); });
} }
}
void _onTapCancel() { void _onTapCancel() {
setState(() { setState(() {
...@@ -169,14 +222,69 @@ class _SegmentedControlState<T> extends State<SegmentedControl<T>> { ...@@ -169,14 +222,69 @@ class _SegmentedControlState<T> extends State<SegmentedControl<T>> {
} }
void _onTap(T currentKey) { void _onTap(T currentKey) {
if (currentKey != widget.groupValue) { if (currentKey != widget.groupValue && currentKey == _pressedKey) {
widget.onValueChanged(currentKey); widget.onValueChanged(currentKey);
_pressedKey = null;
}
}
Color getTextColor(int index, T currentKey) {
if (_selectionControllers[index].isAnimating)
return textColorTween.evaluate(_selectionControllers[index]);
if (widget.groupValue == currentKey)
return CupertinoColors.white;
return CupertinoColors.activeBlue;
}
Color getBackgroundColor(int index, T currentKey) {
if (_selectionControllers[index].isAnimating)
return _childTweens[index].evaluate(_selectionControllers[index]);
if (widget.groupValue == currentKey)
return CupertinoColors.activeBlue;
if (_pressedKey == currentKey)
return _kPressedBackground;
return CupertinoColors.white;
}
void updateAnimationControllers() {
if (_selectionControllers.length > widget.children.length) {
_selectionControllers.length = widget.children.length;
_childTweens.length = widget.children.length;
} else {
for (int index = _selectionControllers.length; index < widget.children.length; index += 1) {
_selectionControllers.add(createAnimationController());
_childTweens.add(reverseBackgroundColorTween);
}
}
}
@override
void didUpdateWidget(SegmentedControl<T> oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.children.length != widget.children.length) {
updateAnimationControllers();
}
if (oldWidget.groupValue != widget.groupValue) {
int index = 0;
for (T key in widget.children.keys) {
if (widget.groupValue == key) {
_childTweens[index] = forwardBackgroundColorTween;
_selectionControllers[index].forward();
} else {
_childTweens[index] = reverseBackgroundColorTween;
_selectionControllers[index].reverse();
}
index += 1;
}
} }
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final List<Widget> gestureChildren = <Widget>[]; final List<Widget> _gestureChildren = <Widget>[];
final List<Color> _backgroundColors = <Color>[];
int index = 0; int index = 0;
int selectedIndex; int selectedIndex;
int pressedIndex; int pressedIndex;
...@@ -185,12 +293,10 @@ class _SegmentedControlState<T> extends State<SegmentedControl<T>> { ...@@ -185,12 +293,10 @@ class _SegmentedControlState<T> extends State<SegmentedControl<T>> {
pressedIndex = (_pressedKey == currentKey) ? index : pressedIndex; pressedIndex = (_pressedKey == currentKey) ? index : pressedIndex;
final TextStyle textStyle = DefaultTextStyle.of(context).style.copyWith( final TextStyle textStyle = DefaultTextStyle.of(context).style.copyWith(
color: (widget.groupValue == currentKey) ? color: getTextColor(index, currentKey),
CupertinoColors.white : CupertinoColors.activeBlue,
); );
final IconThemeData iconTheme = new IconThemeData( final IconThemeData iconTheme = new IconThemeData(
color: (widget.groupValue == currentKey) ? color: getTextColor(index, currentKey),
CupertinoColors.white : CupertinoColors.activeBlue,
); );
Widget child = widget.children[currentKey]; Widget child = widget.children[currentKey];
...@@ -198,7 +304,6 @@ class _SegmentedControlState<T> extends State<SegmentedControl<T>> { ...@@ -198,7 +304,6 @@ class _SegmentedControlState<T> extends State<SegmentedControl<T>> {
onTapDown: (TapDownDetails event) { onTapDown: (TapDownDetails event) {
_onTapDown(currentKey); _onTapDown(currentKey);
}, },
onTapUp: _onTapUp,
onTapCancel: _onTapCancel, onTapCancel: _onTapCancel,
onTap: () { onTap: () {
_onTap(currentKey); _onTap(currentKey);
...@@ -215,14 +320,17 @@ class _SegmentedControlState<T> extends State<SegmentedControl<T>> { ...@@ -215,14 +320,17 @@ class _SegmentedControlState<T> extends State<SegmentedControl<T>> {
), ),
), ),
); );
gestureChildren.add(child);
_backgroundColors.add(getBackgroundColor(index, currentKey));
_gestureChildren.add(child);
index += 1; index += 1;
} }
final Widget box = new _SegmentedControlRenderWidget<T>( final Widget box = new _SegmentedControlRenderWidget<T>(
children: gestureChildren, children: _gestureChildren,
selectedIndex: selectedIndex, selectedIndex: selectedIndex,
pressedIndex: pressedIndex, pressedIndex: pressedIndex,
backgroundColors: _backgroundColors,
); );
return new Padding( return new Padding(
...@@ -241,6 +349,7 @@ class _SegmentedControlRenderWidget<T> extends MultiChildRenderObjectWidget { ...@@ -241,6 +349,7 @@ class _SegmentedControlRenderWidget<T> extends MultiChildRenderObjectWidget {
List<Widget> children = const <Widget>[], List<Widget> children = const <Widget>[],
@required this.selectedIndex, @required this.selectedIndex,
@required this.pressedIndex, @required this.pressedIndex,
@required this.backgroundColors,
}) : super( }) : super(
key: key, key: key,
children: children, children: children,
...@@ -248,6 +357,7 @@ class _SegmentedControlRenderWidget<T> extends MultiChildRenderObjectWidget { ...@@ -248,6 +357,7 @@ class _SegmentedControlRenderWidget<T> extends MultiChildRenderObjectWidget {
final int selectedIndex; final int selectedIndex;
final int pressedIndex; final int pressedIndex;
final List<Color> backgroundColors;
@override @override
RenderObject createRenderObject(BuildContext context) { RenderObject createRenderObject(BuildContext context) {
...@@ -255,6 +365,7 @@ class _SegmentedControlRenderWidget<T> extends MultiChildRenderObjectWidget { ...@@ -255,6 +365,7 @@ class _SegmentedControlRenderWidget<T> extends MultiChildRenderObjectWidget {
textDirection: Directionality.of(context), textDirection: Directionality.of(context),
selectedIndex: selectedIndex, selectedIndex: selectedIndex,
pressedIndex: pressedIndex, pressedIndex: pressedIndex,
backgroundColors: backgroundColors,
); );
} }
...@@ -263,7 +374,8 @@ class _SegmentedControlRenderWidget<T> extends MultiChildRenderObjectWidget { ...@@ -263,7 +374,8 @@ class _SegmentedControlRenderWidget<T> extends MultiChildRenderObjectWidget {
renderObject renderObject
..textDirection = Directionality.of(context) ..textDirection = Directionality.of(context)
..selectedIndex = selectedIndex ..selectedIndex = selectedIndex
..pressedIndex = pressedIndex; ..pressedIndex = pressedIndex
..backgroundColors = backgroundColors;
} }
} }
...@@ -281,10 +393,12 @@ class _RenderSegmentedControl<T> extends RenderBox ...@@ -281,10 +393,12 @@ class _RenderSegmentedControl<T> extends RenderBox
@required int selectedIndex, @required int selectedIndex,
@required int pressedIndex, @required int pressedIndex,
@required TextDirection textDirection, @required TextDirection textDirection,
@required List<Color> backgroundColors,
}) : assert(textDirection != null), }) : assert(textDirection != null),
_textDirection = textDirection, _textDirection = textDirection,
_selectedIndex = selectedIndex, _selectedIndex = selectedIndex,
_pressedIndex = pressedIndex { _pressedIndex = pressedIndex,
_backgroundColors = backgroundColors {
addAll(children); addAll(children);
} }
...@@ -318,6 +432,16 @@ class _RenderSegmentedControl<T> extends RenderBox ...@@ -318,6 +432,16 @@ class _RenderSegmentedControl<T> extends RenderBox
markNeedsLayout(); markNeedsLayout();
} }
List<Color> get backgroundColors => _backgroundColors;
List<Color> _backgroundColors;
set backgroundColors(List<Color> value) {
if (_backgroundColors == value) {
return;
}
_backgroundColors = value;
markNeedsPaint();
}
final Paint _outlinePaint = new Paint() final Paint _outlinePaint = new Paint()
..color = CupertinoColors.activeBlue ..color = CupertinoColors.activeBlue
..strokeWidth = 1.0 ..strokeWidth = 1.0
...@@ -481,17 +605,10 @@ class _RenderSegmentedControl<T> extends RenderBox ...@@ -481,17 +605,10 @@ class _RenderSegmentedControl<T> extends RenderBox
final _SegmentedControlContainerBoxParentData childParentData = child.parentData; final _SegmentedControlContainerBoxParentData childParentData = child.parentData;
Color color = CupertinoColors.white;
if (selectedIndex != null && selectedIndex == childIndex) {
color = CupertinoColors.activeBlue;
} else if (pressedIndex != null && pressedIndex == childIndex) {
color = _kPressedBackground;
}
context.canvas.drawRRect( context.canvas.drawRRect(
childParentData.surroundingRect.shift(offset), childParentData.surroundingRect.shift(offset),
new Paint() new Paint()
..color = color ..color = backgroundColors[childIndex]
..style = PaintingStyle.fill, ..style = PaintingStyle.fill,
); );
context.canvas.drawRRect( context.canvas.drawRRect(
...@@ -505,6 +622,14 @@ class _RenderSegmentedControl<T> extends RenderBox ...@@ -505,6 +622,14 @@ class _RenderSegmentedControl<T> extends RenderBox
@override @override
bool hitTestChildren(HitTestResult result, {@required Offset position}) { bool hitTestChildren(HitTestResult result, {@required Offset position}) {
assert(position != null); assert(position != null);
return defaultHitTestChildren(result, position: position); RenderBox child = lastChild;
while (child != null) {
final _SegmentedControlContainerBoxParentData childParentData = child.parentData;
if (childParentData.surroundingRect.contains(position)) {
return child.hitTest(result, position: (Offset.zero & child.size).center);
}
child = childParentData.previousSibling;
}
return false;
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment