mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Previously, when the content extent changed during a scroll interaction, we'd stop the current scroll interaction and reset the scroll offset. Now we try to continue the scroll interaction (e.g., drag, fling, or overscroll) even through the underlying scroll behavior has changed. For physics-based scroll interactions, we keep the current position and velocity and recompute the operative forces. For drag interactions, we keep the current position and continue to let the user drag the scroll offset. After this patch, we still disrupt non-physical scroll animations that are operating outside the new scroll bounds because it's not clear how we can sensibly modify them to work with the new scroll bounds.
156 lines
4.6 KiB
Dart
156 lines
4.6 KiB
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 'dart:math' as math;
|
|
|
|
import 'package:collection/collection.dart' show lowerBound;
|
|
import 'package:flutter/rendering.dart';
|
|
|
|
import 'framework.dart';
|
|
import 'scroll_behavior.dart';
|
|
import 'scrollable.dart';
|
|
import 'virtual_viewport.dart';
|
|
|
|
/// A vertically scrollable grid.
|
|
///
|
|
/// Requires that delegate places its children in row-major order.
|
|
class ScrollableGrid extends Scrollable {
|
|
ScrollableGrid({
|
|
Key key,
|
|
double initialScrollOffset,
|
|
ScrollListener onScroll,
|
|
SnapOffsetCallback snapOffsetCallback,
|
|
this.delegate,
|
|
this.children
|
|
}) : super(
|
|
key: key,
|
|
initialScrollOffset: initialScrollOffset,
|
|
// TODO(abarth): Support horizontal offsets. For horizontally scrolling
|
|
// grids. For horizontally scrolling grids, we'll probably need to use a
|
|
// delegate that places children in column-major order.
|
|
scrollDirection: Axis.vertical,
|
|
onScroll: onScroll,
|
|
snapOffsetCallback: snapOffsetCallback
|
|
);
|
|
|
|
final GridDelegate delegate;
|
|
final Iterable<Widget> children;
|
|
|
|
@override
|
|
ScrollableState createState() => new _ScrollableGridState();
|
|
}
|
|
|
|
class _ScrollableGridState extends ScrollableState<ScrollableGrid> {
|
|
@override
|
|
ScrollBehavior<double, double> createScrollBehavior() => new OverscrollBehavior();
|
|
|
|
@override
|
|
ExtentScrollBehavior get scrollBehavior => super.scrollBehavior;
|
|
|
|
void _handleExtentsChanged(double contentExtent, double containerExtent) {
|
|
setState(() {
|
|
didUpdateScrollBehavior(scrollBehavior.updateExtents(
|
|
contentExtent: contentExtent,
|
|
containerExtent: containerExtent,
|
|
scrollOffset: scrollOffset
|
|
));
|
|
});
|
|
}
|
|
|
|
@override
|
|
Widget buildContent(BuildContext context) {
|
|
return new GridViewport(
|
|
startOffset: scrollOffset,
|
|
delegate: config.delegate,
|
|
onExtentsChanged: _handleExtentsChanged,
|
|
children: config.children
|
|
);
|
|
}
|
|
}
|
|
|
|
class GridViewport extends VirtualViewportFromIterable {
|
|
GridViewport({
|
|
this.startOffset,
|
|
this.delegate,
|
|
this.onExtentsChanged,
|
|
this.children
|
|
});
|
|
|
|
@override
|
|
final double startOffset;
|
|
final GridDelegate delegate;
|
|
final ExtentsChangedCallback onExtentsChanged;
|
|
|
|
@override
|
|
final Iterable<Widget> children;
|
|
|
|
// TODO(abarth): Support horizontal grids.
|
|
Axis get mainAxis => Axis.vertical;
|
|
|
|
@override
|
|
RenderGrid createRenderObject(BuildContext context) => new RenderGrid(delegate: delegate);
|
|
|
|
@override
|
|
_GridViewportElement createElement() => new _GridViewportElement(this);
|
|
}
|
|
|
|
class _GridViewportElement extends VirtualViewportElement {
|
|
_GridViewportElement(GridViewport widget) : super(widget);
|
|
|
|
@override
|
|
GridViewport get widget => super.widget;
|
|
|
|
@override
|
|
RenderGrid get renderObject => super.renderObject;
|
|
|
|
@override
|
|
int get materializedChildBase => _materializedChildBase;
|
|
int _materializedChildBase;
|
|
|
|
@override
|
|
int get materializedChildCount => _materializedChildCount;
|
|
int _materializedChildCount;
|
|
|
|
@override
|
|
double get startOffsetBase => _startOffsetBase;
|
|
double _startOffsetBase;
|
|
|
|
@override
|
|
double get startOffsetLimit =>_startOffsetLimit;
|
|
double _startOffsetLimit;
|
|
|
|
@override
|
|
void updateRenderObject(GridViewport oldWidget) {
|
|
renderObject.delegate = widget.delegate;
|
|
super.updateRenderObject(oldWidget);
|
|
}
|
|
|
|
double _contentExtent;
|
|
double _containerExtent;
|
|
GridSpecification _specification;
|
|
|
|
@override
|
|
void layout(BoxConstraints constraints) {
|
|
_specification = renderObject.specification;
|
|
double contentExtent = _specification.gridSize.height;
|
|
double containerExtent = renderObject.size.height;
|
|
|
|
int materializedRowBase = math.max(0, lowerBound(_specification.rowOffsets, widget.startOffset) - 1);
|
|
int materializedRowLimit = math.min(_specification.rowCount, lowerBound(_specification.rowOffsets, widget.startOffset + containerExtent));
|
|
|
|
_materializedChildBase = (materializedRowBase * _specification.columnCount).clamp(0, renderObject.virtualChildCount);
|
|
_materializedChildCount = (materializedRowLimit * _specification.columnCount).clamp(0, renderObject.virtualChildCount) - _materializedChildBase;
|
|
_startOffsetBase = _specification.rowOffsets[materializedRowBase];
|
|
_startOffsetLimit = _specification.rowOffsets[materializedRowLimit] - containerExtent;
|
|
|
|
super.layout(constraints);
|
|
|
|
if (contentExtent != _contentExtent || containerExtent != _containerExtent) {
|
|
_contentExtent = contentExtent;
|
|
_containerExtent = containerExtent;
|
|
widget.onExtentsChanged(_contentExtent, _containerExtent);
|
|
}
|
|
}
|
|
}
|