Commit 6a17d180 authored by Hans Muller's avatar Hans Muller

When the selected tab changes in a TabBar for which isScrollable:true, animate...

When the selected tab changes in a TabBar for which isScrollable:true, animate the selected tab to the center of the viewport, if that's possible.

Added an optional duration: to Scrollable.scrollTo(newOffset, duration: d) which animates the scroll to newOffset.

Refactored the Scrollable implementation a little to make the roles of the "toEnd" and "toOffset" animations clearer.

_handlePointerDown() now stops both animations.

Similarly, we only call settleScrollOffset() from the cancel gesture handlers if the animations aren't already running.
parent 54803998
......@@ -5,7 +5,10 @@
import 'dart:sky' as sky;
import 'package:newton/newton.dart';
import 'package:sky/animation/animation_performance.dart';
import 'package:sky/animation/animated_simulation.dart';
import 'package:sky/animation/animated_value.dart';
import 'package:sky/animation/curves.dart';
import 'package:sky/animation/scroll_behavior.dart';
import 'package:sky/theme/view_configuration.dart' as config;
import 'package:sky/widgets/basic.dart';
......@@ -33,8 +36,15 @@ abstract class Scrollable extends StatefulComponent {
ScrollDirection direction;
AnimatedSimulation _toEndAnimation; // See _startToEndAnimation()
AnimationPerformance _toOffsetAnimation; // Started by scrollTo(offset, duration: d)
void initState() {
_animation = new AnimatedSimulation(_tickScrollOffset);
_toEndAnimation = new AnimatedSimulation(_tickScrollOffset);
_toOffsetAnimation = new AnimationPerformance()
..addListener(() {
scrollTo(_toOffsetAnimation.variable.value);
});
}
void syncFields(Scrollable source) {
......@@ -52,8 +62,6 @@ abstract class Scrollable extends StatefulComponent {
return _scrollBehavior;
}
AnimatedSimulation _animation;
Widget buildContent();
Widget build() {
......@@ -87,12 +95,54 @@ abstract class Scrollable extends StatefulComponent {
});
}
bool scrollTo(double newScrollOffset) {
void _startToOffsetAnimation(double newScrollOffset, Duration duration) {
_stopToEndAnimation();
_stopToOffsetAnimation();
_toOffsetAnimation
..variable = new AnimatedValue<double>(scrollOffset,
end: newScrollOffset,
curve: ease
)
..progress = 0.0
..duration = duration
..play();
}
void _stopToOffsetAnimation() {
if (_toOffsetAnimation.isAnimating)
_toOffsetAnimation.stop();
}
void _startToEndAnimation({ double velocity: 0.0 }) {
_stopToEndAnimation();
_stopToOffsetAnimation();
Simulation simulation = scrollBehavior.release(scrollOffset, velocity);
if (simulation != null)
_toEndAnimation.start(simulation);
}
void _stopToEndAnimation() {
_toEndAnimation.stop();
}
void didUnmount() {
_stopToEndAnimation();
_stopToOffsetAnimation();
super.didUnmount();
}
bool scrollTo(double newScrollOffset, { Duration duration }) {
if (newScrollOffset == _scrollOffset)
return false;
if (duration == null) {
setState(() {
_scrollOffset = newScrollOffset;
});
} else {
_startToOffsetAnimation(newScrollOffset, duration);
}
if (_registeredScrollClients != null) {
var newList = null;
_registeredScrollClients.forEach((target) {
......@@ -114,24 +164,8 @@ abstract class Scrollable extends StatefulComponent {
return scrollTo(newScrollOffset);
}
void didUnmount() {
_stopSimulation();
super.didUnmount();
}
void settleScrollOffset() {
_startSimulation();
}
void _stopSimulation() {
_animation.stop();
}
void _startSimulation({ double velocity: 0.0 }) {
_stopSimulation();
Simulation simulation = scrollBehavior.release(scrollOffset, velocity);
if (simulation != null)
_animation.start(simulation);
_startToEndAnimation();
}
void _tickScrollOffset(double value) {
......@@ -139,12 +173,8 @@ abstract class Scrollable extends StatefulComponent {
}
void _handlePointerDown(_) {
_stopSimulation();
}
void _handlePointerUpOrCancel(_) {
if (!_animation.isAnimating)
settleScrollOffset();
_stopToEndAnimation();
_stopToOffsetAnimation();
}
void _handleScrollUpdate(sky.GestureEvent event) {
......@@ -155,15 +185,20 @@ abstract class Scrollable extends StatefulComponent {
double eventVelocity = direction == ScrollDirection.horizontal
? -event.velocityX
: -event.velocityY;
_startSimulation(velocity: _velocityForFlingGesture(eventVelocity));
_startToEndAnimation(velocity: _velocityForFlingGesture(eventVelocity));
}
void _handleFlingCancel(sky.GestureEvent event) {
void _maybeSettleScrollOffset() {
if (!_toEndAnimation.isAnimating && !_toOffsetAnimation.isAnimating)
settleScrollOffset();
}
void _handlePointerUpOrCancel(_) { _maybeSettleScrollOffset(); }
void _handleFlingCancel(sky.GestureEvent event) { _maybeSettleScrollOffset(); }
void _handleWheel(sky.WheelEvent event) {
scrollBy(-event.offsetY);
}
}
......@@ -34,6 +34,7 @@ const EdgeDims _kTabLabelPadding = const EdgeDims.symmetric(horizontal: 12.0);
const TextStyle _kTabTextStyle = const TextStyle(textAlign: TextAlign.center);
const int _kTabIconSize = 24;
const double _kTabBarScrollDrag = 0.025;
const Duration _kTabBarScroll = const Duration(milliseconds: 200);
class TabBarParentData extends BoxParentData with
ContainerParentDataMixin<RenderBox> { }
......@@ -381,6 +382,9 @@ class TabBar extends Scrollable {
SelectedIndexChanged onChanged;
bool isScrollable;
Size _tabBarSize;
List<double> _tabWidths;
void syncFields(TabBar source) {
super.syncFields(source);
labels = source.labels;
......@@ -396,9 +400,14 @@ class TabBar extends Scrollable {
_TabsScrollBehavior get scrollBehavior => super.scrollBehavior;
void _handleTap(int tabIndex) {
if (tabIndex != selectedIndex && onChanged != null)
if (tabIndex != selectedIndex) {
if (_tabWidths != null) {
scrollTo(_centeredTabScrollOffset(tabIndex), duration: _kTabBarScroll);
}
if (onChanged != null)
onChanged(tabIndex);
}
}
Widget _toTab(TabLabel label, int tabIndex) {
return new Listener(
......@@ -410,8 +419,16 @@ class TabBar extends Scrollable {
);
}
Size _tabBarSize;
List<double> _tabWidths;
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) {
setState(() {
......
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