mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Add a scrollbar to the license screen. (#5114)
And make Scrollbar work with LazyBlock. And an about box to the Stocks sample app.
This commit is contained in:
parent
2b3099c814
commit
51f8fb9979
@ -64,6 +64,11 @@ class FancyItemDelegate extends LazyBlockDelegate {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool shouldRebuild(FancyItemDelegate oldDelegate) => false;
|
bool shouldRebuild(FancyItemDelegate oldDelegate) => false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
double estimateTotalExtent(int firstIndex, int lastIndex, double minOffset, double firstStartOffset, double lastEndOffset) {
|
||||||
|
return double.INFINITY;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ComplexLayoutState extends State<ComplexLayout> {
|
class ComplexLayoutState extends State<ComplexLayout> {
|
||||||
|
@ -125,6 +125,11 @@ class CardBuilder extends LazyBlockDelegate {
|
|||||||
bool shouldRebuild(CardBuilder oldDelegate) {
|
bool shouldRebuild(CardBuilder oldDelegate) {
|
||||||
return oldDelegate.cardModels != cardModels;
|
return oldDelegate.cardModels != cardModels;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
double estimateTotalExtent(int firstIndex, int lastIndex, double minOffset, double firstStartOffset, double lastEndOffset) {
|
||||||
|
return (lastEndOffset - minOffset) * cardModels.length / (lastIndex + 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class OverlayGeometryAppState extends State<OverlayGeometryApp> {
|
class OverlayGeometryAppState extends State<OverlayGeometryApp> {
|
||||||
|
@ -132,29 +132,7 @@ class StockHomeState extends State<StockHome> {
|
|||||||
),
|
),
|
||||||
new DrawerItem(
|
new DrawerItem(
|
||||||
icon: new Icon(Icons.account_balance),
|
icon: new Icon(Icons.account_balance),
|
||||||
onPressed: () {
|
onPressed: null,
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
child: new Dialog(
|
|
||||||
title: new Text('Not Implemented'),
|
|
||||||
content: new Text('This feature has not yet been implemented.'),
|
|
||||||
actions: <Widget>[
|
|
||||||
new FlatButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.pop(context, false);
|
|
||||||
},
|
|
||||||
child: new Text('USE IT')
|
|
||||||
),
|
|
||||||
new FlatButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.pop(context, false);
|
|
||||||
},
|
|
||||||
child: new Text('OH WELL')
|
|
||||||
),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: new Text('Account Balance')
|
child: new Text('Account Balance')
|
||||||
),
|
),
|
||||||
new DrawerItem(
|
new DrawerItem(
|
||||||
@ -199,7 +177,8 @@ class StockHomeState extends State<StockHome> {
|
|||||||
child: new Text('Settings')),
|
child: new Text('Settings')),
|
||||||
new DrawerItem(
|
new DrawerItem(
|
||||||
icon: new Icon(Icons.help),
|
icon: new Icon(Icons.help),
|
||||||
child: new Text('Help & Feedback'))
|
onPressed: _handleShowAbout,
|
||||||
|
child: new Text('About'))
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -208,6 +187,10 @@ class StockHomeState extends State<StockHome> {
|
|||||||
Navigator.popAndPushNamed(context, '/settings');
|
Navigator.popAndPushNamed(context, '/settings');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _handleShowAbout() {
|
||||||
|
showAboutDialog(context: context);
|
||||||
|
}
|
||||||
|
|
||||||
Widget buildAppBar() {
|
Widget buildAppBar() {
|
||||||
return new AppBar(
|
return new AppBar(
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
|
@ -58,10 +58,10 @@ void main() {
|
|||||||
|
|
||||||
// sanity check
|
// sanity check
|
||||||
expect(find.text('MARKET'), findsOneWidget);
|
expect(find.text('MARKET'), findsOneWidget);
|
||||||
expect(find.text('Help & Feedback'), findsNothing);
|
expect(find.text('Account Balance'), findsNothing);
|
||||||
await tester.pump(new Duration(seconds: 2));
|
await tester.pump(new Duration(seconds: 2));
|
||||||
expect(find.text('MARKET'), findsOneWidget);
|
expect(find.text('MARKET'), findsOneWidget);
|
||||||
expect(find.text('Help & Feedback'), findsNothing);
|
expect(find.text('Account Balance'), findsNothing);
|
||||||
|
|
||||||
// drag the drawer out
|
// drag the drawer out
|
||||||
Point left = new Point(0.0, ui.window.size.height / 2.0);
|
Point left = new Point(0.0, ui.window.size.height / 2.0);
|
||||||
@ -73,12 +73,12 @@ void main() {
|
|||||||
await gesture.up();
|
await gesture.up();
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
expect(find.text('MARKET'), findsOneWidget);
|
expect(find.text('MARKET'), findsOneWidget);
|
||||||
expect(find.text('Help & Feedback'), findsOneWidget);
|
expect(find.text('Account Balance'), findsOneWidget);
|
||||||
|
|
||||||
// check the colour of the icon - light mode
|
// check the colour of the icon - light mode
|
||||||
checkIconColor(tester, 'Stock List', Colors.purple[500]); // theme primary color
|
checkIconColor(tester, 'Stock List', Colors.purple[500]); // theme primary color
|
||||||
checkIconColor(tester, 'Account Balance', Colors.black45); // enabled
|
checkIconColor(tester, 'Account Balance', Colors.black26); // disabled
|
||||||
checkIconColor(tester, 'Help & Feedback', Colors.black26); // disabled
|
checkIconColor(tester, 'About', Colors.black45); // enabled
|
||||||
|
|
||||||
// switch to dark mode
|
// switch to dark mode
|
||||||
await tester.tap(find.text('Pessimistic'));
|
await tester.tap(find.text('Pessimistic'));
|
||||||
@ -88,7 +88,7 @@ void main() {
|
|||||||
|
|
||||||
// check the colour of the icon - dark mode
|
// check the colour of the icon - dark mode
|
||||||
checkIconColor(tester, 'Stock List', Colors.redAccent[200]); // theme accent color
|
checkIconColor(tester, 'Stock List', Colors.redAccent[200]); // theme accent color
|
||||||
checkIconColor(tester, 'Account Balance', Colors.white); // enabled
|
checkIconColor(tester, 'Account Balance', Colors.white30); // disabled
|
||||||
checkIconColor(tester, 'Help & Feedback', Colors.white30); // disabled
|
checkIconColor(tester, 'About', Colors.white); // enabled
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ import 'icon.dart';
|
|||||||
import 'page.dart';
|
import 'page.dart';
|
||||||
import 'progress_indicator.dart';
|
import 'progress_indicator.dart';
|
||||||
import 'scaffold.dart';
|
import 'scaffold.dart';
|
||||||
|
import 'scrollbar.dart';
|
||||||
import 'theme.dart';
|
import 'theme.dart';
|
||||||
|
|
||||||
/// A [DrawerItem] to show an about box.
|
/// A [DrawerItem] to show an about box.
|
||||||
@ -426,10 +427,12 @@ class _LicensePageState extends State<LicensePage> {
|
|||||||
),
|
),
|
||||||
body: new DefaultTextStyle(
|
body: new DefaultTextStyle(
|
||||||
style: Theme.of(context).textTheme.caption,
|
style: Theme.of(context).textTheme.caption,
|
||||||
child: new LazyBlock(
|
child: new Scrollbar(
|
||||||
padding: new EdgeInsets.symmetric(horizontal: 8.0, vertical: 12.0),
|
child: new LazyBlock(
|
||||||
delegate: new LazyBlockChildren(
|
padding: new EdgeInsets.symmetric(horizontal: 8.0, vertical: 12.0),
|
||||||
children: contents
|
delegate: new LazyBlockChildren(
|
||||||
|
children: contents
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -64,10 +64,10 @@ class _Painter extends CustomPainter {
|
|||||||
@override
|
@override
|
||||||
bool shouldRepaint(_Painter oldPainter) {
|
bool shouldRepaint(_Painter oldPainter) {
|
||||||
return oldPainter.scrollOffset != scrollOffset
|
return oldPainter.scrollOffset != scrollOffset
|
||||||
|| oldPainter.scrollDirection != scrollDirection
|
|| oldPainter.scrollDirection != scrollDirection
|
||||||
|| oldPainter.contentExtent != contentExtent
|
|| oldPainter.contentExtent != contentExtent
|
||||||
|| oldPainter.containerExtent != containerExtent
|
|| oldPainter.containerExtent != containerExtent
|
||||||
|| oldPainter.color != color;
|
|| oldPainter.color != color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,7 +98,6 @@ class Scrollbar extends StatefulWidget {
|
|||||||
class _ScrollbarState extends State<Scrollbar> {
|
class _ScrollbarState extends State<Scrollbar> {
|
||||||
final AnimationController _fade = new AnimationController(duration: _kScrollbarThumbFadeDuration);
|
final AnimationController _fade = new AnimationController(duration: _kScrollbarThumbFadeDuration);
|
||||||
CurvedAnimation _opacity;
|
CurvedAnimation _opacity;
|
||||||
double _scrollOffsetAnchor;
|
|
||||||
double _scrollOffset;
|
double _scrollOffset;
|
||||||
Axis _scrollDirection;
|
Axis _scrollDirection;
|
||||||
double _containerExtent;
|
double _containerExtent;
|
||||||
@ -119,28 +118,25 @@ class _ScrollbarState extends State<Scrollbar> {
|
|||||||
void _updateState(ScrollableState scrollable) {
|
void _updateState(ScrollableState scrollable) {
|
||||||
if (scrollable.scrollBehavior is! ExtentScrollBehavior)
|
if (scrollable.scrollBehavior is! ExtentScrollBehavior)
|
||||||
return;
|
return;
|
||||||
|
if (_scrollOffset != scrollable.scrollOffset)
|
||||||
|
setState(() { _scrollOffset = scrollable.scrollOffset; });
|
||||||
|
if (_scrollDirection != scrollable.config.scrollDirection)
|
||||||
|
setState(() { _scrollDirection = scrollable.config.scrollDirection; });
|
||||||
final ExtentScrollBehavior scrollBehavior = scrollable.scrollBehavior;
|
final ExtentScrollBehavior scrollBehavior = scrollable.scrollBehavior;
|
||||||
_scrollOffset = scrollable.scrollOffset;
|
if (_contentExtent != scrollBehavior.contentExtent)
|
||||||
_scrollDirection = scrollable.config.scrollDirection;
|
setState(() { _contentExtent = scrollBehavior.contentExtent; });
|
||||||
_contentExtent = scrollBehavior.contentExtent;
|
if (_containerExtent != scrollBehavior.containerExtent)
|
||||||
_containerExtent = scrollBehavior.containerExtent;
|
setState(() { _containerExtent = scrollBehavior.containerExtent; });
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onScrollStarted(ScrollableState scrollable) {
|
void _onScrollStarted(ScrollableState scrollable) {
|
||||||
_updateState(scrollable);
|
_updateState(scrollable);
|
||||||
_scrollOffsetAnchor = _scrollOffset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onScrollUpdated(ScrollableState scrollable) {
|
void _onScrollUpdated(ScrollableState scrollable) {
|
||||||
_updateState(scrollable);
|
_updateState(scrollable);
|
||||||
if (!_fade.isAnimating) {
|
if (_fade.status != AnimationStatus.completed)
|
||||||
if (_scrollOffsetAnchor != _scrollOffset && _fade.value == 0.0)
|
_fade.forward();
|
||||||
_fade.forward(); // Lazily start the scrollbar fade-in.
|
|
||||||
setState(() {
|
|
||||||
// If the scrollbar has faded in, rebuild it per the new scrollable state.
|
|
||||||
// If the fade-in is underway this setState() will have no effect.
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onScrollEnded(ScrollableState scrollable) {
|
void _onScrollEnded(ScrollableState scrollable) {
|
||||||
@ -150,8 +146,8 @@ class _ScrollbarState extends State<Scrollbar> {
|
|||||||
|
|
||||||
bool _handleScrollNotification(ScrollNotification notification) {
|
bool _handleScrollNotification(ScrollNotification notification) {
|
||||||
if (config.scrollableKey == null) {
|
if (config.scrollableKey == null) {
|
||||||
if (notification.depth != 0)
|
if (notification.depth != 0)
|
||||||
return false;
|
return false;
|
||||||
} else if (config.scrollableKey != notification.scrollable.config.key) {
|
} else if (config.scrollableKey != notification.scrollable.config.key) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -72,6 +72,7 @@ class _FrameCallbackEntry {
|
|||||||
});
|
});
|
||||||
stack = currentCallbackStack;
|
stack = currentCallbackStack;
|
||||||
} else {
|
} else {
|
||||||
|
// TODO(ianh): trim the frames from this library, so that the call to scheduleFrameCallback is the top one
|
||||||
stack = StackTrace.current;
|
stack = StackTrace.current;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@ -283,25 +284,30 @@ abstract class SchedulerBinding extends BindingBase {
|
|||||||
bool debugAssertNoTransientCallbacks(String reason) {
|
bool debugAssertNoTransientCallbacks(String reason) {
|
||||||
assert(() {
|
assert(() {
|
||||||
if (transientCallbackCount > 0) {
|
if (transientCallbackCount > 0) {
|
||||||
|
// We cache the values so that we can produce them later
|
||||||
|
// even if the information collector is called after
|
||||||
|
// the problem has been resolved.
|
||||||
|
final int count = transientCallbackCount;
|
||||||
|
final Map<int, _FrameCallbackEntry> callbacks = new Map<int, _FrameCallbackEntry>.from(_transientCallbacks);
|
||||||
FlutterError.reportError(new FlutterErrorDetails(
|
FlutterError.reportError(new FlutterErrorDetails(
|
||||||
exception: reason,
|
exception: reason,
|
||||||
library: 'scheduler library',
|
library: 'scheduler library',
|
||||||
informationCollector: (StringBuffer information) {
|
informationCollector: (StringBuffer information) {
|
||||||
if (transientCallbackCount == 1) {
|
if (count == 1) {
|
||||||
information.writeln(
|
information.writeln(
|
||||||
'There was one transient callback left. '
|
'There was one transient callback left. '
|
||||||
'The stack traces for when it was registered is as follows:'
|
'The stack trace for when it was registered is as follows:'
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
information.writeln(
|
information.writeln(
|
||||||
'There were $transientCallbackCount transient callbacks left. '
|
'There were $count transient callbacks left. '
|
||||||
'The stack traces for when they were registered are as follows:'
|
'The stack traces for when they were registered are as follows:'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
for (int id in _transientCallbacks.keys) {
|
for (int id in callbacks.keys) {
|
||||||
_FrameCallbackEntry entry = _transientCallbacks[id];
|
_FrameCallbackEntry entry = callbacks[id];
|
||||||
information.writeln('-- callback $id --');
|
information.writeln('── callback $id ──');
|
||||||
information.writeln(entry.stack);
|
FlutterError.defaultStackFilter(entry.stack.toString().trimRight().split('\n')).forEach(information.writeln);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
|
@ -72,6 +72,8 @@ class DecoratedBox extends SingleChildRenderObjectWidget {
|
|||||||
/// extent.
|
/// extent.
|
||||||
class Container extends StatelessWidget {
|
class Container extends StatelessWidget {
|
||||||
/// Creates a widget that combines common painting, positioning, and sizing widgets.
|
/// Creates a widget that combines common painting, positioning, and sizing widgets.
|
||||||
|
///
|
||||||
|
/// The `height` and `width` values include the padding.
|
||||||
Container({
|
Container({
|
||||||
Key key,
|
Key key,
|
||||||
this.align,
|
this.align,
|
||||||
@ -116,6 +118,8 @@ class Container extends StatelessWidget {
|
|||||||
final Decoration foregroundDecoration;
|
final Decoration foregroundDecoration;
|
||||||
|
|
||||||
/// Additional constraints to apply to the child.
|
/// Additional constraints to apply to the child.
|
||||||
|
///
|
||||||
|
/// The [padding] goes inside the constraints.
|
||||||
final BoxConstraints constraints;
|
final BoxConstraints constraints;
|
||||||
|
|
||||||
/// Empty space to surround the decoration.
|
/// Empty space to surround the decoration.
|
||||||
|
@ -405,8 +405,8 @@ abstract class Widget {
|
|||||||
/// use another widget as its configuration if, and only if, the two widgets
|
/// use another widget as its configuration if, and only if, the two widgets
|
||||||
/// have [runtimeType] and [key] properties that are [operator==].
|
/// have [runtimeType] and [key] properties that are [operator==].
|
||||||
static bool canUpdate(Widget oldWidget, Widget newWidget) {
|
static bool canUpdate(Widget oldWidget, Widget newWidget) {
|
||||||
return oldWidget.runtimeType == newWidget.runtimeType &&
|
return oldWidget.runtimeType == newWidget.runtimeType
|
||||||
oldWidget.key == newWidget.key;
|
&& oldWidget.key == newWidget.key;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,17 +50,56 @@ abstract class LazyBlockDelegate {
|
|||||||
/// When calling this function, [LazyBlock] will always pass an argument that
|
/// When calling this function, [LazyBlock] will always pass an argument that
|
||||||
/// matches the runtimeType of the receiver.
|
/// matches the runtimeType of the receiver.
|
||||||
bool shouldRebuild(LazyBlockDelegate oldDelegate);
|
bool shouldRebuild(LazyBlockDelegate oldDelegate);
|
||||||
|
|
||||||
|
/// Returns the estimated total height of the children, in pixels.
|
||||||
|
///
|
||||||
|
/// If there's an infinite number of children, this should return
|
||||||
|
/// [double.INFINITY].
|
||||||
|
///
|
||||||
|
/// The provided values can be used to estimate the total extent.
|
||||||
|
///
|
||||||
|
/// The `firstIndex` and `lastIndex` values give the integers that were passed
|
||||||
|
/// to [buildItem] to build the respective widgets.
|
||||||
|
///
|
||||||
|
/// The `minOffset` is the offset of the widget with index 0. Unless the
|
||||||
|
/// `firstIndex` is 0, the `minOffset` is only itself an estimate.
|
||||||
|
///
|
||||||
|
/// The `firstStartOffset` is the offset of the widget with `firstIndex`, in
|
||||||
|
/// the same coordinate space as `minOffset`.
|
||||||
|
///
|
||||||
|
/// The `lastEndOffset` is the offset of the widget that would be after
|
||||||
|
/// `lastIndex`, in the same coordinate space as `minOffset`. (In other words,
|
||||||
|
/// it's the offset to the end of the `lastIndex` widget.)
|
||||||
|
///
|
||||||
|
/// A simple algorithm for this function, which works well when there are many
|
||||||
|
/// children, the exact child count is known, and the children near the top of
|
||||||
|
/// the list are more or less representative of the length of the other
|
||||||
|
/// children, is the following:
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// // childCount is the number of children
|
||||||
|
/// return (lastEndOffset - minOffset) * childCount / (lastIndex + 1);
|
||||||
|
/// ```
|
||||||
|
double estimateTotalExtent(int firstIndex, int lastIndex, double minOffset, double firstStartOffset, double lastEndOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Signature for callbacks that estimate the total height of a [LazyBlock]'s contents.
|
||||||
|
///
|
||||||
|
/// See [LazyBlockDelegate.estimateTotalExtent] for details.
|
||||||
|
typedef double TotalExtentEstimator(int firstIndex, int lastIndex, double minOffset, double firstStartOffset, double lastEndOffset);
|
||||||
|
|
||||||
/// Uses an [IndexedWidgetBuilder] to provide children for [LazyBlock].
|
/// Uses an [IndexedWidgetBuilder] to provide children for [LazyBlock].
|
||||||
///
|
///
|
||||||
/// A LazyBlockBuilder rebuilds the children whenever the [LazyBlock] is
|
/// A LazyBlockBuilder rebuilds the children whenever the [LazyBlock] is
|
||||||
/// rebuilt, similar to the behavior of [Builder].
|
/// rebuilt, similar to the behavior of [Builder].
|
||||||
///
|
///
|
||||||
|
/// To use a [Scrollbar] with this delegate, you must provide an
|
||||||
|
/// [estimateTotalExtent] callback.
|
||||||
|
///
|
||||||
/// See also [LazyBlockViewport].
|
/// See also [LazyBlockViewport].
|
||||||
class LazyBlockBuilder extends LazyBlockDelegate {
|
class LazyBlockBuilder extends LazyBlockDelegate {
|
||||||
/// Creates a LazyBlockBuilder based on the given builder.
|
/// Creates a LazyBlockBuilder based on the given builder.
|
||||||
LazyBlockBuilder({ this.builder }) {
|
LazyBlockBuilder({ this.builder, this.totalExtentEstimator }) {
|
||||||
assert(builder != null);
|
assert(builder != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,11 +115,26 @@ class LazyBlockBuilder extends LazyBlockDelegate {
|
|||||||
/// pipeline.
|
/// pipeline.
|
||||||
final IndexedWidgetBuilder builder;
|
final IndexedWidgetBuilder builder;
|
||||||
|
|
||||||
|
/// Returns the estimated total height of the children, in pixels.
|
||||||
|
///
|
||||||
|
/// If null, the estimate will be infinite, even if a null child has been
|
||||||
|
/// returned by [builder].
|
||||||
|
///
|
||||||
|
/// See [LazyBlockDelegate.estimateTotalExtent] for details.
|
||||||
|
final TotalExtentEstimator totalExtentEstimator;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget buildItem(BuildContext context, int index) => builder(context, index);
|
Widget buildItem(BuildContext context, int index) => builder(context, index);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool shouldRebuild(LazyBlockDelegate oldDelegate) => true;
|
bool shouldRebuild(LazyBlockDelegate oldDelegate) => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
double estimateTotalExtent(int firstIndex, int lastIndex, double minOffset, double firstStartOffset, double lastEndOffset) {
|
||||||
|
if (totalExtentEstimator != null)
|
||||||
|
return totalExtentEstimator(firstIndex, lastIndex, minOffset, firstStartOffset, lastEndOffset);
|
||||||
|
return double.INFINITY;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Uses a [List<Widget>] to provide children for [LazyBlock].
|
/// Uses a [List<Widget>] to provide children for [LazyBlock].
|
||||||
@ -110,6 +164,14 @@ class LazyBlockChildren extends LazyBlockDelegate {
|
|||||||
bool shouldRebuild(LazyBlockChildren oldDelegate) {
|
bool shouldRebuild(LazyBlockChildren oldDelegate) {
|
||||||
return children != oldDelegate.children;
|
return children != oldDelegate.children;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
double estimateTotalExtent(int firstIndex, int lastIndex, double minOffset, double firstStartOffset, double lastEndOffset) {
|
||||||
|
final int childCount = children.length;
|
||||||
|
if (childCount == 0)
|
||||||
|
return 0.0;
|
||||||
|
return (lastEndOffset - minOffset) * childCount / (lastIndex + 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An infinite scrolling list of variably-sized children.
|
/// An infinite scrolling list of variably-sized children.
|
||||||
@ -202,10 +264,10 @@ class LazyBlock extends StatelessWidget {
|
|||||||
startOffset: scrollOffset,
|
startOffset: scrollOffset,
|
||||||
mainAxis: scrollDirection,
|
mainAxis: scrollDirection,
|
||||||
padding: padding,
|
padding: padding,
|
||||||
onExtentsChanged: (double contentExtent, double containerExtent, double minScrollOffset) {
|
onExtentsChanged: (int firstIndex, int lastIndex, double firstStartOffset, double lastEndOffset, double minScrollOffset, double containerExtent) {
|
||||||
final BoundedBehavior scrollBehavior = state.scrollBehavior;
|
final BoundedBehavior scrollBehavior = state.scrollBehavior;
|
||||||
state.didUpdateScrollBehavior(scrollBehavior.updateExtents(
|
state.didUpdateScrollBehavior(scrollBehavior.updateExtents(
|
||||||
contentExtent: contentExtent,
|
contentExtent: delegate.estimateTotalExtent(firstIndex, lastIndex, minScrollOffset, firstStartOffset, lastEndOffset),
|
||||||
containerExtent: containerExtent,
|
containerExtent: containerExtent,
|
||||||
minScrollOffset: minScrollOffset,
|
minScrollOffset: minScrollOffset,
|
||||||
scrollOffset: state.scrollOffset
|
scrollOffset: state.scrollOffset
|
||||||
@ -237,12 +299,16 @@ class LazyBlock extends StatelessWidget {
|
|||||||
|
|
||||||
/// Signature used by [LazyBlockViewport] to report its interior and exterior dimensions.
|
/// Signature used by [LazyBlockViewport] to report its interior and exterior dimensions.
|
||||||
///
|
///
|
||||||
/// * The [contentExtent] is the interior dimension of the viewport (i.e., the
|
/// * The `firstIndex` is the index of the child that is visible at the
|
||||||
/// size of the thing that's being viewed through the viewport).
|
/// starting edge of the viewport.
|
||||||
/// * The [containerExtent] is the exterior dimension of the viewport (i.e.,
|
/// * The `lastIndex` is the index of the child that is visible at the ending
|
||||||
/// the amount of the thing inside the viewport that is visible from outside
|
/// edge of the viewport. This could be the same as the `firstIndex` if the
|
||||||
/// the viewport).
|
/// child is bigger than the viewport or if it is the last child.
|
||||||
/// * The [minScrollOffset] is the offset at which the starting edge of the
|
/// * The `firstStartOffset` is the offset of the starting edge of the child
|
||||||
|
/// with index `firstIndex`.
|
||||||
|
/// * The `lastEndOffset` is the offset of the ending edge of the child with
|
||||||
|
/// index `lastIndex`.
|
||||||
|
/// * The `minScrollOffset` is the offset at which the starting edge of the
|
||||||
/// first item in the viewport is aligned with the starting edge of the
|
/// first item in the viewport is aligned with the starting edge of the
|
||||||
/// viewport. (As the scroll offset increases, items with larger indices are
|
/// viewport. (As the scroll offset increases, items with larger indices are
|
||||||
/// revealed in the viewport.) Typically the min scroll offset is 0.0, but
|
/// revealed in the viewport.) Typically the min scroll offset is 0.0, but
|
||||||
@ -250,7 +316,10 @@ class LazyBlock extends StatelessWidget {
|
|||||||
/// might not always be 0.0. For example, if an item that's offscreen changes
|
/// might not always be 0.0. For example, if an item that's offscreen changes
|
||||||
/// size, the visible items will retain their current scroll offsets even if
|
/// size, the visible items will retain their current scroll offsets even if
|
||||||
/// the distance to the starting edge of the first item changes.
|
/// the distance to the starting edge of the first item changes.
|
||||||
typedef void LazyBlockExtentsChangedCallback(double contentExtent, double containerExtent, double minScrollOffset);
|
/// * The `containerExtent` is the exterior dimension of the viewport (i.e.,
|
||||||
|
/// the amount of the thing inside the viewport that is visible from outside
|
||||||
|
/// the viewport).
|
||||||
|
typedef void LazyBlockExtentsChangedCallback(int firstIndex, int lastIndex, double firstStartOffset, double lastEndOffset, double minScrollOffset, double containerExtent);
|
||||||
|
|
||||||
/// A viewport on an infinite list of variable height children.
|
/// A viewport on an infinite list of variable height children.
|
||||||
///
|
///
|
||||||
@ -315,8 +384,6 @@ class LazyBlockViewport extends RenderObjectWidget {
|
|||||||
/// See [LazyBlockDelegate] for details.
|
/// See [LazyBlockDelegate] for details.
|
||||||
final LazyBlockDelegate delegate;
|
final LazyBlockDelegate delegate;
|
||||||
|
|
||||||
double get _mainAxisPadding => padding == null ? 0.0 : padding.along(mainAxis);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_LazyBlockElement createElement() => new _LazyBlockElement(this);
|
_LazyBlockElement createElement() => new _LazyBlockElement(this);
|
||||||
|
|
||||||
@ -424,21 +491,18 @@ class _LazyBlockElement extends RenderObjectElement {
|
|||||||
/// reprsented explicitly in _children.
|
/// reprsented explicitly in _children.
|
||||||
double _minScrollOffset = 0.0;
|
double _minScrollOffset = 0.0;
|
||||||
|
|
||||||
/// The maximum scroll offset used by the scroll behavior.
|
|
||||||
///
|
|
||||||
/// Not all the items between the minimum and maximum scroll offsets are
|
|
||||||
/// reprsented explicitly in _children.
|
|
||||||
double _maxScrollOffset = 0.0;
|
|
||||||
|
|
||||||
/// The smallest start offset (inclusive) that can be displayed properly with the items currently represented in [_children].
|
/// The smallest start offset (inclusive) that can be displayed properly with the items currently represented in [_children].
|
||||||
double _startOffsetLowerLimit = 0.0;
|
double _startOffsetLowerLimit = 0.0;
|
||||||
|
|
||||||
/// The largest start offset (exclusive) that can be displayed properly with the items currently represented in [_children].
|
/// The largest start offset (exclusive) that can be displayed properly with the items currently represented in [_children].
|
||||||
double _startOffsetUpperLimit = 0.0;
|
double _startOffsetUpperLimit = 0.0;
|
||||||
|
|
||||||
double _lastReportedContentExtent;
|
int _lastReportedFirstChildLogicalIndex;
|
||||||
double _lastReportedContainerExtent;
|
int _lastReportedLastChildLogicalIndex;
|
||||||
|
double _lastReportedFirstChildLogicalOffset;
|
||||||
|
double _lastReportedLastChildLogicalOffset;
|
||||||
double _lastReportedMinScrollOffset;
|
double _lastReportedMinScrollOffset;
|
||||||
|
double _lastReportedContainerExtent;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void visitChildren(ElementVisitor visitor) {
|
void visitChildren(ElementVisitor visitor) {
|
||||||
@ -485,13 +549,45 @@ class _LazyBlockElement extends RenderObjectElement {
|
|||||||
super.unmount();
|
super.unmount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _callBuilder(IndexedWidgetBuilder builder, int index, { bool requireNonNull: false }) {
|
||||||
|
Widget result;
|
||||||
|
try {
|
||||||
|
result = builder(this, index);
|
||||||
|
if (requireNonNull && result == null) {
|
||||||
|
throw new FlutterError(
|
||||||
|
'buildItem must not return null after returning non-null.\n'
|
||||||
|
'If buildItem for a LazyBlockDelegate returns a non-null widget for a given '
|
||||||
|
'index, it must return non-null widgets for every smaller index as well. The '
|
||||||
|
'buildItem function for ${widget.delegate.runtimeType} returned null for '
|
||||||
|
'index $index after having returned a non-null value for index '
|
||||||
|
'${index - 1}.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e, stack) {
|
||||||
|
FlutterError.reportError(new FlutterErrorDetails(
|
||||||
|
exception: e,
|
||||||
|
stack: stack,
|
||||||
|
library: 'widgets library',
|
||||||
|
context: 'while building items for a LazyBlock',
|
||||||
|
informationCollector: (StringBuffer information) {
|
||||||
|
information.writeln('The LazyBlock in question was:\n $this');
|
||||||
|
information.writeln('The delegate that was being used was:\n ${widget.delegate}');
|
||||||
|
information.write('The index of the offending child widget was: $index');
|
||||||
|
}
|
||||||
|
));
|
||||||
|
result = new ErrorWidget(e);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void performRebuild() {
|
void performRebuild() {
|
||||||
IndexedWidgetBuilder builder = widget.delegate.buildItem;
|
IndexedWidgetBuilder builder = widget.delegate.buildItem;
|
||||||
List<Widget> widgets = <Widget>[];
|
List<Widget> widgets = <Widget>[];
|
||||||
for (int i = 0; i < _children.length; ++i) {
|
for (int i = 0; i < _children.length; ++i) {
|
||||||
int logicalIndex = _firstChildLogicalIndex + i;
|
int logicalIndex = _firstChildLogicalIndex + i;
|
||||||
Widget childWidget = builder(this, logicalIndex);
|
Widget childWidget = _callBuilder(builder, logicalIndex);
|
||||||
if (childWidget == null)
|
if (childWidget == null)
|
||||||
break;
|
break;
|
||||||
widgets.add(new RepaintBoundary.wrap(childWidget, logicalIndex));
|
widgets.add(new RepaintBoundary.wrap(childWidget, logicalIndex));
|
||||||
@ -534,18 +630,7 @@ class _LazyBlockElement extends RenderObjectElement {
|
|||||||
currentLogicalIndex -= 1;
|
currentLogicalIndex -= 1;
|
||||||
Element newElement;
|
Element newElement;
|
||||||
owner.lockState(() {
|
owner.lockState(() {
|
||||||
// TODO(abarth): Handle exceptions from builder gracefully.
|
Widget newWidget = _callBuilder(builder, currentLogicalIndex, requireNonNull: true);
|
||||||
Widget newWidget = builder(this, currentLogicalIndex);
|
|
||||||
if (newWidget == null) {
|
|
||||||
throw new FlutterError(
|
|
||||||
'buildItem must not return null after returning non-null.\n'
|
|
||||||
'If buildItem for a LazyBlockDelegate returns a non-null widget for a given '
|
|
||||||
'index, it must return non-null widgets for every smaller index as well. The '
|
|
||||||
'buildItem function for ${widget.delegate.runtimeType} returned null for '
|
|
||||||
'index $currentLogicalIndex after having returned a non-null value for index '
|
|
||||||
'${currentLogicalIndex - 1}.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
newWidget = new RepaintBoundary.wrap(newWidget, currentLogicalIndex);
|
newWidget = new RepaintBoundary.wrap(newWidget, currentLogicalIndex);
|
||||||
newElement = inflateWidget(newWidget, null);
|
newElement = inflateWidget(newWidget, null);
|
||||||
}, building: true);
|
}, building: true);
|
||||||
@ -597,12 +682,10 @@ class _LazyBlockElement extends RenderObjectElement {
|
|||||||
if (currentLogicalOffset >= startLogicalOffset) {
|
if (currentLogicalOffset >= startLogicalOffset) {
|
||||||
// The first element is visible. We need to update our reckoning of where
|
// The first element is visible. We need to update our reckoning of where
|
||||||
// the min scroll offset is.
|
// the min scroll offset is.
|
||||||
_minScrollOffset = currentLogicalOffset;
|
|
||||||
_startOffsetLowerLimit = double.NEGATIVE_INFINITY;
|
_startOffsetLowerLimit = double.NEGATIVE_INFINITY;
|
||||||
} else {
|
} else {
|
||||||
// The first element is not visible. Ensure that we have one blockExtent
|
// The first element is not visible. Ensure that we have one blockExtent
|
||||||
// of headroom so we don't hit the min scroll offset prematurely.
|
// of headroom so we don't hit the min scroll offset prematurely.
|
||||||
_minScrollOffset = currentLogicalOffset - blockExtent;
|
|
||||||
_startOffsetLowerLimit = currentLogicalOffset;
|
_startOffsetLowerLimit = currentLogicalOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -616,8 +699,7 @@ class _LazyBlockElement extends RenderObjectElement {
|
|||||||
assert(physicalIndex == _children.length);
|
assert(physicalIndex == _children.length);
|
||||||
Element newElement;
|
Element newElement;
|
||||||
owner.lockState(() {
|
owner.lockState(() {
|
||||||
// TODO(abarth): Handle exceptions from builder gracefully.
|
Widget newWidget = _callBuilder(builder, currentLogicalIndex);
|
||||||
Widget newWidget = builder(this, currentLogicalIndex);
|
|
||||||
if (newWidget == null)
|
if (newWidget == null)
|
||||||
return;
|
return;
|
||||||
newWidget = new RepaintBoundary.wrap(newWidget, currentLogicalIndex);
|
newWidget = new RepaintBoundary.wrap(newWidget, currentLogicalIndex);
|
||||||
@ -644,14 +726,10 @@ class _LazyBlockElement extends RenderObjectElement {
|
|||||||
// we don't need.
|
// we don't need.
|
||||||
|
|
||||||
if (currentLogicalOffset < endLogicalOffset) {
|
if (currentLogicalOffset < endLogicalOffset) {
|
||||||
// The last element is visible. We need to update our reckoning of where
|
// The last element is visible. We can scroll as far as they want, there's
|
||||||
// the max scroll offset is.
|
// nothing more to paint.
|
||||||
_maxScrollOffset = currentLogicalOffset + widget._mainAxisPadding - blockExtent;
|
|
||||||
_startOffsetUpperLimit = double.INFINITY;
|
_startOffsetUpperLimit = double.INFINITY;
|
||||||
} else {
|
} else {
|
||||||
// The last element is not visible. Ensure that we have one blockExtent
|
|
||||||
// of headroom so we don't hit the max scroll offset prematurely.
|
|
||||||
_maxScrollOffset = currentLogicalOffset;
|
|
||||||
_startOffsetUpperLimit = currentLogicalOffset - blockExtent;
|
_startOffsetUpperLimit = currentLogicalOffset - blockExtent;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -684,14 +762,27 @@ class _LazyBlockElement extends RenderObjectElement {
|
|||||||
|
|
||||||
LazyBlockExtentsChangedCallback onExtentsChanged = widget.onExtentsChanged;
|
LazyBlockExtentsChangedCallback onExtentsChanged = widget.onExtentsChanged;
|
||||||
if (onExtentsChanged != null) {
|
if (onExtentsChanged != null) {
|
||||||
double contentExtent = _maxScrollOffset - _minScrollOffset + blockExtent;
|
int lastChildLogicalIndex = _firstChildLogicalIndex + _children.length - 1;
|
||||||
if (_lastReportedContentExtent != contentExtent ||
|
if (_lastReportedFirstChildLogicalIndex != _firstChildLogicalIndex ||
|
||||||
_lastReportedContainerExtent != blockExtent ||
|
_lastReportedLastChildLogicalIndex != lastChildLogicalIndex ||
|
||||||
_lastReportedMinScrollOffset != _minScrollOffset) {
|
_lastReportedFirstChildLogicalOffset != _firstChildLogicalIndex ||
|
||||||
_lastReportedContentExtent = contentExtent;
|
_lastReportedLastChildLogicalOffset != currentLogicalOffset ||
|
||||||
_lastReportedContainerExtent = blockExtent;
|
_lastReportedMinScrollOffset != _minScrollOffset ||
|
||||||
|
_lastReportedContainerExtent != blockExtent) {
|
||||||
|
_lastReportedFirstChildLogicalIndex = _firstChildLogicalIndex;
|
||||||
|
_lastReportedLastChildLogicalIndex = lastChildLogicalIndex;
|
||||||
|
_lastReportedFirstChildLogicalOffset = _firstChildLogicalOffset;
|
||||||
|
_lastReportedLastChildLogicalOffset = currentLogicalOffset;
|
||||||
_lastReportedMinScrollOffset = _minScrollOffset;
|
_lastReportedMinScrollOffset = _minScrollOffset;
|
||||||
onExtentsChanged(_lastReportedContentExtent, _lastReportedContainerExtent, _lastReportedMinScrollOffset);
|
_lastReportedContainerExtent = blockExtent;
|
||||||
|
onExtentsChanged(
|
||||||
|
_firstChildLogicalIndex,
|
||||||
|
lastChildLogicalIndex,
|
||||||
|
_firstChildLogicalOffset,
|
||||||
|
currentLogicalOffset,
|
||||||
|
_lastReportedMinScrollOffset,
|
||||||
|
_lastReportedContainerExtent
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -233,7 +233,7 @@ class OverscrollWhenScrollableBehavior extends OverscrollBehavior {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Simulation createScrollSimulation(double position, double velocity) {
|
Simulation createScrollSimulation(double position, double velocity) {
|
||||||
if (isScrollable || position < minScrollOffset || position > maxScrollOffset) {
|
if ((isScrollable && velocity.abs() > 0) || position < minScrollOffset || position > maxScrollOffset) {
|
||||||
// If the triggering gesture starts at or beyond the contentExtent's limits
|
// If the triggering gesture starts at or beyond the contentExtent's limits
|
||||||
// then the simulation only serves to settle the scrollOffset back to its
|
// then the simulation only serves to settle the scrollOffset back to its
|
||||||
// minimum or maximum value.
|
// minimum or maximum value.
|
||||||
|
@ -463,7 +463,6 @@ class ScrollableState<T extends Scrollable> extends State<T> {
|
|||||||
Future<Null> fling(double scrollVelocity) {
|
Future<Null> fling(double scrollVelocity) {
|
||||||
if (scrollVelocity.abs() > kPixelScrollTolerance.velocity || !_controller.isAnimating)
|
if (scrollVelocity.abs() > kPixelScrollTolerance.velocity || !_controller.isAnimating)
|
||||||
return _startToEndAnimation(scrollVelocity);
|
return _startToEndAnimation(scrollVelocity);
|
||||||
|
|
||||||
return new Future<Null>.value();
|
return new Future<Null>.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -524,7 +523,7 @@ class ScrollableState<T extends Scrollable> extends State<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Simulation _createFlingSimulation(double scrollVelocity) {
|
Simulation _createFlingSimulation(double scrollVelocity) {
|
||||||
final Simulation simulation = scrollBehavior.createScrollSimulation(scrollOffset, scrollVelocity);
|
final Simulation simulation = scrollBehavior.createScrollSimulation(scrollOffset, scrollVelocity);
|
||||||
if (simulation != null) {
|
if (simulation != null) {
|
||||||
final double endVelocity = pixelOffsetToScrollOffset(kPixelScrollTolerance.velocity).abs();
|
final double endVelocity = pixelOffsetToScrollOffset(kPixelScrollTolerance.velocity).abs();
|
||||||
final double endDistance = pixelOffsetToScrollOffset(kPixelScrollTolerance.distance).abs();
|
final double endDistance = pixelOffsetToScrollOffset(kPixelScrollTolerance.distance).abs();
|
||||||
|
51
packages/flutter/test/material/scrollbar_test.dart
Normal file
51
packages/flutter/test/material/scrollbar_test.dart
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// Copyright 2016 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:flutter/material.dart';
|
||||||
|
import 'package:flutter/scheduler.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('Scrollbar doesn\'t show when tapping list', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
new Center(
|
||||||
|
child: new Container(
|
||||||
|
decoration: new BoxDecoration(
|
||||||
|
border: new Border.all(color: const Color(0xFFFFFF00))
|
||||||
|
),
|
||||||
|
height: 200.0,
|
||||||
|
width: 300.0,
|
||||||
|
child: new Scrollbar(
|
||||||
|
child: new Block(
|
||||||
|
children: <Widget>[
|
||||||
|
new Container(height: 40.0, child: new Text('0')),
|
||||||
|
new Container(height: 40.0, child: new Text('1')),
|
||||||
|
new Container(height: 40.0, child: new Text('2')),
|
||||||
|
new Container(height: 40.0, child: new Text('3')),
|
||||||
|
new Container(height: 40.0, child: new Text('4')),
|
||||||
|
new Container(height: 40.0, child: new Text('5')),
|
||||||
|
new Container(height: 40.0, child: new Text('6')),
|
||||||
|
new Container(height: 40.0, child: new Text('7')),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
SchedulerBinding.instance.debugAssertNoTransientCallbacks('Building a list with a scrollbar triggered an animation.');
|
||||||
|
await tester.tap(find.byType(Block));
|
||||||
|
SchedulerBinding.instance.debugAssertNoTransientCallbacks('Tapping a block with a scrollbar triggered an animation.');
|
||||||
|
await tester.pump(const Duration(milliseconds: 200));
|
||||||
|
await tester.pump(const Duration(milliseconds: 200));
|
||||||
|
await tester.pump(const Duration(milliseconds: 200));
|
||||||
|
await tester.pump(const Duration(milliseconds: 200));
|
||||||
|
await tester.scroll(find.byType(Block), const Offset(0.0, -10.0));
|
||||||
|
expect(SchedulerBinding.instance.transientCallbackCount, greaterThan(0));
|
||||||
|
await tester.pump(const Duration(milliseconds: 200));
|
||||||
|
await tester.pump(const Duration(milliseconds: 200));
|
||||||
|
await tester.pump(const Duration(milliseconds: 200));
|
||||||
|
await tester.pump(const Duration(milliseconds: 200));
|
||||||
|
});
|
||||||
|
}
|
@ -267,13 +267,19 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Underflow extents', (WidgetTester tester) async {
|
testWidgets('Underflow extents', (WidgetTester tester) async {
|
||||||
double lastContentExtent;
|
int lastFirstIndex;
|
||||||
double lastContainerExtent;
|
int lastLastIndex;
|
||||||
|
double lastFirstStartOffset;
|
||||||
|
double lastLastEndOffset;
|
||||||
double lastMinScrollOffset;
|
double lastMinScrollOffset;
|
||||||
void handleExtendsChanged(double contentExtent, double containerExtent, double minScrollOffset) {
|
double lastContainerExtent;
|
||||||
lastContentExtent = contentExtent;
|
void handleExtendsChanged(int firstIndex, int lastIndex, double firstStartOffset, double lastEndOffset, double minScrollOffset, double containerExtent) {
|
||||||
lastContainerExtent = containerExtent;
|
lastFirstIndex = firstIndex;
|
||||||
|
lastLastIndex = lastIndex;
|
||||||
|
lastFirstStartOffset = firstStartOffset;
|
||||||
|
lastLastEndOffset = lastEndOffset;
|
||||||
lastMinScrollOffset = minScrollOffset;
|
lastMinScrollOffset = minScrollOffset;
|
||||||
|
lastContainerExtent = containerExtent;
|
||||||
}
|
}
|
||||||
|
|
||||||
await tester.pumpWidget(new LazyBlockViewport(
|
await tester.pumpWidget(new LazyBlockViewport(
|
||||||
@ -287,8 +293,11 @@ void main() {
|
|||||||
)
|
)
|
||||||
));
|
));
|
||||||
|
|
||||||
expect(lastContentExtent, equals(300.0));
|
expect(lastFirstIndex, 0);
|
||||||
expect(lastContainerExtent, equals(600.0));
|
expect(lastLastIndex, 2);
|
||||||
expect(lastMinScrollOffset, equals(0.0));
|
expect(lastFirstStartOffset, 0.0);
|
||||||
|
expect(lastLastEndOffset, 300.0);
|
||||||
|
expect(lastContainerExtent, 600.0);
|
||||||
|
expect(lastMinScrollOffset, 0.0);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ Widget buildFrame(ViewportAnchor scrollAnchor) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('Drag horizontally with scroll anchor at top', (WidgetTester tester) async {
|
testWidgets('Drag horizontally with scroll anchor at start', (WidgetTester tester) async {
|
||||||
await tester.pumpWidget(buildFrame(ViewportAnchor.start));
|
await tester.pumpWidget(buildFrame(ViewportAnchor.start));
|
||||||
|
|
||||||
await tester.pump(const Duration(seconds: 1));
|
await tester.pump(const Duration(seconds: 1));
|
||||||
|
Loading…
Reference in New Issue
Block a user