mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-16 23:26:14 +08:00
445 lines
16 KiB
Dart
445 lines
16 KiB
Dart
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
// ignore_for_file: uri_does_not_exist_in_doc_import
|
|
|
|
/// @docImport 'package:flutter/material.dart';
|
|
///
|
|
/// @docImport 'single_child_scroll_view.dart';
|
|
/// @docImport 'text.dart';
|
|
library;
|
|
|
|
import 'package:PiliPlus/common/widgets/page/scrollable.dart';
|
|
import 'package:flutter/gestures.dart' show DragStartBehavior;
|
|
import 'package:flutter/material.dart' hide Scrollable, ScrollableState;
|
|
import 'package:flutter/rendering.dart';
|
|
|
|
class _ForceImplicitScrollPhysics extends ScrollPhysics {
|
|
const _ForceImplicitScrollPhysics(
|
|
{required this.allowImplicitScrolling, super.parent});
|
|
|
|
@override
|
|
_ForceImplicitScrollPhysics applyTo(ScrollPhysics? ancestor) {
|
|
return _ForceImplicitScrollPhysics(
|
|
allowImplicitScrolling: allowImplicitScrolling,
|
|
parent: buildParent(ancestor),
|
|
);
|
|
}
|
|
|
|
@override
|
|
final bool allowImplicitScrolling;
|
|
}
|
|
|
|
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 [CustomPageView], 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
|
|
/// [CustomPageView] 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}
|
|
///
|
|
/// {@tool dartpad}
|
|
/// Here is an example of [CustomPageView]. It creates a centered [Text] in each of the three pages
|
|
/// which scroll horizontally.
|
|
///
|
|
/// ** See code in examples/api/lib/widgets/page_view/page_view.0.dart **
|
|
/// {@end-tool}
|
|
///
|
|
/// ## Persisting the scroll position during a session
|
|
///
|
|
/// Scroll views attempt to persist their scroll position using [PageStorage].
|
|
/// For a [CustomPageView], this can be disabled by setting [PageController.keepPage]
|
|
/// to false on the [controller]. If it is enabled, using a [PageStorageKey] for
|
|
/// the [key] of this widget is recommended to help disambiguate different
|
|
/// scroll views from each other.
|
|
///
|
|
/// 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 CustomPageView 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.
|
|
///
|
|
/// Like other widgets in the framework, this widget expects that
|
|
/// the [children] list will not be mutated after it has been passed in here.
|
|
/// See the documentation at [SliverChildListDelegate.children] for more details.
|
|
///
|
|
/// {@template flutter.widgets.PageView.allowImplicitScrolling}
|
|
/// If [allowImplicitScrolling] is true, the [CustomPageView] will participate in
|
|
/// accessibility scrolling more like a [ListView], where implicit scroll
|
|
/// actions will move to the next page rather than into the contents of the
|
|
/// [CustomPageView].
|
|
/// {@endtemplate}
|
|
CustomPageView({
|
|
super.key,
|
|
this.scrollDirection = Axis.horizontal,
|
|
this.reverse = false,
|
|
this.controller,
|
|
this.physics,
|
|
this.pageSnapping = true,
|
|
this.onPageChanged,
|
|
List<Widget> children = const <Widget>[],
|
|
this.dragStartBehavior = DragStartBehavior.start,
|
|
this.allowImplicitScrolling = false,
|
|
this.restorationId,
|
|
this.clipBehavior = Clip.hardEdge,
|
|
this.hitTestBehavior = HitTestBehavior.opaque,
|
|
this.scrollBehavior,
|
|
this.padEnds = true,
|
|
this.header,
|
|
this.bgColor = Colors.transparent,
|
|
}) : childrenDelegate = SliverChildListDelegate(children);
|
|
|
|
final Widget? header;
|
|
final Color bgColor;
|
|
|
|
/// 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 [CustomPageView] compute the maximum
|
|
/// scroll extent.
|
|
///
|
|
/// [itemBuilder] will be called only with indices greater than or equal to
|
|
/// zero and less than [itemCount].
|
|
///
|
|
/// {@macro flutter.widgets.ListView.builder.itemBuilder}
|
|
///
|
|
/// {@template flutter.widgets.PageView.findChildIndexCallback}
|
|
/// The [findChildIndexCallback] corresponds to the
|
|
/// [SliverChildBuilderDelegate.findChildIndexCallback] property. If null,
|
|
/// a child widget may not map to its existing [RenderObject] when the order
|
|
/// of children returned from the children builder changes.
|
|
/// This may result in state-loss. This callback needs to be implemented if
|
|
/// the order of the children may change at a later time.
|
|
/// {@endtemplate}
|
|
///
|
|
/// {@macro flutter.widgets.PageView.allowImplicitScrolling}
|
|
CustomPageView.builder({
|
|
super.key,
|
|
this.scrollDirection = Axis.horizontal,
|
|
this.reverse = false,
|
|
this.controller,
|
|
this.physics,
|
|
this.pageSnapping = true,
|
|
this.onPageChanged,
|
|
required NullableIndexedWidgetBuilder itemBuilder,
|
|
ChildIndexGetter? findChildIndexCallback,
|
|
int? itemCount,
|
|
this.dragStartBehavior = DragStartBehavior.start,
|
|
this.allowImplicitScrolling = false,
|
|
this.restorationId,
|
|
this.clipBehavior = Clip.hardEdge,
|
|
this.hitTestBehavior = HitTestBehavior.opaque,
|
|
this.scrollBehavior,
|
|
this.padEnds = true,
|
|
this.header,
|
|
this.bgColor = Colors.transparent,
|
|
}) : childrenDelegate = SliverChildBuilderDelegate(
|
|
itemBuilder,
|
|
findChildIndexCallback: findChildIndexCallback,
|
|
childCount: itemCount,
|
|
);
|
|
|
|
/// Creates a scrollable list that works page by page with a custom child
|
|
/// model.
|
|
///
|
|
/// {@tool dartpad}
|
|
/// This example shows a [CustomPageView] that uses a custom [SliverChildBuilderDelegate] to support child
|
|
/// reordering.
|
|
///
|
|
/// ** See code in examples/api/lib/widgets/page_view/page_view.1.dart **
|
|
/// {@end-tool}
|
|
///
|
|
/// {@macro flutter.widgets.PageView.allowImplicitScrolling}
|
|
const CustomPageView.custom({
|
|
super.key,
|
|
this.scrollDirection = Axis.horizontal,
|
|
this.reverse = false,
|
|
this.controller,
|
|
this.physics,
|
|
this.pageSnapping = true,
|
|
this.onPageChanged,
|
|
required this.childrenDelegate,
|
|
this.dragStartBehavior = DragStartBehavior.start,
|
|
this.allowImplicitScrolling = false,
|
|
this.restorationId,
|
|
this.clipBehavior = Clip.hardEdge,
|
|
this.hitTestBehavior = HitTestBehavior.opaque,
|
|
this.scrollBehavior,
|
|
this.padEnds = true,
|
|
this.header,
|
|
this.bgColor = Colors.transparent,
|
|
});
|
|
|
|
/// Controls whether the widget's pages will respond to
|
|
/// [RenderObject.showOnScreen], which will allow for implicit accessibility
|
|
/// scrolling.
|
|
///
|
|
/// With this flag set to false, when accessibility focus reaches the end of
|
|
/// the current page and the user attempts to move it to the next element, the
|
|
/// focus will traverse to the next widget outside of the page view.
|
|
///
|
|
/// With this flag set to true, when accessibility focus reaches the end of
|
|
/// the current page and user attempts to move it to the next element, focus
|
|
/// will traverse to the next page in the page view.
|
|
final bool allowImplicitScrolling;
|
|
|
|
/// {@macro flutter.widgets.scrollable.restorationId}
|
|
final String? restorationId;
|
|
|
|
/// The [Axis] along which the scroll view's offset increases with each page.
|
|
///
|
|
/// For the direction in which active scrolling may be occurring, see
|
|
/// [ScrollDirection].
|
|
///
|
|
/// 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.
|
|
///
|
|
/// If an explicit [ScrollBehavior] is provided to [scrollBehavior], the
|
|
/// [ScrollPhysics] provided by that behavior will take precedence after
|
|
/// [physics].
|
|
///
|
|
/// Defaults to matching platform conventions.
|
|
final ScrollPhysics? physics;
|
|
|
|
/// Set to false to disable page snapping, useful for custom scroll behavior.
|
|
///
|
|
/// If the [padEnds] is false and [PageController.viewportFraction] < 1.0,
|
|
/// the page will snap to the beginning of the viewport; otherwise, the page
|
|
/// will snap to the center of the viewport.
|
|
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 [CustomPageView].
|
|
///
|
|
/// The [PageView.custom] constructor lets you specify this delegate
|
|
/// explicitly. The [CustomPageView] 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;
|
|
|
|
/// {@macro flutter.material.Material.clipBehavior}
|
|
///
|
|
/// Defaults to [Clip.hardEdge].
|
|
final Clip clipBehavior;
|
|
|
|
/// {@macro flutter.widgets.scrollable.hitTestBehavior}
|
|
///
|
|
/// Defaults to [HitTestBehavior.opaque].
|
|
final HitTestBehavior hitTestBehavior;
|
|
|
|
/// {@macro flutter.widgets.scrollable.scrollBehavior}
|
|
///
|
|
/// The [ScrollBehavior] of the inherited [ScrollConfiguration] will be
|
|
/// modified by default to not apply a [Scrollbar].
|
|
final ScrollBehavior? scrollBehavior;
|
|
|
|
/// Whether to add padding to both ends of the list.
|
|
///
|
|
/// If this is set to true and [PageController.viewportFraction] < 1.0, padding will be added
|
|
/// such that the first and last child slivers will be in the center of
|
|
/// the viewport when scrolled all the way to the start or end, respectively.
|
|
///
|
|
/// If [PageController.viewportFraction] >= 1.0, this property has no effect.
|
|
///
|
|
/// This property defaults to true.
|
|
final bool padEnds;
|
|
|
|
@override
|
|
State<CustomPageView> createState() => _CustomPageViewState();
|
|
}
|
|
|
|
class _CustomPageViewState extends State<CustomPageView> {
|
|
int _lastReportedPage = 0;
|
|
|
|
late PageController _controller;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_initController();
|
|
_lastReportedPage = _controller.initialPage;
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
if (widget.controller == null) {
|
|
_controller.dispose();
|
|
}
|
|
super.dispose();
|
|
}
|
|
|
|
void _initController() {
|
|
_controller = widget.controller ?? PageController();
|
|
}
|
|
|
|
@override
|
|
void didUpdateWidget(CustomPageView oldWidget) {
|
|
if (oldWidget.controller != widget.controller) {
|
|
if (oldWidget.controller == null) {
|
|
_controller.dispose();
|
|
}
|
|
_initController();
|
|
}
|
|
super.didUpdateWidget(oldWidget);
|
|
}
|
|
|
|
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 = _ForceImplicitScrollPhysics(
|
|
allowImplicitScrolling: widget.allowImplicitScrolling,
|
|
).applyTo(
|
|
widget.pageSnapping
|
|
? _kPagePhysics.applyTo(
|
|
widget.physics ??
|
|
widget.scrollBehavior?.getScrollPhysics(context),
|
|
)
|
|
: widget.physics ?? widget.scrollBehavior?.getScrollPhysics(context),
|
|
);
|
|
|
|
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: CustomScrollable(
|
|
header: widget.header,
|
|
bgColor: widget.bgColor,
|
|
dragStartBehavior: widget.dragStartBehavior,
|
|
axisDirection: axisDirection,
|
|
controller: _controller,
|
|
physics: physics,
|
|
restorationId: widget.restorationId,
|
|
hitTestBehavior: widget.hitTestBehavior,
|
|
scrollBehavior: widget.scrollBehavior ??
|
|
ScrollConfiguration.of(context).copyWith(scrollbars: false),
|
|
viewportBuilder: (BuildContext context, ViewportOffset position) {
|
|
return Viewport(
|
|
// TODO(dnfield): we should provide a way to set cacheExtent
|
|
// independent of implicit scrolling:
|
|
// https://github.com/flutter/flutter/issues/45632
|
|
cacheExtent: widget.allowImplicitScrolling ? 1.0 : 0.0,
|
|
cacheExtentStyle: CacheExtentStyle.viewport,
|
|
axisDirection: axisDirection,
|
|
offset: position,
|
|
clipBehavior: widget.clipBehavior,
|
|
slivers: <Widget>[
|
|
SliverFillViewport(
|
|
viewportFraction: _controller.viewportFraction,
|
|
delegate: widget.childrenDelegate,
|
|
padEnds: widget.padEnds,
|
|
),
|
|
],
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
void debugFillProperties(DiagnosticPropertiesBuilder description) {
|
|
super.debugFillProperties(description);
|
|
description
|
|
..add(EnumProperty<Axis>('scrollDirection', widget.scrollDirection))
|
|
..add(FlagProperty('reverse', value: widget.reverse, ifTrue: 'reversed'))
|
|
..add(
|
|
DiagnosticsProperty<PageController>('controller', _controller,
|
|
showName: false),
|
|
)
|
|
..add(DiagnosticsProperty<ScrollPhysics>('physics', widget.physics,
|
|
showName: false))
|
|
..add(
|
|
FlagProperty('pageSnapping',
|
|
value: widget.pageSnapping, ifFalse: 'snapping disabled'),
|
|
)
|
|
..add(
|
|
FlagProperty(
|
|
'allowImplicitScrolling',
|
|
value: widget.allowImplicitScrolling,
|
|
ifTrue: 'allow implicit scrolling',
|
|
),
|
|
);
|
|
}
|
|
}
|