Closed fzyzcjy closed 1 year ago
class ListView extends BoxScrollView {
@override
Widget buildChildLayout(BuildContext context) {
return SliverList(delegate: childrenDelegate);
/// A [ScrollView] that uses a single child layout model.
abstract class BoxScrollView extends ScrollView {
@override
List<Widget> buildSlivers(BuildContext context) {
Widget sliver = buildChildLayout(context);
return <Widget>[ sliver ];
}
/// Subclasses should override this method to build the layout model.
@protected
Widget buildChildLayout(BuildContext context);
/// A widget that scrolls.
///
/// Scrollable widgets consist of three pieces:
///
/// 1. A [Scrollable] widget, which listens for various user gestures and
/// implements the interaction design for scrolling.
/// 2. A viewport widget, such as [Viewport] or [ShrinkWrappingViewport], which
/// implements the visual design for scrolling by displaying only a portion
/// of the widgets inside the scroll view.
/// 3. One or more slivers, which are widgets that can be composed to created
/// various scrolling effects, such as lists, grids, and expanding headers.
///
/// [ScrollView] helps orchestrate these pieces by creating the [Scrollable] and
/// the viewport and deferring to its subclass to create the slivers.
abstract class ScrollView extends StatelessWidget {
/// Build the list of widgets to place inside the viewport.
///
/// Subclasses should override this method to build the slivers for the inside
/// of the viewport.
@protected
List<Widget> buildSlivers(BuildContext context);
Widget buildViewport(
ViewportOffset offset,
List<Widget> slivers,
) {
return Viewport();
}
Widget build(BuildContext context) {
final List<Widget> slivers = buildSlivers(context);
final Scrollable scrollable = Scrollable(
viewportBuilder: (BuildContext context, ViewportOffset offset) {
return buildViewport(context, offset, axisDirection, slivers);
},
);
return scrollable;
}
/// A widget that scrolls.
///
/// [Scrollable] implements the interaction model for a scrollable widget,
/// including gesture recognition, but does not have an opinion about how the
/// viewport, which actually displays the children, is constructed.
///
/// To further customize scrolling behavior with a [Scrollable]:
///
/// 1. You can provide a [viewportBuilder] to customize the child model. For
/// example, [SingleChildScrollView] uses a viewport that displays a single
/// box child whereas [CustomScrollView] uses a [Viewport] or a
/// [ShrinkWrappingViewport], both of which display a list of slivers.
///
/// 2. You can provide a custom [ScrollController] that creates a custom
/// [ScrollPosition] subclass. For example, [PageView] uses a
/// [PageController], which creates a page-oriented scroll position subclass
/// that keeps the same page visible when the [Scrollable] resizes.
class Scrollable extends StatefulWidget {
final ScrollController? controller;
final ScrollPhysics? physics;
final ViewportBuilder viewportBuilder;
}
class ScrollableState extends State<Scrollable> with TickerProviderStateMixin, RestorationMixin implements ScrollContext {
/// The manager for this [Scrollable] widget's viewport position.
///
/// To control what kind of [ScrollPosition] is created for a [Scrollable],
/// provide it with custom [ScrollController] that creates the appropriate
/// [ScrollPosition] in its [ScrollController.createScrollPosition] method.
ScrollPosition get position => _position!;
ScrollPosition? _position;
void setCanDrag(bool value) {
_gestureRecognizers = ...VerticalDragGestureRecognizer...
..onDown = _handleDragDown
..onStart = _handleDragStart
..onUpdate = _handleDragUpdate
..onEnd = _handleDragEnd
..onCancel = _handleDragCancel;
}
Drag? _drag;
void _handleDragStart(DragStartDetails details) => _drag = position.drag(details, _disposeDrag);
void _handleDragUpdate(DragUpdateDetails details) => _drag?.update(details);
void _handleDragEnd(DragEndDetails details) => _drag?.end(details);
Widget build(BuildContext context) {
return _ScrollableScope(
scrollable: this,
position: position,
child: RawGestureDetector(
key: _gestureDetectorKey,
gestures: _gestureRecognizers,
child: IgnorePointer(
key: _ignorePointerKey,
ignoring: _shouldIgnorePointer,
child: widget.viewportBuilder(context, position),
),
),
);
/// Interface for objects that receive updates about drags.
/// ... Similarly, the scrolling infrastructure in the widgets
/// library uses it to notify the [DragScrollActivity] when the user drags the
/// scrollable.
abstract class Drag {
void update(DragUpdateDetails details) { }
void end(DragEndDetails details) { }
void cancel() { }
}
/// Determines which portion of the content is visible in a scroll view.
///
/// The [pixels] value determines the scroll offset that the scroll view uses to
/// select which part of its content to display. As the user scrolls the
/// viewport, this value changes, which changes the content that is displayed.
///
/// The [ScrollPosition] applies [physics] to scrolling, and stores the
/// [minScrollExtent] and [maxScrollExtent].
///
/// Scrolling is controlled by the current [activity], which is set by
/// [beginActivity]. [ScrollPosition] itself does not start any activities.
/// Instead, concrete subclasses, such as [ScrollPositionWithSingleContext],
/// typically start activities in response to user input or instructions from a
/// [ScrollController].
abstract class ScrollPosition extends ViewportOffset with ScrollMetrics {
@override
double get pixels => _pixels!;
double? _pixels;
/// Start a drag activity corresponding to the given [DragStartDetails].
Drag drag(DragStartDetails details, VoidCallback dragCancelCallback);
/// Update the scroll position ([pixels]) to a given pixel value.
///
/// This should only be called by the current [ScrollActivity], either during
/// the transient callback phase or in response to user input.
double setPixels(double newPixels) {
final double overscroll = applyBoundaryConditions(newPixels); // ... about overscroll ...
_pixels = newPixels - overscroll;
}
most commonly used ScrollPosition
/// A scroll position that manages scroll activities for a single
/// [ScrollContext].
///
/// This class is a concrete subclass of [ScrollPosition] logic that handles a
/// single [ScrollContext], such as a [Scrollable]. An instance of this class
/// manages [ScrollActivity] instances, which change what content is visible in
/// the [Scrollable]'s [Viewport].
class ScrollPositionWithSingleContext extends ScrollPosition implements ScrollActivityDelegate {
Drag drag(DragStartDetails details, VoidCallback dragCancelCallback) {
final ScrollDragController drag = ScrollDragController(...);
beginActivity(DragScrollActivity(this, drag));
_currentDrag = drag;
return drag;
}
void applyUserOffset(double delta) {
updateUserScrollDirection(delta > 0.0 ? ScrollDirection.forward : ScrollDirection.reverse);
setPixels(pixels - physics.applyPhysicsToUserOffset(this, delta));
}
}
/// Base class for scrolling activities like dragging and flinging.
abstract class ScrollActivity {
ScrollActivityDelegate _delegate;
}
/// The activity a scroll view performs when the user drags their finger
/// across the screen.
class DragScrollActivity extends ScrollActivity {
}
/// A backend for a [ScrollActivity].
///
/// Used by subclasses of [ScrollActivity] to manipulate the scroll view that
/// they are acting upon.
abstract class ScrollActivityDelegate {
/// Update the scroll position to the given pixel value.
///
/// Returns the overscroll, if any. See [ScrollPosition.setPixels] for more
/// information.
double setPixels(double pixels);
/// Updates the scroll position by the given amount.
///
/// Appropriate for when the user is directly manipulating the scroll
/// position, for example by dragging the scroll view. Typically applies
/// [ScrollPhysics.applyPhysicsToUserOffset] and other transformations that
/// are appropriate for user-driving scrolling.
void applyUserOffset(double delta);
/// Terminate the current activity and start an idle activity.
void goIdle();
/// Terminate the current activity and start a ballistic activity with the
/// given velocity.
void goBallistic(double velocity);
}
/// Scrolls a scroll view as the user drags their finger across the screen.
///
/// * [DragScrollActivity], which is the activity the scroll view performs
/// while a drag is underway.
class ScrollDragController implements Drag {
ScrollActivityDelegate _delegate;
void update(DragUpdateDetails details) {
double offset = details.primaryDelta!;
...
delegate.applyUserOffset(offset);
}
}
/// Controls a scrollable widget.
///
/// A single scroll controller can
/// be used to control multiple scrollable widgets, but some operations, such
/// as reading the scroll [offset], require the controller to be used with a
/// single scrollable widget.
///
/// A scroll controller creates a [ScrollPosition] to manage the state specific
/// to an individual [Scrollable] widget.
class ScrollController extends ChangeNotifier {
/// The currently attached positions.
Iterable<ScrollPosition> get positions => _positions;
final List<ScrollPosition> _positions = <ScrollPosition>[];
void attach(ScrollPosition position);
void detach(ScrollPosition position);
ScrollPosition createScrollPosition() {
return ScrollPositionWithSingleContext(...);
}
/// A simulation models an object, in a one-dimensional space, on which particular
/// forces are being applied, and exposes:
///
/// * The object's position, [x]
/// * The object's velocity, [dx]
/// * Whether the simulation is "done", [isDone]
///
/// The [x], [dx], and [isDone] functions take a time argument which specifies
/// the time for which they are to be evaluated.
abstract class Simulation {
double x(double time);
double dx(double time);
bool isDone(double time);
}
class ClampingScrollSimulation extends Simulation {
ClampingScrollSimulation(this.position, this.velocity) {
_duration = _flingDuration(velocity);
_distance = (velocity * _duration / _initialVelocityPenetration).abs();
}
/// The position of the particle at the beginning of the simulation.
final double position;
/// The velocity at which the particle is traveling at the beginning of the
/// simulation.
final double velocity;
final double friction = 0.015;
@override
double x(double time) {
final double t = clampDouble(time / _duration, 0.0, 1.0);
return position + _distance * _flingDistancePenetration(t) * velocity.sign;
}
/// A widget that is bigger on the inside.
///
/// [Viewport] is the visual workhorse of the scrolling machinery. It displays a
/// subset of its children according to its own dimensions and the given
/// [offset]. As the offset varies, different children are visible through
/// the viewport.
///
/// [Viewport] hosts a bidirectional list of slivers, anchored on a [center]
/// sliver, which is placed at the zero scroll offset. The center widget is
/// displayed in the viewport according to the [anchor] property.
class Viewport extends MultiChildRenderObjectWidget {
/// The viewport listens to the [offset], which means you do not need to
/// rebuild this widget when the [offset] changes.
Viewport({...});
}
class RenderViewport extends RenderViewportBase<SliverPhysicalContainerParentData> {
void performLayout() {
offset.applyViewportDimension(size.height);
mainAxisExtent = size.height;
crossAxisExtent = size.width;
double correction;
int count = 0;
do {
correction = _attemptLayout(mainAxisExtent, crossAxisExtent, offset.pixels + centerOffsetAdjustment);
if (correction != 0.0) {
offset.correctBy(correction);
} else {
if (offset.applyContentDimensions(
math.min(0.0, _minScrollExtent + mainAxisExtent * anchor),
math.max(0.0, _maxScrollExtent - mainAxisExtent * (1.0 - anchor)),
)) {
break;
}
}
count += 1;
} while (count < _maxLayoutCycles);
}
double _attemptLayout(double mainAxisExtent, double crossAxisExtent, double correctedOffset) {
return layoutChildSequence(...);
}
/// A base class for render objects that are bigger on the inside.
///
/// This render object provides the shared code for render objects that host
/// [RenderSliver] render objects inside a [RenderBox]. The viewport establishes
/// an [axisDirection], which orients the sliver's coordinate system, which is
/// based on scroll offsets rather than Cartesian coordinates.
///
/// The viewport also listens to an [offset], which determines the
/// [SliverConstraints.scrollOffset] input to the sliver layout protocol.
///
/// Subclasses typically override [performLayout] and call
/// [layoutChildSequence], perhaps multiple times.
abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMixin<RenderSliver>>
extends RenderBox with ContainerRenderObjectMixin<RenderSliver, ParentDataClass>
implements RenderAbstractViewport {
/// Determines the size and position of some of the children of the viewport.
///
/// This function is the workhorse of `performLayout` implementations in
/// subclasses.
///
/// Layout starts with `child`, proceeds according to the `advance` callback,
/// and stops once `advance` returns null.
double layoutChildSequence({...}){ ... }
void paint(PaintingContext context, Offset offset) {
_paintContents(context, offset);
}
void _paintContents(PaintingContext context, Offset offset) {
for (final RenderSliver child in childrenInPaintOrder) {
if (child.geometry!.visible) {
context.paintChild(child, offset + paintOffsetOf(child));
}
}
}
/// Which part of the content inside the viewport should be visible.
///
/// The [pixels] value determines the scroll offset that the viewport uses to
/// select which part of its content to display. As the user scrolls the
/// viewport, this value changes, which changes the content that is displayed.
abstract class ViewportOffset extends ChangeNotifier {
/// The number of pixels to offset the children
double get pixels;
/// Animates [pixels] from its current value to the given value.
Future<void> animateTo();
Future<void> moveTo();
ScrollableState gesture detector, call handleDragStart, handleDragUpdate
handleDragStart
handleDragUpdate
Seems no inertia at all, when user is dragging the screen. Only if the user leave the screen (pointer up/cancel), will start doing things like ClampingScrollSimulation.
UML
https://www.processon.com/diagraming/63363e8ae401fd4f1b759f2e
(outdated) screenshot