提交 eb8e8142 编写于 作者: H Hans Muller

Merge pull request #572 from HansMuller/ensure-visible

Adds ensureWidgetIsVisible() function to scrollable.dart
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:sky/animation/animated_value.dart';
import 'package:sky/animation/animation_performance.dart';
import 'package:sky/animation/curves.dart';
import 'package:sky/base/lerp.dart';
import 'package:sky/painting/box_painter.dart';
import 'package:sky/painting/text_style.dart';
import 'package:sky/rendering/box.dart';
import 'package:sky/theme/colors.dart';
import 'package:sky/widgets/basic.dart';
import 'package:sky/widgets/block_viewport.dart';
import 'package:sky/widgets/card.dart';
import 'package:sky/widgets/icon.dart';
import 'package:sky/widgets/scrollable.dart';
import 'package:sky/widgets/scaffold.dart';
import 'package:sky/widgets/theme.dart';
import 'package:sky/widgets/tool_bar.dart';
import 'package:sky/widgets/framework.dart';
import 'package:sky/widgets/task_description.dart';
class CardModel {
CardModel(this.value, this.height, this.color);
int value;
double height;
Color color;
String get label => "Card $value";
Key get key => new Key.fromObjectIdentity(this);
}
class EnsureVisibleApp extends App {
static const TextStyle cardLabelStyle =
const TextStyle(color: white, fontSize: 18.0, fontWeight: bold);
List<CardModel> cardModels;
BlockViewportLayoutState layoutState = new BlockViewportLayoutState();
ScrollListener scrollListener;
AnimationPerformance scrollAnimation;
void initState() {
List<double> cardHeights = <double>[
48.0, 63.0, 82.0, 146.0, 60.0, 55.0, 84.0, 96.0, 50.0,
48.0, 63.0, 82.0, 146.0, 60.0, 55.0, 84.0, 96.0, 50.0,
48.0, 63.0, 82.0, 146.0, 60.0, 55.0, 84.0, 96.0, 50.0
];
cardModels = new List.generate(cardHeights.length, (i) {
Color color = lerpColor(Red[300], Blue[900], i / cardHeights.length);
return new CardModel(i, cardHeights[i], color);
});
scrollAnimation = new AnimationPerformance()
..duration = const Duration(milliseconds: 200)
..variable = new AnimatedValue<double>(0.0, curve: ease);
super.initState();
}
EventDisposition handleTap(Widget target) {
ensureWidgetIsVisible(target, animation: scrollAnimation);
return EventDisposition.processed;
}
Widget builder(int index) {
if (index >= cardModels.length)
return null;
CardModel cardModel = cardModels[index];
Widget card = new Card(
color: cardModel.color,
child: new Container(
height: cardModel.height,
padding: const EdgeDims.all(8.0),
child: new Center(child: new Text(cardModel.label, style: cardLabelStyle))
)
);
return new Listener(
key: cardModel.key,
onGestureTap: (_) { return handleTap(card); },
child: card
);
}
Widget build() {
Widget cardCollection = new Container(
padding: const EdgeDims.symmetric(vertical: 12.0, horizontal: 8.0),
decoration: new BoxDecoration(backgroundColor: Theme.of(this).primarySwatch[50]),
child: new VariableHeightScrollable(
builder: builder,
token: cardModels.length,
layoutState: layoutState
)
);
return new IconTheme(
data: const IconThemeData(color: IconThemeColor.white),
child: new Theme(
data: new ThemeData(
brightness: ThemeBrightness.light,
primarySwatch: Blue,
accentColor: RedAccent[200]
),
child: new TaskDescription(
label: 'Cards',
child: new Scaffold(
toolbar: new ToolBar(center: new Text('Tap a Card')),
body: cardCollection
)
)
)
);
}
}
void main() {
runApp(new EnsureVisibleApp());
}
......@@ -9,8 +9,8 @@ import 'package:newton/newton.dart';
import 'package:sky/animation/animated_simulation.dart';
import 'package:sky/animation/animated_value.dart';
import 'package:sky/animation/animation_performance.dart';
import 'package:sky/animation/curves.dart';
import 'package:sky/animation/scroll_behavior.dart';
import 'package:sky/rendering/box.dart';
import 'package:sky/theme/view_configuration.dart' as config;
import 'package:sky/widgets/basic.dart';
import 'package:sky/widgets/block_viewport.dart';
......@@ -44,15 +44,10 @@ abstract class Scrollable extends StatefulComponent {
ScrollDirection scrollDirection;
AnimatedSimulation _toEndAnimation; // See _startToEndAnimation()
AnimationPerformance _toOffsetAnimation; // Started by scrollTo(offset, duration: d)
AnimationPerformance _toOffsetAnimation; // Started by scrollTo()
void initState() {
_toEndAnimation = new AnimatedSimulation(_tickScrollOffset);
_toOffsetAnimation = new AnimationPerformance()
..addListener(() {
AnimatedValue<double> offset = _toOffsetAnimation.variable;
scrollTo(offset.value);
});
}
void syncFields(Scrollable source) {
......@@ -91,22 +86,39 @@ abstract class Scrollable extends StatefulComponent {
);
}
void _startToOffsetAnimation(double newScrollOffset, Duration duration) {
_stopToEndAnimation();
void _startToOffsetAnimation(double newScrollOffset, AnimationPerformance animation) {
_stopToEndAnimation();
_stopToOffsetAnimation();
(animation.variable as AnimatedValue<double>)
..begin = scrollOffset
..end = newScrollOffset;
_toOffsetAnimation = animation
..progress = 0.0
..addListener(_updateToOffsetAnimation)
..addStatusListener(_updateToOffsetAnimationStatus)
..play();
}
void _updateToOffsetAnimation() {
AnimatedValue<double> offset = _toOffsetAnimation.variable;
scrollTo(offset.value);
}
void _updateToOffsetAnimationStatus(AnimationStatus status) {
if (status == AnimationStatus.dismissed || status == AnimationStatus.completed)
_stopToOffsetAnimation();
_toOffsetAnimation
..variable = new AnimatedValue<double>(scrollOffset,
end: newScrollOffset,
curve: ease
)
..progress = 0.0
..duration = duration
..play();
}
void _stopToOffsetAnimation() {
if (_toOffsetAnimation.isAnimating)
_toOffsetAnimation.stop();
if (_toOffsetAnimation != null) {
_toOffsetAnimation
..removeStatusListener(_updateToOffsetAnimationStatus)
..removeListener(_updateToOffsetAnimation)
..stop();
_toOffsetAnimation = null;
}
}
void _startToEndAnimation({ double velocity: 0.0 }) {
......@@ -127,16 +139,16 @@ abstract class Scrollable extends StatefulComponent {
super.didUnmount();
}
bool scrollTo(double newScrollOffset, { Duration duration }) {
bool scrollTo(double newScrollOffset, { AnimationPerformance animation }) {
if (newScrollOffset == _scrollOffset)
return false;
if (duration == null) {
if (animation == null) {
setState(() {
_scrollOffset = newScrollOffset;
});
} else {
_startToOffsetAnimation(newScrollOffset, duration);
_startToOffsetAnimation(newScrollOffset, animation);
}
if (_listeners.length > 0)
......@@ -178,7 +190,8 @@ abstract class Scrollable extends StatefulComponent {
}
void _maybeSettleScrollOffset() {
if (!_toEndAnimation.isAnimating && !_toOffsetAnimation.isAnimating)
if (!_toEndAnimation.isAnimating &&
(_toOffsetAnimation == null || !_toOffsetAnimation.isAnimating))
settleScrollOffset();
}
......@@ -220,6 +233,42 @@ Scrollable findScrollableAncestor({ Widget target }) {
return ancestor;
}
bool ensureWidgetIsVisible(Widget target, { AnimationPerformance animation }) {
assert(target.mounted);
assert(target.root is RenderBox);
Scrollable scrollable = findScrollableAncestor(target: target);
if (scrollable == null)
return false;
Size targetSize = (target.root as RenderBox).size;
Point targetCenter = target.localToGlobal(
scrollable.scrollDirection == ScrollDirection.vertical
? new Point(0.0, targetSize.height / 2.0)
: new Point(targetSize.width / 2.0, 0.0)
);
Size scrollableSize = (scrollable.root as RenderBox).size;
Point scrollableCenter = scrollable.localToGlobal(
scrollable.scrollDirection == ScrollDirection.vertical
? new Point(0.0, scrollableSize.height / 2.0)
: new Point(scrollableSize.width / 2.0, 0.0)
);
double scrollOffsetDelta = scrollable.scrollDirection == ScrollDirection.vertical
? targetCenter.y - scrollableCenter.y
: targetCenter.x - scrollableCenter.x;
BoundedBehavior scrollBehavior = scrollable.scrollBehavior;
double scrollOffset = (scrollable.scrollOffset + scrollOffsetDelta)
.clamp(scrollBehavior.minScrollOffset, scrollBehavior.maxScrollOffset);
if (scrollOffset != scrollable.scrollOffset) {
scrollable.scrollTo(scrollOffset, animation: animation);
return true;
}
return false;
}
/// A simple scrollable widget that has a single child. Use this component if
/// you are not worried about offscreen widgets consuming resources.
class ScrollableViewport extends Scrollable {
......
......@@ -407,12 +407,16 @@ class TabBar extends Scrollable {
Size _tabBarSize;
List<double> _tabWidths;
AnimationPerformance _indicatorAnimation;
AnimationPerformance _scrollAnimation;
void initState() {
super.initState();
_indicatorAnimation = new AnimationPerformance()
..duration = _kTabBarScroll
..variable = new AnimatedRect(null, curve: ease);
_scrollAnimation = new AnimationPerformance()
..duration = _kTabBarScroll
..variable = new AnimatedValue<double>(0.0, curve: ease);
}
void syncFields(TabBar source) {
......@@ -468,7 +472,7 @@ class TabBar extends Scrollable {
if (tabIndex != selectedIndex) {
if (_tabWidths != null) {
if (isScrollable)
scrollTo(_centeredTabScrollOffset(tabIndex), duration: _kTabBarScroll);
scrollTo(_centeredTabScrollOffset(tabIndex), animation: _scrollAnimation);
_startIndicatorAnimation(selectedIndex, tabIndex);
}
if (onChanged != null)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册