|
@@ -1,381 +0,0 @@
|
|
|
-import 'package:flutter/gestures.dart';
|
|
|
-import 'package:flutter/rendering.dart';
|
|
|
-import 'package:flutter/widgets.dart' hide PageView;
|
|
|
-
|
|
|
-/// This is copy-pasted from the Flutter framework with a support added for building
|
|
|
-/// pages off screen using [Viewport.cacheExtents] and a [LayoutBuilder]
|
|
|
-///
|
|
|
-/// Based on commit 3932ffb1cd5dfa0c3891c60977ee4f9cd70ade66 on channel dev
|
|
|
-
|
|
|
-// Having this global (mutable) page controller is a bit of a hack. We need it
|
|
|
-// to plumb in the factory for _PagePosition, but it will end up accumulating
|
|
|
-// a large list of scroll positions. As long as you don't try to actually
|
|
|
-// control the scroll positions, everything should be fine.
|
|
|
-final PageController _defaultPageController = PageController();
|
|
|
-const PageScrollPhysics _kPagePhysics = PageScrollPhysics();
|
|
|
-
|
|
|
-/// A scrollable list that works page by page.
|
|
|
-///
|
|
|
-/// Each child of a page view is forced to be the same size as the viewport.
|
|
|
-///
|
|
|
-/// You can use a [PageController] to control which page is visible in the view.
|
|
|
-/// In addition to being able to control the pixel offset of the content inside
|
|
|
-/// the [PageView], a [PageController] also lets you control the offset in terms
|
|
|
-/// of pages, which are increments of the viewport size.
|
|
|
-///
|
|
|
-/// The [PageController] can also be used to control the
|
|
|
-/// [PageController.initialPage], which determines which page is shown when the
|
|
|
-/// [PageView] is first constructed, and the [PageController.viewportFraction],
|
|
|
-/// which determines the size of the pages as a fraction of the viewport size.
|
|
|
-///
|
|
|
-/// {@youtube 560 315 https://www.youtube.com/watch?v=J1gE9xvph-A}
|
|
|
-///
|
|
|
-/// See also:
|
|
|
-///
|
|
|
-/// * [PageController], which controls which page is visible in the view.
|
|
|
-/// * [SingleChildScrollView], when you need to make a single child scrollable.
|
|
|
-/// * [ListView], for a scrollable list of boxes.
|
|
|
-/// * [GridView], for a scrollable grid of boxes.
|
|
|
-/// * [ScrollNotification] and [NotificationListener], which can be used to watch
|
|
|
-/// the scroll position without using a [ScrollController].
|
|
|
-class ExtentsPageView extends StatefulWidget {
|
|
|
- /// Creates a scrollable list that works page by page from an explicit [List]
|
|
|
- /// of widgets.
|
|
|
- ///
|
|
|
- /// This constructor is appropriate for page views with a small number of
|
|
|
- /// children because constructing the [List] requires doing work for every
|
|
|
- /// child that could possibly be displayed in the page view, instead of just
|
|
|
- /// those children that are actually visible.
|
|
|
- ExtentsPageView({
|
|
|
- Key key,
|
|
|
- this.scrollDirection = Axis.horizontal,
|
|
|
- this.reverse = false,
|
|
|
- PageController controller,
|
|
|
- this.physics,
|
|
|
- this.pageSnapping = true,
|
|
|
- this.onPageChanged,
|
|
|
- List<Widget> children = const <Widget>[],
|
|
|
- this.dragStartBehavior = DragStartBehavior.start,
|
|
|
- }) : controller = controller ?? _defaultPageController,
|
|
|
- childrenDelegate = SliverChildListDelegate(children),
|
|
|
- extents = 0,
|
|
|
- super(key: key);
|
|
|
-
|
|
|
- /// Creates a scrollable list that works page by page using widgets that are
|
|
|
- /// created on demand.
|
|
|
- ///
|
|
|
- /// This constructor is appropriate for page views with a large (or infinite)
|
|
|
- /// number of children because the builder is called only for those children
|
|
|
- /// that are actually visible.
|
|
|
- ///
|
|
|
- /// Providing a non-null [itemCount] lets the [PageView] compute the maximum
|
|
|
- /// scroll extent.
|
|
|
- ///
|
|
|
- /// [itemBuilder] will be called only with indices greater than or equal to
|
|
|
- /// zero and less than [itemCount].
|
|
|
- ///
|
|
|
- /// [PageView.builder] by default does not support child reordering. If
|
|
|
- /// you are planning to change child order at a later time, consider using
|
|
|
- /// [PageView] or [PageView.custom].
|
|
|
- ExtentsPageView.builder({
|
|
|
- Key key,
|
|
|
- this.scrollDirection = Axis.horizontal,
|
|
|
- this.reverse = false,
|
|
|
- PageController controller,
|
|
|
- this.physics,
|
|
|
- this.pageSnapping = true,
|
|
|
- this.onPageChanged,
|
|
|
- @required IndexedWidgetBuilder itemBuilder,
|
|
|
- int itemCount,
|
|
|
- this.dragStartBehavior = DragStartBehavior.start,
|
|
|
- }) : controller = controller ?? _defaultPageController,
|
|
|
- childrenDelegate =
|
|
|
- SliverChildBuilderDelegate(itemBuilder, childCount: itemCount),
|
|
|
- extents = 0,
|
|
|
- super(key: key);
|
|
|
-
|
|
|
- ExtentsPageView.extents({
|
|
|
- Key key,
|
|
|
- this.extents = 1,
|
|
|
- this.scrollDirection = Axis.horizontal,
|
|
|
- this.reverse = false,
|
|
|
- PageController controller,
|
|
|
- this.physics,
|
|
|
- this.pageSnapping = true,
|
|
|
- this.onPageChanged,
|
|
|
- @required IndexedWidgetBuilder itemBuilder,
|
|
|
- int itemCount,
|
|
|
- this.dragStartBehavior = DragStartBehavior.start,
|
|
|
- }) : controller = controller ?? _defaultPageController,
|
|
|
- childrenDelegate = SliverChildBuilderDelegate(
|
|
|
- itemBuilder,
|
|
|
- childCount: itemCount,
|
|
|
- addAutomaticKeepAlives: false,
|
|
|
- addRepaintBoundaries: false,
|
|
|
- ),
|
|
|
- super(key: key);
|
|
|
-
|
|
|
- /// Creates a scrollable list that works page by page with a custom child
|
|
|
- /// model.
|
|
|
- ///
|
|
|
- /// {@tool sample}
|
|
|
- ///
|
|
|
- /// This [PageView] uses a custom [SliverChildBuilderDelegate] to support child
|
|
|
- /// reordering.
|
|
|
- ///
|
|
|
- /// ```dart
|
|
|
- /// class MyPageView extends StatefulWidget {
|
|
|
- /// @override
|
|
|
- /// _MyPageViewState createState() => _MyPageViewState();
|
|
|
- /// }
|
|
|
- ///
|
|
|
- /// class _MyPageViewState extends State<MyPageView> {
|
|
|
- /// List<String> items = <String>['1', '2', '3', '4', '5'];
|
|
|
- ///
|
|
|
- /// void _reverse() {
|
|
|
- /// setState(() {
|
|
|
- /// items = items.reversed.toList();
|
|
|
- /// });
|
|
|
- /// }
|
|
|
- ///
|
|
|
- /// @override
|
|
|
- /// Widget build(BuildContext context) {
|
|
|
- /// return Scaffold(
|
|
|
- /// body: SafeArea(
|
|
|
- /// child: PageView.custom(
|
|
|
- /// childrenDelegate: SliverChildBuilderDelegate(
|
|
|
- /// (BuildContext context, int index) {
|
|
|
- /// return KeepAlive(
|
|
|
- /// data: items[index],
|
|
|
- /// key: ValueKey<String>(items[index]),
|
|
|
- /// );
|
|
|
- /// },
|
|
|
- /// childCount: items.length,
|
|
|
- /// findChildIndexCallback: (Key key) {
|
|
|
- /// final ValueKey valueKey = key;
|
|
|
- /// final String data = valueKey.value;
|
|
|
- /// return items.indexOf(data);
|
|
|
- /// }
|
|
|
- /// ),
|
|
|
- /// ),
|
|
|
- /// ),
|
|
|
- /// bottomNavigationBar: BottomAppBar(
|
|
|
- /// child: Row(
|
|
|
- /// mainAxisAlignment: MainAxisAlignment.center,
|
|
|
- /// children: <Widget>[
|
|
|
- /// FlatButton(
|
|
|
- /// onPressed: () => _reverse(),
|
|
|
- /// child: Text('Reverse items'),
|
|
|
- /// ),
|
|
|
- /// ],
|
|
|
- /// ),
|
|
|
- /// ),
|
|
|
- /// );
|
|
|
- /// }
|
|
|
- /// }
|
|
|
- ///
|
|
|
- /// class KeepAlive extends StatefulWidget {
|
|
|
- /// const KeepAlive({Key key, this.data}) : super(key: key);
|
|
|
- ///
|
|
|
- /// final String data;
|
|
|
- ///
|
|
|
- /// @override
|
|
|
- /// _KeepAliveState createState() => _KeepAliveState();
|
|
|
- /// }
|
|
|
- ///
|
|
|
- /// class _KeepAliveState extends State<KeepAlive> with AutomaticKeepAliveClientMixin{
|
|
|
- /// @override
|
|
|
- /// bool get wantKeepAlive => true;
|
|
|
- ///
|
|
|
- /// @override
|
|
|
- /// Widget build(BuildContext context) {
|
|
|
- /// super.build(context);
|
|
|
- /// return Text(widget.data);
|
|
|
- /// }
|
|
|
- /// }
|
|
|
- /// ```
|
|
|
- /// {@end-tool}
|
|
|
- ExtentsPageView.custom({
|
|
|
- Key key,
|
|
|
- this.scrollDirection = Axis.horizontal,
|
|
|
- this.reverse = false,
|
|
|
- PageController controller,
|
|
|
- this.physics,
|
|
|
- this.pageSnapping = true,
|
|
|
- this.onPageChanged,
|
|
|
- @required this.childrenDelegate,
|
|
|
- this.dragStartBehavior = DragStartBehavior.start,
|
|
|
- }) : assert(childrenDelegate != null),
|
|
|
- extents = 0,
|
|
|
- controller = controller ?? _defaultPageController,
|
|
|
- super(key: key);
|
|
|
-
|
|
|
- /// The number of pages to build off screen.
|
|
|
- ///
|
|
|
- /// For example, a value of `1` builds one page ahead and one page behind,
|
|
|
- /// for a total of three built pages.
|
|
|
- ///
|
|
|
- /// This is especially useful for making sure heavyweight widgets have a chance
|
|
|
- /// to load off-screen before the user pulls it into the viewport.
|
|
|
- final int extents;
|
|
|
-
|
|
|
- /// The axis along which the page view scrolls.
|
|
|
- ///
|
|
|
- /// Defaults to [Axis.horizontal].
|
|
|
- final Axis scrollDirection;
|
|
|
-
|
|
|
- /// Whether the page view scrolls in the reading direction.
|
|
|
- ///
|
|
|
- /// For example, if the reading direction is left-to-right and
|
|
|
- /// [scrollDirection] is [Axis.horizontal], then the page view scrolls from
|
|
|
- /// left to right when [reverse] is false and from right to left when
|
|
|
- /// [reverse] is true.
|
|
|
- ///
|
|
|
- /// Similarly, if [scrollDirection] is [Axis.vertical], then the page view
|
|
|
- /// scrolls from top to bottom when [reverse] is false and from bottom to top
|
|
|
- /// when [reverse] is true.
|
|
|
- ///
|
|
|
- /// Defaults to false.
|
|
|
- final bool reverse;
|
|
|
-
|
|
|
- /// An object that can be used to control the position to which this page
|
|
|
- /// view is scrolled.
|
|
|
- final PageController controller;
|
|
|
-
|
|
|
- /// How the page view should respond to user input.
|
|
|
- ///
|
|
|
- /// For example, determines how the page view continues to animate after the
|
|
|
- /// user stops dragging the page view.
|
|
|
- ///
|
|
|
- /// The physics are modified to snap to page boundaries using
|
|
|
- /// [PageScrollPhysics] prior to being used.
|
|
|
- ///
|
|
|
- /// Defaults to matching platform conventions.
|
|
|
- final ScrollPhysics physics;
|
|
|
-
|
|
|
- /// Set to false to disable page snapping, useful for custom scroll behavior.
|
|
|
- final bool pageSnapping;
|
|
|
-
|
|
|
- /// Called whenever the page in the center of the viewport changes.
|
|
|
- final ValueChanged<int> onPageChanged;
|
|
|
-
|
|
|
- /// A delegate that provides the children for the [PageView].
|
|
|
- ///
|
|
|
- /// The [PageView.custom] constructor lets you specify this delegate
|
|
|
- /// explicitly. The [PageView] and [PageView.builder] constructors create a
|
|
|
- /// [childrenDelegate] that wraps the given [List] and [IndexedWidgetBuilder],
|
|
|
- /// respectively.
|
|
|
- final SliverChildDelegate childrenDelegate;
|
|
|
-
|
|
|
- /// {@macro flutter.widgets.scrollable.dragStartBehavior}
|
|
|
- final DragStartBehavior dragStartBehavior;
|
|
|
-
|
|
|
- @override
|
|
|
- _PageViewState createState() => _PageViewState();
|
|
|
-}
|
|
|
-
|
|
|
-class _PageViewState extends State<ExtentsPageView> {
|
|
|
- int _lastReportedPage = 0;
|
|
|
-
|
|
|
- @override
|
|
|
- void initState() {
|
|
|
- super.initState();
|
|
|
- _lastReportedPage = widget.controller.initialPage;
|
|
|
- }
|
|
|
-
|
|
|
- AxisDirection _getDirection(BuildContext context) {
|
|
|
- switch (widget.scrollDirection) {
|
|
|
- case Axis.horizontal:
|
|
|
- assert(debugCheckHasDirectionality(context));
|
|
|
- final TextDirection textDirection = Directionality.of(context);
|
|
|
- final AxisDirection axisDirection =
|
|
|
- textDirectionToAxisDirection(textDirection);
|
|
|
- return widget.reverse
|
|
|
- ? flipAxisDirection(axisDirection)
|
|
|
- : axisDirection;
|
|
|
- case Axis.vertical:
|
|
|
- return widget.reverse ? AxisDirection.up : AxisDirection.down;
|
|
|
- }
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- @override
|
|
|
- Widget build(BuildContext context) {
|
|
|
- final AxisDirection axisDirection = _getDirection(context);
|
|
|
- final ScrollPhysics physics = widget.pageSnapping
|
|
|
- ? _kPagePhysics.applyTo(widget.physics)
|
|
|
- : widget.physics;
|
|
|
-
|
|
|
- return NotificationListener<ScrollNotification>(
|
|
|
- onNotification: (ScrollNotification notification) {
|
|
|
- if (notification.depth == 0 &&
|
|
|
- widget.onPageChanged != null &&
|
|
|
- notification is ScrollUpdateNotification) {
|
|
|
- final PageMetrics metrics = notification.metrics;
|
|
|
- final int currentPage = metrics.page.round();
|
|
|
- if (currentPage != _lastReportedPage) {
|
|
|
- _lastReportedPage = currentPage;
|
|
|
- widget.onPageChanged(currentPage);
|
|
|
- }
|
|
|
- }
|
|
|
- return false;
|
|
|
- },
|
|
|
- child: Scrollable(
|
|
|
- dragStartBehavior: widget.dragStartBehavior,
|
|
|
- axisDirection: axisDirection,
|
|
|
- controller: widget.controller,
|
|
|
- physics: physics,
|
|
|
- viewportBuilder: (BuildContext context, ViewportOffset position) {
|
|
|
- return LayoutBuilder(
|
|
|
- builder: (context, constraints) {
|
|
|
- assert(constraints.hasBoundedHeight);
|
|
|
- assert(constraints.hasBoundedWidth);
|
|
|
-
|
|
|
- double cacheExtent;
|
|
|
-
|
|
|
- switch (widget.scrollDirection) {
|
|
|
- case Axis.vertical:
|
|
|
- cacheExtent = constraints.maxHeight * widget.extents;
|
|
|
- break;
|
|
|
-
|
|
|
- case Axis.horizontal:
|
|
|
- default:
|
|
|
- cacheExtent = constraints.maxWidth * widget.extents;
|
|
|
- break;
|
|
|
- }
|
|
|
-
|
|
|
- return Viewport(
|
|
|
- cacheExtent: cacheExtent,
|
|
|
- axisDirection: axisDirection,
|
|
|
- offset: position,
|
|
|
- slivers: <Widget>[
|
|
|
- SliverFillViewport(
|
|
|
- viewportFraction: widget.controller.viewportFraction,
|
|
|
- delegate: widget.childrenDelegate,
|
|
|
- ),
|
|
|
- ],
|
|
|
- );
|
|
|
- },
|
|
|
- );
|
|
|
- },
|
|
|
- ),
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- @override
|
|
|
- void debugFillProperties(DiagnosticPropertiesBuilder description) {
|
|
|
- super.debugFillProperties(description);
|
|
|
- description
|
|
|
- .add(EnumProperty<Axis>('scrollDirection', widget.scrollDirection));
|
|
|
- description.add(
|
|
|
- FlagProperty('reverse', value: widget.reverse, ifTrue: 'reversed'));
|
|
|
- description.add(DiagnosticsProperty<PageController>(
|
|
|
- 'controller', widget.controller,
|
|
|
- showName: false));
|
|
|
- description.add(DiagnosticsProperty<ScrollPhysics>(
|
|
|
- 'physics', widget.physics,
|
|
|
- showName: false));
|
|
|
- description.add(FlagProperty('pageSnapping',
|
|
|
- value: widget.pageSnapping, ifFalse: 'snapping disabled'));
|
|
|
- }
|
|
|
-}
|