Commit 7af3a32e authored by Hans Muller's avatar Hans Muller

Animated selected tab indicator

parent 3b4093e1
...@@ -101,7 +101,7 @@ class TabbedNavigatorApp extends App { ...@@ -101,7 +101,7 @@ class TabbedNavigatorApp extends App {
return new Container( return new Container(
child: new Card(child: new Padding(child: tabNavigator, padding: const EdgeDims.all(8.0))), child: new Card(child: new Padding(child: tabNavigator, padding: const EdgeDims.all(8.0))),
padding: const EdgeDims.all(12.0), padding: const EdgeDims.all(12.0),
decoration: new BoxDecoration(backgroundColor: Theme.of(this).primarySwatch[50]) decoration: new BoxDecoration(backgroundColor: Theme.of(this).primarySwatch[100])
); );
} }
......
...@@ -77,3 +77,12 @@ class AnimatedColor extends AnimatedValue<Color> { ...@@ -77,3 +77,12 @@ class AnimatedColor extends AnimatedValue<Color> {
value = lerpColor(begin, end, t); value = lerpColor(begin, end, t);
} }
} }
class AnimatedRect extends AnimatedValue<Rect> {
AnimatedRect(Rect begin, { Rect end, Curve curve: linear })
: super(begin, end: end, curve: curve);
void setProgress(double t) {
value = lerpRect(begin, end, t);
}
}
...@@ -41,3 +41,19 @@ Offset lerpOffset(Offset a, Offset b, double t) { ...@@ -41,3 +41,19 @@ Offset lerpOffset(Offset a, Offset b, double t) {
return a * (1.0 - t); return a * (1.0 - t);
return new Offset(lerpNum(a.dx, b.dx, t), lerpNum(a.dy, b.dy, t)); return new Offset(lerpNum(a.dx, b.dx, t), lerpNum(a.dy, b.dy, t));
} }
Rect lerpRect(Rect a, Rect b, double t) {
if (a == null && b == null)
return null;
if (a == null)
return new Rect.fromLTRB(b.left * t, b.top * t, b.right * t, b.bottom * t);
if (b == null) {
double k = 1.0 - t;
return new Rect.fromLTRB(b.left * k, b.top * k, b.right * k, b.bottom * k);
}
return new Rect.fromLTRB(
lerpNum(a.left, b.left, t),
lerpNum(a.top, b.top, t),
lerpNum(a.right, b.right, t),
lerpNum(a.bottom, b.bottom, t));
}
...@@ -5,6 +5,9 @@ ...@@ -5,6 +5,9 @@
import 'dart:math' as math; import 'dart:math' as math;
import 'package:newton/newton.dart'; import 'package:newton/newton.dart';
import 'package:sky/animation/animation_performance.dart';
import 'package:sky/animation/animated_value.dart';
import 'package:sky/animation/curves.dart';
import 'package:sky/animation/scroll_behavior.dart'; import 'package:sky/animation/scroll_behavior.dart';
import 'package:sky/painting/text_style.dart'; import 'package:sky/painting/text_style.dart';
import 'package:sky/rendering/box.dart'; import 'package:sky/rendering/box.dart';
...@@ -72,6 +75,15 @@ class RenderTabBar extends RenderBox with ...@@ -72,6 +75,15 @@ class RenderTabBar extends RenderBox with
} }
} }
Rect _indicatorRect;
Rect get indicatorRect => _indicatorRect;
void set indicatorRect(Rect value) {
if (_indicatorRect != value) {
_indicatorRect = value;
markNeedsPaint();
}
}
bool _textAndIcons; bool _textAndIcons;
bool get textAndIcons => _textAndIcons; bool get textAndIcons => _textAndIcons;
void set textAndIcons(bool value) { void set textAndIcons(bool value) {
...@@ -176,7 +188,7 @@ class RenderTabBar extends RenderBox with ...@@ -176,7 +188,7 @@ class RenderTabBar extends RenderBox with
List<double> widths = new List<double>(childCount); List<double> widths = new List<double>(childCount);
if (!isScrollable && childCount > 0) { if (!isScrollable && childCount > 0) {
double tabWidth = size.width / childCount; double tabWidth = size.width / childCount;
widths.fillRange(0, widths.length - 1, tabWidth); widths.fillRange(0, widths.length, tabWidth);
} else if (isScrollable) { } else if (isScrollable) {
RenderBox child = firstChild; RenderBox child = firstChild;
int childIndex = 0; int childIndex = 0;
...@@ -219,6 +231,11 @@ class RenderTabBar extends RenderBox with ...@@ -219,6 +231,11 @@ class RenderTabBar extends RenderBox with
if (indicatorColor == null) if (indicatorColor == null)
return; return;
if (indicatorRect != null) {
canvas.drawRect(indicatorRect, new Paint()..color = indicatorColor);
return;
}
var size = new Size(selectedTab.size.width, _kTabIndicatorHeight); var size = new Size(selectedTab.size.width, _kTabIndicatorHeight);
var point = new Point( var point = new Point(
selectedTab.parentData.position.x, selectedTab.parentData.position.x,
...@@ -236,7 +253,6 @@ class RenderTabBar extends RenderBox with ...@@ -236,7 +253,6 @@ class RenderTabBar extends RenderBox with
Rect rect = offset & new Size(width, size.height); Rect rect = offset & new Size(width, size.height);
canvas.drawRect(rect, new Paint()..color = backgroundColor); canvas.drawRect(rect, new Paint()..color = backgroundColor);
} }
int index = 0; int index = 0;
RenderBox child = firstChild; RenderBox child = firstChild;
while (child != null) { while (child != null) {
...@@ -256,6 +272,7 @@ class TabBarWrapper extends MultiChildRenderObjectWrapper { ...@@ -256,6 +272,7 @@ class TabBarWrapper extends MultiChildRenderObjectWrapper {
this.selectedIndex, this.selectedIndex,
this.backgroundColor, this.backgroundColor,
this.indicatorColor, this.indicatorColor,
this.indicatorRect,
this.textAndIcons, this.textAndIcons,
this.isScrollable: false, this.isScrollable: false,
this.onLayoutChanged this.onLayoutChanged
...@@ -264,6 +281,7 @@ class TabBarWrapper extends MultiChildRenderObjectWrapper { ...@@ -264,6 +281,7 @@ class TabBarWrapper extends MultiChildRenderObjectWrapper {
final int selectedIndex; final int selectedIndex;
final Color backgroundColor; final Color backgroundColor;
final Color indicatorColor; final Color indicatorColor;
final Rect indicatorRect;
final bool textAndIcons; final bool textAndIcons;
final bool isScrollable; final bool isScrollable;
final LayoutChanged onLayoutChanged; final LayoutChanged onLayoutChanged;
...@@ -276,6 +294,7 @@ class TabBarWrapper extends MultiChildRenderObjectWrapper { ...@@ -276,6 +294,7 @@ class TabBarWrapper extends MultiChildRenderObjectWrapper {
root.selectedIndex = selectedIndex; root.selectedIndex = selectedIndex;
root.backgroundColor = backgroundColor; root.backgroundColor = backgroundColor;
root.indicatorColor = indicatorColor; root.indicatorColor = indicatorColor;
root.indicatorRect = indicatorRect;
root.textAndIcons = textAndIcons; root.textAndIcons = textAndIcons;
root.isScrollable = isScrollable; root.isScrollable = isScrollable;
root.onLayoutChanged = onLayoutChanged; root.onLayoutChanged = onLayoutChanged;
...@@ -384,6 +403,14 @@ class TabBar extends Scrollable { ...@@ -384,6 +403,14 @@ class TabBar extends Scrollable {
Size _tabBarSize; Size _tabBarSize;
List<double> _tabWidths; List<double> _tabWidths;
AnimationPerformance _indicatorAnimation;
void initState() {
super.initState();
_indicatorAnimation = new AnimationPerformance()
..duration = _kTabBarScroll
..variable = new AnimatedRect(null, curve: ease);
}
void syncFields(TabBar source) { void syncFields(TabBar source) {
super.syncFields(source); super.syncFields(source);
...@@ -396,13 +423,65 @@ class TabBar extends Scrollable { ...@@ -396,13 +423,65 @@ class TabBar extends Scrollable {
scrollBehavior.isScrollable = source.isScrollable; scrollBehavior.isScrollable = source.isScrollable;
} }
void didMount() {
_indicatorAnimation.addListener(_indicatorAnimationUpdated);
super.didMount();
}
void didUnmount() {
_indicatorAnimation.removeListener(_indicatorAnimationUpdated);
super.didUnmount();
}
void _indicatorAnimationUpdated() {
setState(() {
});
}
AnimatedRect get _indicatorRect => _indicatorAnimation.variable as AnimatedRect;
void _startIndicatorAnimation(int fromTabIndex, int toTabIndex) {
_indicatorRect
..begin = _tabIndicatorRect(fromTabIndex)
..end = _tabIndicatorRect(toTabIndex);
_indicatorAnimation
..progress = 0.0
..play();
}
ScrollBehavior createScrollBehavior() => new _TabsScrollBehavior(); ScrollBehavior createScrollBehavior() => new _TabsScrollBehavior();
_TabsScrollBehavior get scrollBehavior => super.scrollBehavior; _TabsScrollBehavior get scrollBehavior => super.scrollBehavior;
Rect _tabRect(int tabIndex) {
assert(_tabBarSize != null);
assert(_tabWidths != null);
assert(tabIndex >= 0 && tabIndex < _tabWidths.length);
double tabLeft = 0.0;
if (tabIndex > 0)
tabLeft = _tabWidths.take(tabIndex).reduce((sum, width) => sum + width);
double tabTop = 0.0;
double tabBottom = _tabBarSize.height -_kTabIndicatorHeight;
double tabRight = tabLeft + _tabWidths[tabIndex];
return new Rect.fromLTRB(tabLeft, tabTop, tabRight, tabBottom);
}
Rect _tabIndicatorRect(int tabIndex) {
Rect r = _tabRect(tabIndex);
return new Rect.fromLTRB(r.left, r.bottom, r.right, r.bottom + _kTabIndicatorHeight);
}
double _centeredTabScrollOffset(int tabIndex) {
double viewportWidth = scrollBehavior.containerSize;
return (_tabRect(tabIndex).left + _tabWidths[tabIndex] / 2.0 - viewportWidth / 2.0)
.clamp(scrollBehavior.minScrollOffset, scrollBehavior.maxScrollOffset);
}
void _handleTap(int tabIndex) { void _handleTap(int tabIndex) {
if (tabIndex != selectedIndex) { if (tabIndex != selectedIndex) {
if (_tabWidths != null) { if (_tabWidths != null) {
if (isScrollable)
scrollTo(_centeredTabScrollOffset(tabIndex), duration: _kTabBarScroll); scrollTo(_centeredTabScrollOffset(tabIndex), duration: _kTabBarScroll);
_startIndicatorAnimation(selectedIndex, tabIndex);
} }
if (onChanged != null) if (onChanged != null)
onChanged(tabIndex); onChanged(tabIndex);
...@@ -419,17 +498,6 @@ class TabBar extends Scrollable { ...@@ -419,17 +498,6 @@ class TabBar extends Scrollable {
); );
} }
double _centeredTabScrollOffset(int tabIndex) {
assert(_tabWidths != null);
assert(tabIndex >= 0 && tabIndex < _tabWidths.length);
double viewportWidth = scrollBehavior.containerSize;
double tabOffset = 0.0;
if (tabIndex > 0)
tabOffset = _tabWidths.take(tabIndex).reduce((sum, width) => sum + width);
return (tabOffset + _tabWidths[tabIndex] / 2.0 - viewportWidth / 2.0)
.clamp(scrollBehavior.minScrollOffset, scrollBehavior.maxScrollOffset);
}
void _layoutChanged(Size tabBarSize, List<double> tabWidths) { void _layoutChanged(Size tabBarSize, List<double> tabWidths) {
setState(() { setState(() {
_tabBarSize = tabBarSize; _tabBarSize = tabBarSize;
...@@ -484,9 +552,10 @@ class TabBar extends Scrollable { ...@@ -484,9 +552,10 @@ class TabBar extends Scrollable {
selectedIndex: selectedIndex, selectedIndex: selectedIndex,
backgroundColor: backgroundColor, backgroundColor: backgroundColor,
indicatorColor: indicatorColor, indicatorColor: indicatorColor,
indicatorRect: _indicatorRect.value,
textAndIcons: textAndIcons, textAndIcons: textAndIcons,
isScrollable: isScrollable, isScrollable: isScrollable,
onLayoutChanged: isScrollable ? _layoutChanged : null onLayoutChanged: _layoutChanged
) )
) )
) )
......
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