Commit 7458ed22 authored by Adam Barth's avatar Adam Barth

Merge pull request #1710 from abarth/scroll_anchor

Preliminaries for scroll anchoring
parents f1cc388f 3c8cbef9
......@@ -777,7 +777,7 @@ class _TabBarState<T> extends ScrollableState<TabBar<T>> implements TabBarSelect
onSizeChanged: _handleViewportSizeChanged,
child: new Viewport(
scrollDirection: Axis.horizontal,
scrollOffset: new Offset(scrollOffset, 0.0),
paintOffset: scrollOffsetToPixelDelta(scrollOffset),
child: contents
)
);
......@@ -929,12 +929,12 @@ class _TabBarViewState extends PageableListState<TabBarView> implements TabBarSe
controller.value = scrollOffset / 2.0;
}
Future fling(Offset scrollVelocity) {
Future fling(double scrollVelocity) {
if (_selection == null || _selection.valueIsChanging)
return new Future.value();
if (scrollVelocity.dx.abs() > _kMinFlingVelocity) {
final int selectionDelta = scrollVelocity.dx > 0 ? -1 : 1;
if (scrollVelocity.abs() > _kMinFlingVelocity) {
final int selectionDelta = scrollVelocity.sign.truncate();
final int targetIndex = (_selection.index + selectionDelta).clamp(0, _tabCount - 1);
_selection.value = _selection.values[targetIndex];
return new Future.value();
......
......@@ -314,10 +314,12 @@ class RenderGrid extends RenderVirtualViewport<GridParentData> {
int virtualChildBase: 0,
int virtualChildCount,
Offset paintOffset: Offset.zero,
Painter overlayPainter,
LayoutCallback callback
}) : _delegate = delegate, _virtualChildBase = virtualChildBase, super(
virtualChildCount: virtualChildCount,
paintOffset: paintOffset,
overlayPainter: overlayPainter,
callback: callback
) {
assert(delegate != null);
......@@ -338,6 +340,15 @@ class RenderGrid extends RenderVirtualViewport<GridParentData> {
_delegate = newDelegate;
}
void set scrollDirection(Axis value) {
assert(() {
if (value != Axis.vertical)
throw new RenderingError('RenderGrid doesn\'t yet support horizontal scrolling.');
return true;
});
super.scrollDirection = value;
}
int get virtualChildCount => super.virtualChildCount ?? childCount;
/// The virtual index of the first child.
......
......@@ -11,7 +11,7 @@ import 'viewport.dart';
/// Parent data for use with [RenderList].
class ListParentData extends ContainerBoxParentDataMixin<RenderBox> { }
class RenderList extends RenderVirtualViewport<ListParentData> implements HasScrollDirection {
class RenderList extends RenderVirtualViewport<ListParentData> {
RenderList({
List<RenderBox> children,
double itemExtent,
......@@ -19,13 +19,15 @@ class RenderList extends RenderVirtualViewport<ListParentData> implements HasScr
int virtualChildCount,
Offset paintOffset: Offset.zero,
Axis scrollDirection: Axis.vertical,
Painter overlayPainter,
LayoutCallback callback
}) : _itemExtent = itemExtent,
_padding = padding,
_scrollDirection = scrollDirection,
super(
virtualChildCount: virtualChildCount,
paintOffset: paintOffset,
scrollDirection: scrollDirection,
overlayPainter: overlayPainter,
callback: callback
) {
addAll(children);
......@@ -50,15 +52,6 @@ class RenderList extends RenderVirtualViewport<ListParentData> implements HasScr
markNeedsLayout();
}
Axis get scrollDirection => _scrollDirection;
Axis _scrollDirection;
void set scrollDirection (Axis newValue) {
if (_scrollDirection == newValue)
return;
_scrollDirection = newValue;
markNeedsLayout();
}
void setupParentData(RenderBox child) {
if (child.parentData is! ListParentData)
child.parentData = new ListParentData();
......
......@@ -797,11 +797,12 @@ class Viewport extends OneChildRenderObjectWidget {
Viewport({
Key key,
this.scrollDirection: Axis.vertical,
this.scrollOffset: Offset.zero,
this.paintOffset: Offset.zero,
this.overlayPainter,
Widget child
}) : super(key: key, child: child) {
assert(scrollDirection != null);
assert(scrollOffset != null);
assert(paintOffset != null);
}
/// The direction in which the child is permitted to be larger than the viewport
......@@ -814,14 +815,25 @@ class Viewport extends OneChildRenderObjectWidget {
/// The offset at which to paint the child.
///
/// The offset can be non-zero only in the [scrollDirection].
final Offset scrollOffset;
final Offset paintOffset;
RenderViewport createRenderObject() => new RenderViewport(scrollDirection: scrollDirection, scrollOffset: scrollOffset);
/// Paints an overlay over the viewport.
///
/// Often used to paint scroll bars.
final Painter overlayPainter;
RenderViewport createRenderObject() => new RenderViewport(
scrollDirection: scrollDirection,
paintOffset: paintOffset,
overlayPainter: overlayPainter
);
void updateRenderObject(RenderViewport renderObject, Viewport oldWidget) {
// Order dependency: RenderViewport validates scrollOffset based on scrollDirection.
renderObject.scrollDirection = scrollDirection;
renderObject.scrollOffset = scrollOffset;
renderObject
..scrollDirection = scrollDirection
..paintOffset = paintOffset
..overlayPainter = overlayPainter;
}
}
......
......@@ -5,7 +5,6 @@
import 'dart:async';
import 'package:flutter/animation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'basic.dart';
......@@ -65,12 +64,25 @@ class PageableListState<T extends PageableList> extends ScrollableState<T> {
int get itemCount => config.children?.length ?? 0;
int _previousItemCount;
double pixelToScrollOffset(double value) {
double get _pixelsPerScrollUnit {
final RenderBox box = context.findRenderObject();
if (box == null || !box.hasSize)
return 0.0;
final double pixelScrollExtent = config.scrollDirection == Axis.vertical ? box.size.height : box.size.width;
return pixelScrollExtent == 0.0 ? 0.0 : value / pixelScrollExtent;
switch (config.scrollDirection) {
case Axis.horizontal:
return box.size.width;
case Axis.vertical:
return box.size.height;
}
}
double pixelOffsetToScrollOffset(double pixelOffset) {
final double pixelsPerScrollUnit = _pixelsPerScrollUnit;
return super.pixelOffsetToScrollOffset(pixelsPerScrollUnit == 0.0 ? 0.0 : pixelOffset / pixelsPerScrollUnit);
}
double scrollOffsetToPixelOffset(double scrollOffset) {
return super.scrollOffsetToPixelOffset(scrollOffset * _pixelsPerScrollUnit);
}
void initState() {
......@@ -143,7 +155,7 @@ class PageableListState<T extends PageableList> extends ScrollableState<T> {
ScrollBehavior createScrollBehavior() => scrollBehavior;
bool get snapScrollOffsetChanges => config.itemsSnapAlignment == ItemsSnapAlignment.item;
bool get shouldSnapScrollOffset => config.itemsSnapAlignment == ItemsSnapAlignment.item;
double snapScrollOffset(double newScrollOffset) {
final double previousItemOffset = newScrollOffset.floorToDouble();
......@@ -152,20 +164,19 @@ class PageableListState<T extends PageableList> extends ScrollableState<T> {
.clamp(scrollBehavior.minScrollOffset, scrollBehavior.maxScrollOffset);
}
Future _flingToAdjacentItem(Offset velocity) {
final double scrollVelocity = scrollDirectionVelocity(velocity);
Future _flingToAdjacentItem(double scrollVelocity) {
final double newScrollOffset = snapScrollOffset(scrollOffset + scrollVelocity.sign)
.clamp(snapScrollOffset(scrollOffset - 0.5), snapScrollOffset(scrollOffset + 0.5));
return scrollTo(newScrollOffset, duration: config.duration, curve: config.curve)
.then(_notifyPageChanged);
}
Future fling(Offset velocity) {
Future fling(double scrollVelocity) {
switch(config.itemsSnapAlignment) {
case ItemsSnapAlignment.adjacentItem:
return _flingToAdjacentItem(velocity);
return _flingToAdjacentItem(scrollVelocity);
default:
return super.fling(velocity).then(_notifyPageChanged);
return super.fling(scrollVelocity).then(_notifyPageChanged);
}
}
......
......@@ -24,7 +24,7 @@ void main() {
),
child: size);
RenderViewport viewport = new RenderViewport(child: red, scrollOffset: new Offset(0.0, -10.0));
RenderViewport viewport = new RenderViewport(child: red, paintOffset: new Offset(0.0, 10.0));
layout(viewport);
HitTestResult result;
......
......@@ -48,14 +48,11 @@ void set scrollOffset(double value) {
}
Future fling(double velocity) {
Offset velocityOffset = scrollDirection == Axis.vertical
? new Offset(0.0, velocity)
: new Offset(velocity, 0.0);
return scrollableState.fling(velocityOffset);
return scrollableState.fling(velocity);
}
void main() {
test('ScrollableList snap scrolling, fling(-0.8)', () {
test('ScrollableList snap scrolling, fling(0.8)', () {
testWidgets((WidgetTester tester) {
tester.pumpWidget(buildFrame());
......@@ -65,7 +62,7 @@ void main() {
Duration dt = const Duration(seconds: 2);
fling(-0.8);
fling(0.8);
tester.pump(); // Start the scheduler at 0.0
tester.pump(dt);
expect(scrollOffset, closeTo(200.0, 1.0));
......@@ -74,7 +71,7 @@ void main() {
tester.pump();
expect(scrollOffset, 0.0);
fling(-2.0);
fling(2.0);
tester.pump();
tester.pump(dt);
expect(scrollOffset, closeTo(400.0, 1.0));
......@@ -83,7 +80,7 @@ void main() {
tester.pump();
expect(scrollOffset, 400.0);
fling(0.8);
fling(-0.8);
tester.pump();
tester.pump(dt);
expect(scrollOffset, closeTo(0.0, 1.0));
......@@ -92,7 +89,7 @@ void main() {
tester.pump();
expect(scrollOffset, 800.0);
fling(2.0);
fling(-2.0);
tester.pump();
tester.pump(dt);
expect(scrollOffset, closeTo(200.0, 1.0));
......@@ -102,7 +99,7 @@ void main() {
expect(scrollOffset, 800.0);
bool completed = false;
fling(2.0).then((_) {
fling(-2.0).then((_) {
completed = true;
expect(scrollOffset, closeTo(200.0, 1.0));
});
......
......@@ -298,6 +298,7 @@ class AnalyzeCommand extends FlutterCommand {
new RegExp(r'\[lint\] Prefer using lowerCamelCase for constant names.'), // sometimes we have no choice (e.g. when matching other platforms)
new RegExp(r'\[lint\] Avoid defining a one-member abstract class when a simple function will do.'), // too many false-positives; code review should catch real instances
new RegExp(r'\[info\] TODO.+'),
new RegExp('\\[warning\\] Missing concrete implementation of \'RenderObject\\.applyPaintTransform\''), // https://github.com/dart-lang/sdk/issues/25232
new RegExp(r'[0-9]+ (error|warning|hint|lint).+found\.'),
new RegExp(r'^$'),
];
......
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