123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412 |
- 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,
- this.openDrawer,
- }) : controller = controller ?? _defaultPageController,
- childrenDelegate = SliverChildListDelegate(children),
- extents = children.length,
- 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,
- this.openDrawer,
- }) : 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,
- this.openDrawer,
- }) : 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
- /// _MyPageView> 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
- /// _KeepAlive> 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,
- this.openDrawer,
- }) : 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;
- final Function? openDrawer; //nullable
- @override
- State<ExtentsPageView> createState() => _PageViewState();
- }
- class _PageViewState extends State<ExtentsPageView> {
- int _lastReportedPage = 0;
- @override
- void initState() {
- super.initState();
- _lastReportedPage = widget.controller.initialPage;
- widget.openDrawer != null
- ? widget.controller.addListener(() {
- if (widget.controller.offset < -45) {
- widget.openDrawer!();
- }
- })
- : null;
- }
- @override
- void dispose() {
- widget.controller.dispose();
- super.dispose();
- }
- 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;
- }
- }
- @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 as PageMetrics;
- 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',
- ),
- );
- }
- }
|