mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Port most of scrollable.dart to fn3
This commit is contained in:
parent
e7bc8f57d3
commit
0d63d6b775
@ -8,7 +8,7 @@ import 'package:sky/rendering.dart';
|
||||
import 'package:sky/src/fn3/framework.dart';
|
||||
import 'package:sky/src/fn3/basic.dart';
|
||||
|
||||
typedef List<Widget> ListBuilder(int startIndex, int count, BuildContext context);
|
||||
typedef List<Widget> ListBuilder(BuildContext context, int startIndex, int count);
|
||||
|
||||
class HomogeneousViewport extends RenderObjectWidget {
|
||||
HomogeneousViewport({
|
||||
@ -137,7 +137,7 @@ class HomogeneousViewportElement extends RenderObjectElement<HomogeneousViewport
|
||||
assert(_layoutItemCount != null);
|
||||
List<Widget> newWidgets;
|
||||
if (_layoutItemCount > 0)
|
||||
newWidgets = widget.builder(_layoutFirstIndex, _layoutItemCount, this);
|
||||
newWidgets = widget.builder(this, _layoutFirstIndex, _layoutItemCount);
|
||||
else
|
||||
newWidgets = <Widget>[];
|
||||
_children = updateChildren(_children, newWidgets);
|
||||
|
548
packages/flutter/lib/src/fn3/scrollable.dart
Normal file
548
packages/flutter/lib/src/fn3/scrollable.dart
Normal file
@ -0,0 +1,548 @@
|
||||
// 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 'dart:async';
|
||||
import 'dart:math' as math;
|
||||
import 'dart:sky' as sky;
|
||||
|
||||
import 'package:newton/newton.dart';
|
||||
import 'package:sky/animation.dart';
|
||||
import 'package:sky/gestures.dart';
|
||||
import 'package:sky/src/rendering/box.dart';
|
||||
import 'package:sky/src/rendering/viewport.dart';
|
||||
import 'package:sky/src/fn3/basic.dart';
|
||||
import 'package:sky/src/fn3/framework.dart';
|
||||
import 'package:sky/src/fn3/gesture_detector.dart';
|
||||
import 'package:sky/src/fn3/homogeneous_viewport.dart';
|
||||
|
||||
// The gesture velocity properties are pixels/second, config min,max limits are pixels/ms
|
||||
const double _kMillisecondsPerSecond = 1000.0;
|
||||
const double _kMinFlingVelocity = -kMaxFlingVelocity * _kMillisecondsPerSecond;
|
||||
const double _kMaxFlingVelocity = kMaxFlingVelocity * _kMillisecondsPerSecond;
|
||||
|
||||
typedef void ScrollListener();
|
||||
|
||||
/// A base class for scrollable widgets that reacts to user input and generates
|
||||
/// a scrollOffset.
|
||||
abstract class Scrollable extends StatefulComponent {
|
||||
Scrollable({
|
||||
Key key,
|
||||
this.initialScrollOffset,
|
||||
this.scrollDirection: ScrollDirection.vertical
|
||||
}) : super(key: key) {
|
||||
assert(scrollDirection == ScrollDirection.vertical ||
|
||||
scrollDirection == ScrollDirection.horizontal);
|
||||
}
|
||||
|
||||
final double initialScrollOffset;
|
||||
final ScrollDirection scrollDirection;
|
||||
}
|
||||
|
||||
abstract class ScrollableState<T extends Scrollable> extends ComponentState<T> {
|
||||
ScrollableState(T config) : super(config);
|
||||
|
||||
AnimatedSimulation _toEndAnimation; // See _startToEndAnimation()
|
||||
ValueAnimation<double> _toOffsetAnimation; // Started by scrollTo()
|
||||
|
||||
void initState(BuildContext context) {
|
||||
if (config.initialScrollOffset is double)
|
||||
_scrollOffset = config.initialScrollOffset;
|
||||
_toEndAnimation = new AnimatedSimulation(_setScrollOffset);
|
||||
_toOffsetAnimation = new ValueAnimation<double>()
|
||||
..addListener(() {
|
||||
AnimatedValue<double> offset = _toOffsetAnimation.variable;
|
||||
_setScrollOffset(offset.value);
|
||||
});
|
||||
}
|
||||
|
||||
double _scrollOffset = 0.0;
|
||||
double get scrollOffset => _scrollOffset;
|
||||
|
||||
Offset get scrollOffsetVector {
|
||||
if (config.scrollDirection == ScrollDirection.horizontal)
|
||||
return new Offset(scrollOffset, 0.0);
|
||||
return new Offset(0.0, scrollOffset);
|
||||
}
|
||||
|
||||
ScrollBehavior _scrollBehavior;
|
||||
ScrollBehavior createScrollBehavior();
|
||||
ScrollBehavior get scrollBehavior {
|
||||
if (_scrollBehavior == null)
|
||||
_scrollBehavior = createScrollBehavior();
|
||||
return _scrollBehavior;
|
||||
}
|
||||
|
||||
GestureDragUpdateCallback _getDragUpdateHandler(ScrollDirection direction) {
|
||||
if (config.scrollDirection != direction || !scrollBehavior.isScrollable)
|
||||
return null;
|
||||
return _handleDragUpdate;
|
||||
}
|
||||
|
||||
GestureDragEndCallback _getDragEndHandler(ScrollDirection direction) {
|
||||
if (config.scrollDirection != direction || !scrollBehavior.isScrollable)
|
||||
return null;
|
||||
return _handleDragEnd;
|
||||
}
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
return new GestureDetector(
|
||||
onVerticalDragUpdate: _getDragUpdateHandler(ScrollDirection.vertical),
|
||||
onVerticalDragEnd: _getDragEndHandler(ScrollDirection.vertical),
|
||||
onHorizontalDragUpdate: _getDragUpdateHandler(ScrollDirection.horizontal),
|
||||
onHorizontalDragEnd: _getDragEndHandler(ScrollDirection.horizontal),
|
||||
child: new Listener(
|
||||
child: buildContent(context),
|
||||
onPointerDown: _handlePointerDown
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildContent(BuildContext context);
|
||||
|
||||
Future _startToOffsetAnimation(double newScrollOffset, Duration duration, Curve curve) {
|
||||
_stopAnimations();
|
||||
_toOffsetAnimation
|
||||
..variable = new AnimatedValue<double>(scrollOffset,
|
||||
end: newScrollOffset,
|
||||
curve: curve
|
||||
)
|
||||
..progress = 0.0
|
||||
..duration = duration;
|
||||
return _toOffsetAnimation.play();
|
||||
}
|
||||
|
||||
void _stopAnimations() {
|
||||
if (_toOffsetAnimation.isAnimating)
|
||||
_toOffsetAnimation.stop();
|
||||
if (_toEndAnimation.isAnimating)
|
||||
_toEndAnimation.stop();
|
||||
}
|
||||
|
||||
void _startToEndAnimation({ double velocity: 0.0 }) {
|
||||
_stopAnimations();
|
||||
Simulation simulation = scrollBehavior.release(scrollOffset, velocity);
|
||||
if (simulation != null)
|
||||
_toEndAnimation.start(simulation);
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
_stopAnimations();
|
||||
}
|
||||
|
||||
void _setScrollOffset(double newScrollOffset) {
|
||||
if (_scrollOffset == newScrollOffset)
|
||||
return;
|
||||
setState(() {
|
||||
_scrollOffset = newScrollOffset;
|
||||
});
|
||||
if (_listeners.length > 0)
|
||||
_notifyListeners();
|
||||
}
|
||||
|
||||
Future scrollTo(double newScrollOffset, { Duration duration, Curve curve: ease }) {
|
||||
if (newScrollOffset == _scrollOffset)
|
||||
return new Future.value();
|
||||
|
||||
if (duration == null) {
|
||||
_stopAnimations();
|
||||
_setScrollOffset(newScrollOffset);
|
||||
return new Future.value();
|
||||
}
|
||||
|
||||
return _startToOffsetAnimation(newScrollOffset, duration, curve);
|
||||
}
|
||||
|
||||
Future scrollBy(double scrollDelta, { Duration duration, Curve curve }) {
|
||||
double newScrollOffset = scrollBehavior.applyCurve(_scrollOffset, scrollDelta);
|
||||
return scrollTo(newScrollOffset, duration: duration, curve: curve);
|
||||
}
|
||||
|
||||
void settleScrollOffset() {
|
||||
_startToEndAnimation();
|
||||
}
|
||||
|
||||
double _scrollVelocity(sky.Offset velocity) {
|
||||
double scrollVelocity = config.scrollDirection == ScrollDirection.horizontal
|
||||
? -velocity.dx
|
||||
: -velocity.dy;
|
||||
return scrollVelocity.clamp(_kMinFlingVelocity, _kMaxFlingVelocity) / _kMillisecondsPerSecond;
|
||||
}
|
||||
|
||||
void _handlePointerDown(_) {
|
||||
_stopAnimations();
|
||||
}
|
||||
|
||||
void _handleDragUpdate(double delta) {
|
||||
// We negate the delta here because a positive scroll offset moves the
|
||||
// the content up (or to the left) rather than down (or the right).
|
||||
scrollBy(-delta);
|
||||
}
|
||||
|
||||
void _handleDragEnd(Offset velocity) {
|
||||
if (velocity != Offset.zero) {
|
||||
_startToEndAnimation(velocity: _scrollVelocity(velocity));
|
||||
} else if (!_toEndAnimation.isAnimating && (_toOffsetAnimation == null || !_toOffsetAnimation.isAnimating)) {
|
||||
settleScrollOffset();
|
||||
}
|
||||
}
|
||||
|
||||
final List<ScrollListener> _listeners = new List<ScrollListener>();
|
||||
void addListener(ScrollListener listener) {
|
||||
_listeners.add(listener);
|
||||
}
|
||||
|
||||
void removeListener(ScrollListener listener) {
|
||||
_listeners.remove(listener);
|
||||
}
|
||||
|
||||
void _notifyListeners() {
|
||||
List<ScrollListener> localListeners = new List<ScrollListener>.from(_listeners);
|
||||
for (ScrollListener listener in localListeners)
|
||||
listener();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(abarth): findScrollableAncestor
|
||||
// TODO(abarth): ensureWidgetIsVisible
|
||||
|
||||
/// 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 {
|
||||
ScrollableViewport({
|
||||
Key key,
|
||||
this.child,
|
||||
double initialScrollOffset,
|
||||
ScrollDirection scrollDirection: ScrollDirection.vertical
|
||||
}) : super(
|
||||
key: key,
|
||||
scrollDirection: scrollDirection,
|
||||
initialScrollOffset: initialScrollOffset
|
||||
);
|
||||
|
||||
final Widget child;
|
||||
|
||||
ScrollableViewportState createState() => new ScrollableViewportState(this);
|
||||
}
|
||||
|
||||
class ScrollableViewportState extends ScrollableState<ScrollableViewport> {
|
||||
ScrollableViewportState(ScrollableViewport config) : super(config);
|
||||
|
||||
ScrollBehavior createScrollBehavior() => new OverscrollWhenScrollableBehavior();
|
||||
OverscrollWhenScrollableBehavior get scrollBehavior => super.scrollBehavior;
|
||||
|
||||
double _viewportSize = 0.0;
|
||||
double _childSize = 0.0;
|
||||
void _handleViewportSizeChanged(Size newSize) {
|
||||
_viewportSize = config.scrollDirection == ScrollDirection.vertical ? newSize.height : newSize.width;
|
||||
setState(() {
|
||||
_updateScrollBehaviour();
|
||||
});
|
||||
}
|
||||
void _handleChildSizeChanged(Size newSize) {
|
||||
_childSize = config.scrollDirection == ScrollDirection.vertical ? newSize.height : newSize.width;
|
||||
setState(() {
|
||||
_updateScrollBehaviour();
|
||||
});
|
||||
}
|
||||
void _updateScrollBehaviour() {
|
||||
// if you don't call this from build() or syncConstructorArguments(), you must call it from setState().
|
||||
scrollTo(scrollBehavior.updateExtents(
|
||||
contentExtent: _childSize,
|
||||
containerExtent: _viewportSize,
|
||||
scrollOffset: scrollOffset
|
||||
));
|
||||
}
|
||||
|
||||
Widget buildContent(BuildContext context) {
|
||||
return new SizeObserver(
|
||||
callback: _handleViewportSizeChanged,
|
||||
child: new Viewport(
|
||||
scrollOffset: scrollOffsetVector,
|
||||
scrollDirection: config.scrollDirection,
|
||||
child: new SizeObserver(
|
||||
callback: _handleChildSizeChanged,
|
||||
child: config.child
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A mashup of [ScrollableViewport] and [BlockBody]. Useful when you have a small,
|
||||
/// fixed number of children that you wish to arrange in a block layout and that
|
||||
/// might exceed the height of its container (and therefore need to scroll).
|
||||
class Block extends StatelessComponent {
|
||||
Block(this.children, {
|
||||
Key key,
|
||||
this.initialScrollOffset,
|
||||
this.scrollDirection: ScrollDirection.vertical
|
||||
}) : super(key: key);
|
||||
|
||||
final List<Widget> children;
|
||||
final double initialScrollOffset;
|
||||
final ScrollDirection scrollDirection;
|
||||
|
||||
BlockDirection get _direction {
|
||||
if (scrollDirection == ScrollDirection.vertical)
|
||||
return BlockDirection.vertical;
|
||||
return BlockDirection.horizontal;
|
||||
}
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
return new ScrollableViewport(
|
||||
initialScrollOffset: initialScrollOffset,
|
||||
scrollDirection: scrollDirection,
|
||||
child: new BlockBody(children, direction: _direction)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// An optimized scrollable widget for a large number of children that are all
|
||||
/// the same size (extent) in the scrollDirection. For example for
|
||||
/// ScrollDirection.vertical itemExtent is the height of each item. Use this
|
||||
/// widget when you have a large number of children or when you are concerned
|
||||
// about offscreen widgets consuming resources.
|
||||
abstract class ScrollableWidgetList extends Scrollable {
|
||||
ScrollableWidgetList({
|
||||
Key key,
|
||||
double initialScrollOffset,
|
||||
ScrollDirection scrollDirection: ScrollDirection.vertical,
|
||||
this.itemsWrap: false,
|
||||
this.itemExtent,
|
||||
this.padding
|
||||
}) : super(key: key, initialScrollOffset: initialScrollOffset, scrollDirection: scrollDirection) {
|
||||
assert(itemExtent != null);
|
||||
}
|
||||
|
||||
EdgeDims padding;
|
||||
bool itemsWrap;
|
||||
double itemExtent;
|
||||
Size containerSize = Size.zero;
|
||||
}
|
||||
|
||||
abstract class ScrollableWidgetListState<T extends ScrollableWidgetList> extends ScrollableState<T> {
|
||||
ScrollableWidgetListState(T config) : super(config);
|
||||
|
||||
/// Subclasses must implement `get itemCount` to tell ScrollableWidgetList
|
||||
/// how many items there are in the list.
|
||||
int get itemCount;
|
||||
int _previousItemCount;
|
||||
|
||||
void didUpdateConfig(T oldConfig) {
|
||||
super.didUpdateConfig(oldConfig);
|
||||
|
||||
bool scrollBehaviorUpdateNeeded =
|
||||
config.padding != oldConfig.padding ||
|
||||
config.itemExtent != oldConfig.itemExtent ||
|
||||
config.scrollDirection != oldConfig.scrollDirection;
|
||||
|
||||
if (config.itemsWrap != oldConfig.itemsWrap) {
|
||||
_scrollBehavior = null;
|
||||
scrollBehaviorUpdateNeeded = true;
|
||||
}
|
||||
|
||||
if (itemCount != _previousItemCount) {
|
||||
scrollBehaviorUpdateNeeded = true;
|
||||
_previousItemCount = itemCount;
|
||||
}
|
||||
|
||||
if (scrollBehaviorUpdateNeeded)
|
||||
_updateScrollBehavior();
|
||||
}
|
||||
|
||||
ScrollBehavior createScrollBehavior() => new OverscrollBehavior();
|
||||
ExtentScrollBehavior get scrollBehavior => super.scrollBehavior;
|
||||
|
||||
double get _containerExtent {
|
||||
return config.scrollDirection == ScrollDirection.vertical
|
||||
? config.containerSize.height
|
||||
: config.containerSize.width;
|
||||
}
|
||||
|
||||
void _handleSizeChanged(Size newSize) {
|
||||
setState(() {
|
||||
config.containerSize = newSize;
|
||||
_updateScrollBehavior();
|
||||
});
|
||||
}
|
||||
|
||||
double get _leadingPadding {
|
||||
EdgeDims padding = config.padding;
|
||||
if (config.scrollDirection == ScrollDirection.vertical)
|
||||
return padding != null ? padding.top : 0.0;
|
||||
return padding != null ? padding.left : -.0;
|
||||
}
|
||||
|
||||
double get _trailingPadding {
|
||||
EdgeDims padding = config.padding;
|
||||
if (config.scrollDirection == ScrollDirection.vertical)
|
||||
return padding != null ? padding.bottom : 0.0;
|
||||
return padding != null ? padding.right : 0.0;
|
||||
}
|
||||
|
||||
EdgeDims get _crossAxisPadding {
|
||||
EdgeDims padding = config.padding;
|
||||
if (padding == null)
|
||||
return null;
|
||||
if (config.scrollDirection == ScrollDirection.vertical)
|
||||
return new EdgeDims.only(left: padding.left, right: padding.right);
|
||||
return new EdgeDims.only(top: padding.top, bottom: padding.bottom);
|
||||
}
|
||||
|
||||
void _updateScrollBehavior() {
|
||||
// if you don't call this from build() or syncConstructorArguments(), you must call it from setState().
|
||||
double contentExtent = config.itemExtent * itemCount;
|
||||
if (config.padding != null)
|
||||
contentExtent += _leadingPadding + _trailingPadding;
|
||||
scrollTo(scrollBehavior.updateExtents(
|
||||
contentExtent: contentExtent,
|
||||
containerExtent: _containerExtent,
|
||||
scrollOffset: scrollOffset
|
||||
));
|
||||
}
|
||||
|
||||
Widget buildContent(BuildContext context) {
|
||||
if (itemCount != _previousItemCount) {
|
||||
_previousItemCount = itemCount;
|
||||
_updateScrollBehavior();
|
||||
}
|
||||
|
||||
return new SizeObserver(
|
||||
callback: _handleSizeChanged,
|
||||
child: new Container(
|
||||
padding: _crossAxisPadding,
|
||||
child: new HomogeneousViewport(
|
||||
builder: _buildItems,
|
||||
itemsWrap: config.itemsWrap,
|
||||
itemExtent: config.itemExtent,
|
||||
itemCount: itemCount,
|
||||
direction: config.scrollDirection,
|
||||
startOffset: scrollOffset - _leadingPadding
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildItems(BuildContext context, int start, int count) {
|
||||
List<Widget> result = buildItems(context, start, count);
|
||||
assert(result.every((item) => item.key != null));
|
||||
return result;
|
||||
}
|
||||
|
||||
List<Widget> buildItems(BuildContext context, int start, int count);
|
||||
|
||||
}
|
||||
|
||||
typedef Widget ItemBuilder<T>(BuildContext context, T item);
|
||||
|
||||
/// A wrapper around [ScrollableWidgetList] that helps you translate a list of
|
||||
/// model objects into a scrollable list of widgets. Assumes all the widgets
|
||||
/// have the same height.
|
||||
class ScrollableList<T> extends ScrollableWidgetList {
|
||||
ScrollableList({
|
||||
Key key,
|
||||
double initialScrollOffset,
|
||||
ScrollDirection scrollDirection: ScrollDirection.vertical,
|
||||
this.items,
|
||||
this.itemBuilder,
|
||||
itemsWrap: false,
|
||||
double itemExtent,
|
||||
EdgeDims padding
|
||||
}) : super(
|
||||
key: key,
|
||||
initialScrollOffset: initialScrollOffset,
|
||||
scrollDirection: scrollDirection,
|
||||
itemsWrap: itemsWrap,
|
||||
itemExtent: itemExtent,
|
||||
padding: padding);
|
||||
|
||||
final List<T> items;
|
||||
final ItemBuilder<T> itemBuilder;
|
||||
|
||||
ScrollableListState<T, ScrollableList<T>> createState() => new ScrollableListState<T, ScrollableList<T>>(this);
|
||||
}
|
||||
|
||||
class ScrollableListState<T, Config extends ScrollableList<T>> extends ScrollableWidgetListState<Config> {
|
||||
ScrollableListState(Config config) : super(config);
|
||||
|
||||
ScrollBehavior createScrollBehavior() {
|
||||
return config.itemsWrap ? new UnboundedBehavior() : super.createScrollBehavior();
|
||||
}
|
||||
|
||||
int get itemCount => config.items.length;
|
||||
|
||||
List<Widget> buildItems(BuildContext context, int start, int count) {
|
||||
List<Widget> result = new List<Widget>();
|
||||
int begin = config.itemsWrap ? start : math.max(0, start);
|
||||
int end = config.itemsWrap ? begin + count : math.min(begin + count, config.items.length);
|
||||
for (int i = begin; i < end; ++i)
|
||||
result.add(config.itemBuilder(context, config.items[i % itemCount]));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
typedef void PageChangedCallback(int newPage);
|
||||
|
||||
class PageableList<T> extends ScrollableList<T> {
|
||||
PageableList({
|
||||
Key key,
|
||||
double initialScrollOffset,
|
||||
ScrollDirection scrollDirection: ScrollDirection.horizontal,
|
||||
List<T> items,
|
||||
ItemBuilder<T> itemBuilder,
|
||||
bool itemsWrap: false,
|
||||
double itemExtent,
|
||||
PageChangedCallback this.pageChanged,
|
||||
EdgeDims padding,
|
||||
this.duration: const Duration(milliseconds: 200),
|
||||
this.curve: ease
|
||||
}) : super(
|
||||
key: key,
|
||||
initialScrollOffset: initialScrollOffset,
|
||||
scrollDirection: scrollDirection,
|
||||
items: items,
|
||||
itemBuilder: itemBuilder,
|
||||
itemsWrap: itemsWrap,
|
||||
itemExtent: itemExtent,
|
||||
padding: padding
|
||||
);
|
||||
|
||||
Duration duration;
|
||||
Curve curve;
|
||||
PageChangedCallback pageChanged;
|
||||
}
|
||||
|
||||
class PageableListState<T> extends ScrollableListState<T, PageableList<T>> {
|
||||
PageableListState(PageableList<T> config) : super(config);
|
||||
|
||||
double _snapScrollOffset(double newScrollOffset) {
|
||||
double scaledScrollOffset = newScrollOffset / config.itemExtent;
|
||||
double previousScrollOffset = scaledScrollOffset.floor() * config.itemExtent;
|
||||
double nextScrollOffset = scaledScrollOffset.ceil() * config.itemExtent;
|
||||
double delta = newScrollOffset - previousScrollOffset;
|
||||
return (delta < config.itemExtent / 2.0 ? previousScrollOffset : nextScrollOffset)
|
||||
.clamp(scrollBehavior.minScrollOffset, scrollBehavior.maxScrollOffset);
|
||||
}
|
||||
|
||||
void _handleDragEnd(sky.Offset velocity) {
|
||||
double scrollVelocity = _scrollVelocity(velocity);
|
||||
double newScrollOffset = _snapScrollOffset(scrollOffset + scrollVelocity.sign * config.itemExtent)
|
||||
.clamp(_snapScrollOffset(scrollOffset - config.itemExtent / 2.0),
|
||||
_snapScrollOffset(scrollOffset + config.itemExtent / 2.0));
|
||||
scrollTo(newScrollOffset, duration: config.duration, curve: config.curve).then(_notifyPageChanged);
|
||||
}
|
||||
|
||||
int get currentPage => (scrollOffset / config.itemExtent).floor() % itemCount;
|
||||
|
||||
void _notifyPageChanged(_) {
|
||||
if (config.pageChanged != null)
|
||||
config.pageChanged(currentPage);
|
||||
}
|
||||
|
||||
void settleScrollOffset() {
|
||||
scrollTo(_snapScrollOffset(scrollOffset), duration: config.duration, curve: config.curve).then(_notifyPageChanged);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(abarth): ScrollableMixedWidgetList
|
@ -1,3 +1,4 @@
|
||||
import 'package:sky/rendering.dart';
|
||||
import 'package:sky/src/fn3.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
@ -9,7 +10,6 @@ void main() {
|
||||
|
||||
Key keyA = new GlobalKey();
|
||||
Key keyB = new GlobalKey();
|
||||
Key keyC = new GlobalKey();
|
||||
|
||||
tester.pumpFrame(
|
||||
new Stack([
|
||||
@ -34,13 +34,13 @@ void main() {
|
||||
])
|
||||
);
|
||||
|
||||
expect(tester.findElementByKey(keyA).renderObject.localToGlobal(const Point(0.0, 0.0)),
|
||||
expect((tester.findElementByKey(keyA).renderObject as RenderBox).localToGlobal(const Point(0.0, 0.0)),
|
||||
equals(const Point(100.0, 100.0)));
|
||||
|
||||
expect(tester.findElementByKey(keyB).renderObject.localToGlobal(const Point(0.0, 0.0)),
|
||||
expect((tester.findElementByKey(keyB).renderObject as RenderBox).localToGlobal(const Point(0.0, 0.0)),
|
||||
equals(const Point(100.0, 200.0)));
|
||||
|
||||
expect(tester.findElementByKey(keyB).renderObject.globalToLocal(const Point(110.0, 205.0)),
|
||||
expect((tester.findElementByKey(keyB).renderObject as RenderBox).globalToLocal(const Point(110.0, 205.0)),
|
||||
equals(const Point(10.0, 5.0)));
|
||||
});
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ void main() {
|
||||
|
||||
Widget builder() {
|
||||
return new TestComponent(new HomogeneousViewport(
|
||||
builder: (int start, int count, BuildContext context) {
|
||||
builder: (BuildContext context, int start, int count) {
|
||||
List<Widget> result = <Widget>[];
|
||||
for (int index = start; index < start + count; index += 1) {
|
||||
callbackTracker.add(index);
|
||||
@ -52,18 +52,19 @@ void main() {
|
||||
|
||||
tester.pumpFrame(builder());
|
||||
|
||||
TestComponentState testComponent = tester.findElement((element) => element.widget is TestComponent).state;
|
||||
StatefulComponentElement testComponent = tester.findElement((element) => element.widget is TestComponent);
|
||||
TestComponentState testComponentState = testComponent.state;
|
||||
|
||||
expect(callbackTracker, equals([0, 1, 2, 3, 4, 5]));
|
||||
|
||||
callbackTracker.clear();
|
||||
testComponent.go(false);
|
||||
testComponentState.go(false);
|
||||
tester.pumpFrameWithoutChange();
|
||||
|
||||
expect(callbackTracker, equals([]));
|
||||
|
||||
callbackTracker.clear();
|
||||
testComponent.go(true);
|
||||
testComponentState.go(true);
|
||||
tester.pumpFrameWithoutChange();
|
||||
|
||||
expect(callbackTracker, equals([0, 1, 2, 3, 4, 5]));
|
||||
@ -80,7 +81,7 @@ void main() {
|
||||
|
||||
double offset = 300.0;
|
||||
|
||||
ListBuilder itemBuilder = (int start, int count, BuildContext context) {
|
||||
ListBuilder itemBuilder = (BuildContext context, int start, int count) {
|
||||
List<Widget> result = <Widget>[];
|
||||
for (int index = start; index < start + count; index += 1) {
|
||||
callbackTracker.add(index);
|
||||
@ -130,7 +131,7 @@ void main() {
|
||||
|
||||
double offset = 300.0;
|
||||
|
||||
ListBuilder itemBuilder = (int start, int count, BuildContext context) {
|
||||
ListBuilder itemBuilder = (BuildContext context, int start, int count) {
|
||||
List<Widget> result = <Widget>[];
|
||||
for (int index = start; index < start + count; index += 1) {
|
||||
callbackTracker.add(index);
|
||||
|
@ -1,4 +1,3 @@
|
||||
import 'package:quiver/testing/async.dart';
|
||||
import 'package:sky/widgets.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user