mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-08-02 00:36:20 +08:00
opt scrollable gesture
Signed-off-by: dom <githubaccount56556@proton.me>
This commit is contained in:
@@ -1,524 +0,0 @@
|
||||
// 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: prefer_initializing_formals
|
||||
|
||||
import 'package:PiliPlus/common/widgets/flutter/page/scrollable.dart';
|
||||
import 'package:flutter/gestures.dart'
|
||||
show DragStartBehavior, HorizontalDragGestureRecognizer;
|
||||
import 'package:flutter/material.dart'
|
||||
hide PageView, 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 [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}
|
||||
///
|
||||
/// {@tool dartpad}
|
||||
/// Here is an example of [PageView]. 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 [PageView], 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 PageView<T extends HorizontalDragGestureRecognizer>
|
||||
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 [PageView] 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
|
||||
/// [PageView].
|
||||
/// {@endtemplate}
|
||||
PageView({
|
||||
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,
|
||||
ScrollCacheExtent? scrollCacheExtent,
|
||||
this.restorationId,
|
||||
this.clipBehavior = Clip.hardEdge,
|
||||
this.hitTestBehavior = HitTestBehavior.opaque,
|
||||
this.scrollBehavior,
|
||||
this.padEnds = true,
|
||||
required this.horizontalDragGestureRecognizer,
|
||||
}) : assert(
|
||||
scrollCacheExtent == null ||
|
||||
(scrollCacheExtent.value > 0.0) == allowImplicitScrolling,
|
||||
'scrollCacheExtent and allowImplicitScrolling must be consistent: '
|
||||
'scrollCacheExtent must be greater than 0.0 when allowImplicitScrolling is true, '
|
||||
'and must be 0.0 when allowImplicitScrolling is false.',
|
||||
),
|
||||
scrollCacheExtent =
|
||||
scrollCacheExtent ??
|
||||
ScrollCacheExtent.viewport(allowImplicitScrolling ? 1.0 : 0.0),
|
||||
childrenDelegate = SliverChildListDelegate(children);
|
||||
|
||||
final GestureRecognizerFactoryConstructor<T> horizontalDragGestureRecognizer;
|
||||
|
||||
/// 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].
|
||||
///
|
||||
/// {@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}
|
||||
PageView.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,
|
||||
ScrollCacheExtent? scrollCacheExtent,
|
||||
this.restorationId,
|
||||
this.clipBehavior = Clip.hardEdge,
|
||||
this.hitTestBehavior = HitTestBehavior.opaque,
|
||||
this.scrollBehavior,
|
||||
this.padEnds = true,
|
||||
required this.horizontalDragGestureRecognizer,
|
||||
}) : assert(
|
||||
scrollCacheExtent == null ||
|
||||
(scrollCacheExtent.value > 0.0) == allowImplicitScrolling,
|
||||
'scrollCacheExtent and allowImplicitScrolling must be consistent: '
|
||||
'scrollCacheExtent must be greater than 0.0 when allowImplicitScrolling is true, '
|
||||
'and must be 0.0 when allowImplicitScrolling is false.',
|
||||
),
|
||||
scrollCacheExtent =
|
||||
scrollCacheExtent ??
|
||||
ScrollCacheExtent.viewport(allowImplicitScrolling ? 1.0 : 0.0),
|
||||
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 [PageView] 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}
|
||||
PageView.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,
|
||||
ScrollCacheExtent? scrollCacheExtent,
|
||||
this.restorationId,
|
||||
this.clipBehavior = Clip.hardEdge,
|
||||
this.hitTestBehavior = HitTestBehavior.opaque,
|
||||
this.scrollBehavior,
|
||||
this.padEnds = true,
|
||||
required this.horizontalDragGestureRecognizer,
|
||||
}) : assert(
|
||||
scrollCacheExtent == null ||
|
||||
(scrollCacheExtent.value > 0.0) == allowImplicitScrolling,
|
||||
'scrollCacheExtent and allowImplicitScrolling must be consistent: '
|
||||
'scrollCacheExtent must be greater than 0.0 when allowImplicitScrolling is true, '
|
||||
'and must be 0.0 when allowImplicitScrolling is false.',
|
||||
),
|
||||
scrollCacheExtent =
|
||||
scrollCacheExtent ??
|
||||
ScrollCacheExtent.viewport(allowImplicitScrolling ? 1.0 : 0.0);
|
||||
|
||||
/// {@template flutter.widgets.PageView.allowImplicitScrolling}
|
||||
/// 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.
|
||||
/// {@endtemplate}
|
||||
final bool allowImplicitScrolling;
|
||||
|
||||
/// {@macro flutter.rendering.RenderViewportBase.scrollCacheExtent}
|
||||
///
|
||||
/// In [PageView], the default [scrollCacheExtent] uses
|
||||
/// [ScrollCacheExtent.viewport], where the value represents the number of
|
||||
/// viewport lengths to cache beyond the visible area.
|
||||
///
|
||||
/// When [PageController.viewportFraction] is 1.0 (the default), this is
|
||||
/// equivalent to the number of pages. For example,
|
||||
/// `ScrollCacheExtent.viewport(2.0)` caches 2 pages before and after the
|
||||
/// visible page.
|
||||
///
|
||||
/// When [PageController.viewportFraction] is less than 1.0, multiple pages
|
||||
/// may be visible in a single viewport, so `ScrollCacheExtent.viewport(1.0)`
|
||||
/// may cache more than one additional page in each direction.
|
||||
///
|
||||
/// [ScrollCacheExtent.pixels] can also be used to specify the cache extent
|
||||
/// in logical pixels instead of viewport sizes.
|
||||
///
|
||||
/// If [scrollCacheExtent] is specified, its value must be consistent with
|
||||
/// [allowImplicitScrolling]: the value must be greater than 0.0 when
|
||||
/// [allowImplicitScrolling] is true, and must be 0.0 when
|
||||
/// [allowImplicitScrolling] is false.
|
||||
///
|
||||
/// Defaults to `ScrollCacheExtent.viewport(1.0)` if
|
||||
/// [allowImplicitScrolling] is true, and `ScrollCacheExtent.viewport(0.0)` if
|
||||
/// [allowImplicitScrolling] is false.
|
||||
final ScrollCacheExtent scrollCacheExtent;
|
||||
|
||||
/// {@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 [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;
|
||||
|
||||
/// {@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<PageView<T>> createState() => _PageViewState<T>();
|
||||
}
|
||||
|
||||
class _PageViewState<T extends HorizontalDragGestureRecognizer>
|
||||
extends State<PageView<T>> {
|
||||
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(PageView<T> 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),
|
||||
);
|
||||
|
||||
final child = Scrollable<T>(
|
||||
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(
|
||||
scrollCacheExtent: widget.scrollCacheExtent,
|
||||
axisDirection: axisDirection,
|
||||
offset: position,
|
||||
clipBehavior: widget.clipBehavior,
|
||||
slivers: <Widget>[
|
||||
SliverFillViewport(
|
||||
viewportFraction: _controller.viewportFraction,
|
||||
delegate: widget.childrenDelegate,
|
||||
padEnds: widget.padEnds,
|
||||
allowImplicitScrolling: widget.allowImplicitScrolling,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
horizontalDragGestureRecognizer: widget.horizontalDragGestureRecognizer,
|
||||
);
|
||||
if (widget.onPageChanged != null) {
|
||||
return NotificationListener<ScrollEndNotification>(
|
||||
onNotification: (notification) {
|
||||
if (notification.depth == 0) {
|
||||
final metrics = notification.metrics as PageMetrics;
|
||||
final int currentPage = metrics.page!.round();
|
||||
if (currentPage != _lastReportedPage) {
|
||||
_lastReportedPage = currentPage;
|
||||
widget.onPageChanged!(currentPage);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
@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',
|
||||
),
|
||||
)
|
||||
..add(
|
||||
DiagnosticsProperty<ScrollCacheExtent>(
|
||||
'scrollCacheExtent',
|
||||
widget.scrollCacheExtent,
|
||||
defaultValue: ScrollCacheExtent.viewport(
|
||||
widget.allowImplicitScrolling ? 1.0 : 0.0,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,208 +0,0 @@
|
||||
// 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: dangling_library_doc_comments, prefer_initializing_formals
|
||||
|
||||
/// @docImport 'package:flutter/material.dart';
|
||||
///
|
||||
/// @docImport 'overscroll_indicator.dart';
|
||||
/// @docImport 'viewport.dart';
|
||||
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:PiliPlus/common/widgets/flutter/page/scrollable.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart'
|
||||
hide EdgeDraggingAutoScroller, Scrollable, ScrollableState;
|
||||
|
||||
/// An auto scroller that scrolls the [scrollable] if a drag gesture drags close
|
||||
/// to its edge.
|
||||
///
|
||||
/// The scroll velocity is controlled by the [velocityScalar]:
|
||||
///
|
||||
/// velocity = (distance of overscroll) * [velocityScalar].
|
||||
class EdgeDraggingAutoScroller {
|
||||
/// Creates a auto scroller that scrolls the [scrollable].
|
||||
EdgeDraggingAutoScroller(
|
||||
this.scrollable, {
|
||||
this.onScrollViewScrolled,
|
||||
required this.velocityScalar,
|
||||
});
|
||||
|
||||
/// The [Scrollable] this auto scroller is scrolling.
|
||||
final ScrollableState scrollable;
|
||||
|
||||
/// Called when a scroll view is scrolled.
|
||||
///
|
||||
/// The scroll view may be scrolled multiple times in a row until the drag
|
||||
/// target no longer triggers the auto scroll. This callback will be called
|
||||
/// in between each scroll.
|
||||
final VoidCallback? onScrollViewScrolled;
|
||||
|
||||
/// {@template flutter.widgets.EdgeDraggingAutoScroller.velocityScalar}
|
||||
/// The velocity scalar per pixel over scroll.
|
||||
///
|
||||
/// It represents how the velocity scale with the over scroll distance. The
|
||||
/// auto-scroll velocity = (distance of overscroll) * velocityScalar.
|
||||
/// {@endtemplate}
|
||||
final double velocityScalar;
|
||||
|
||||
late Rect _dragTargetRelatedToScrollOrigin;
|
||||
|
||||
/// Whether the auto scroll is in progress.
|
||||
bool get scrolling => _scrolling;
|
||||
bool _scrolling = false;
|
||||
|
||||
double _offsetExtent(Offset offset, Axis scrollDirection) {
|
||||
return switch (scrollDirection) {
|
||||
Axis.horizontal => offset.dx,
|
||||
Axis.vertical => offset.dy,
|
||||
};
|
||||
}
|
||||
|
||||
double _sizeExtent(Size size, Axis scrollDirection) {
|
||||
return switch (scrollDirection) {
|
||||
Axis.horizontal => size.width,
|
||||
Axis.vertical => size.height,
|
||||
};
|
||||
}
|
||||
|
||||
AxisDirection get _axisDirection => scrollable.axisDirection;
|
||||
Axis get _scrollDirection => axisDirectionToAxis(_axisDirection);
|
||||
|
||||
/// Starts the auto scroll if the [dragTarget] is close to the edge.
|
||||
///
|
||||
/// The scroll starts to scroll the [scrollable] if the target rect is close
|
||||
/// to the edge of the [scrollable]; otherwise, it remains stationary.
|
||||
///
|
||||
/// If the scrollable is already scrolling, calling this method updates the
|
||||
/// previous dragTarget to the new value and continues scrolling if necessary.
|
||||
void startAutoScrollIfNecessary(Rect dragTarget) {
|
||||
final Offset deltaToOrigin = scrollable.deltaToScrollOrigin;
|
||||
_dragTargetRelatedToScrollOrigin = dragTarget.translate(
|
||||
deltaToOrigin.dx,
|
||||
deltaToOrigin.dy,
|
||||
);
|
||||
if (_scrolling) {
|
||||
// The change will be picked up in the next scroll.
|
||||
return;
|
||||
}
|
||||
assert(!_scrolling);
|
||||
_scroll();
|
||||
}
|
||||
|
||||
/// Stop any ongoing auto scrolling.
|
||||
void stopAutoScroll() {
|
||||
_scrolling = false;
|
||||
}
|
||||
|
||||
Future<void> _scroll() async {
|
||||
final scrollRenderBox = scrollable.context.findRenderObject()! as RenderBox;
|
||||
final Matrix4 transform = scrollRenderBox.getTransformTo(null);
|
||||
final Rect globalRect = MatrixUtils.transformRect(
|
||||
transform,
|
||||
Rect.fromLTRB(
|
||||
0,
|
||||
0,
|
||||
scrollRenderBox.size.width,
|
||||
scrollRenderBox.size.height,
|
||||
),
|
||||
);
|
||||
final Rect transformedDragTarget = MatrixUtils.transformRect(
|
||||
transform,
|
||||
_dragTargetRelatedToScrollOrigin,
|
||||
);
|
||||
|
||||
assert(
|
||||
(globalRect.size.width + precisionErrorTolerance) >=
|
||||
transformedDragTarget.size.width &&
|
||||
(globalRect.size.height + precisionErrorTolerance) >=
|
||||
transformedDragTarget.size.height,
|
||||
'Drag target size is larger than scrollable size, which may cause bouncing',
|
||||
);
|
||||
_scrolling = true;
|
||||
double? newOffset;
|
||||
const overDragMax = 20.0;
|
||||
|
||||
final Offset deltaToOrigin = scrollable.deltaToScrollOrigin;
|
||||
final Offset viewportOrigin = globalRect.topLeft.translate(
|
||||
deltaToOrigin.dx,
|
||||
deltaToOrigin.dy,
|
||||
);
|
||||
final double viewportStart = _offsetExtent(
|
||||
viewportOrigin,
|
||||
_scrollDirection,
|
||||
);
|
||||
final double viewportEnd =
|
||||
viewportStart + _sizeExtent(globalRect.size, _scrollDirection);
|
||||
|
||||
final double proxyStart = _offsetExtent(
|
||||
_dragTargetRelatedToScrollOrigin.topLeft,
|
||||
_scrollDirection,
|
||||
);
|
||||
final double proxyEnd = _offsetExtent(
|
||||
_dragTargetRelatedToScrollOrigin.bottomRight,
|
||||
_scrollDirection,
|
||||
);
|
||||
switch (_axisDirection) {
|
||||
case AxisDirection.up:
|
||||
case AxisDirection.left:
|
||||
if (proxyEnd > viewportEnd &&
|
||||
scrollable.position.pixels > scrollable.position.minScrollExtent) {
|
||||
final double overDrag = math.min(proxyEnd - viewportEnd, overDragMax);
|
||||
newOffset = math.max(
|
||||
scrollable.position.minScrollExtent,
|
||||
scrollable.position.pixels - overDrag,
|
||||
);
|
||||
} else if (proxyStart < viewportStart &&
|
||||
scrollable.position.pixels < scrollable.position.maxScrollExtent) {
|
||||
final double overDrag = math.min(
|
||||
viewportStart - proxyStart,
|
||||
overDragMax,
|
||||
);
|
||||
newOffset = math.min(
|
||||
scrollable.position.maxScrollExtent,
|
||||
scrollable.position.pixels + overDrag,
|
||||
);
|
||||
}
|
||||
case AxisDirection.right:
|
||||
case AxisDirection.down:
|
||||
if (proxyStart < viewportStart &&
|
||||
scrollable.position.pixels > scrollable.position.minScrollExtent) {
|
||||
final double overDrag = math.min(
|
||||
viewportStart - proxyStart,
|
||||
overDragMax,
|
||||
);
|
||||
newOffset = math.max(
|
||||
scrollable.position.minScrollExtent,
|
||||
scrollable.position.pixels - overDrag,
|
||||
);
|
||||
} else if (proxyEnd > viewportEnd &&
|
||||
scrollable.position.pixels < scrollable.position.maxScrollExtent) {
|
||||
final double overDrag = math.min(proxyEnd - viewportEnd, overDragMax);
|
||||
newOffset = math.min(
|
||||
scrollable.position.maxScrollExtent,
|
||||
scrollable.position.pixels + overDrag,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (newOffset == null ||
|
||||
(newOffset - scrollable.position.pixels).abs() < 1.0) {
|
||||
// Drag should not trigger scroll.
|
||||
_scrolling = false;
|
||||
return;
|
||||
}
|
||||
final duration = Duration(milliseconds: (1000 / velocityScalar).round());
|
||||
await scrollable.position.animateTo(
|
||||
newOffset,
|
||||
duration: duration,
|
||||
curve: Curves.linear,
|
||||
);
|
||||
onScrollViewScrolled?.call();
|
||||
if (_scrolling) {
|
||||
await _scroll();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,374 +0,0 @@
|
||||
// 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: prefer_initializing_formals
|
||||
|
||||
import 'package:PiliPlus/common/widgets/flutter/page/page_view.dart';
|
||||
import 'package:flutter/foundation.dart' show clampDouble;
|
||||
import 'package:flutter/gestures.dart'
|
||||
show DragStartBehavior, HorizontalDragGestureRecognizer;
|
||||
import 'package:flutter/material.dart' hide TabBarView, PageView;
|
||||
|
||||
/// A page view that displays the widget which corresponds to the currently
|
||||
/// selected tab.
|
||||
///
|
||||
/// This widget is typically used in conjunction with a [TabBar].
|
||||
///
|
||||
/// {@youtube 560 315 https://www.youtube.com/watch?v=POtoEH-5l40}
|
||||
///
|
||||
/// If a [TabController] is not provided, then there must be a [DefaultTabController]
|
||||
/// ancestor.
|
||||
///
|
||||
/// The tab controller's [TabController.length] must equal the length of the
|
||||
/// [children] list and the length of the [TabBar.tabs] list.
|
||||
///
|
||||
/// To see a sample implementation, visit the [TabController] documentation.
|
||||
class TabBarView<T extends HorizontalDragGestureRecognizer>
|
||||
extends StatefulWidget {
|
||||
/// Creates a page view with one child per tab.
|
||||
///
|
||||
/// The length of [children] must be the same as the [controller]'s length.
|
||||
const TabBarView({
|
||||
super.key,
|
||||
required this.children,
|
||||
this.controller,
|
||||
this.physics,
|
||||
this.dragStartBehavior = DragStartBehavior.start,
|
||||
this.viewportFraction = 1.0,
|
||||
this.clipBehavior = Clip.hardEdge,
|
||||
required this.horizontalDragGestureRecognizer,
|
||||
});
|
||||
|
||||
final GestureRecognizerFactoryConstructor<T> horizontalDragGestureRecognizer;
|
||||
|
||||
/// This widget's selection and animation state.
|
||||
///
|
||||
/// If [TabController] is not provided, then the value of [DefaultTabController.of]
|
||||
/// will be used.
|
||||
final TabController? controller;
|
||||
|
||||
/// One widget per tab.
|
||||
///
|
||||
/// Its length must match the length of the [TabBar.tabs]
|
||||
/// list, as well as the [controller]'s [TabController.length].
|
||||
final List<Widget> children;
|
||||
|
||||
/// 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;
|
||||
|
||||
/// {@macro flutter.widgets.scrollable.dragStartBehavior}
|
||||
final DragStartBehavior dragStartBehavior;
|
||||
|
||||
/// {@macro flutter.widgets.pageview.viewportFraction}
|
||||
final double viewportFraction;
|
||||
|
||||
/// {@macro flutter.material.Material.clipBehavior}
|
||||
///
|
||||
/// Defaults to [Clip.hardEdge].
|
||||
final Clip clipBehavior;
|
||||
|
||||
@override
|
||||
State<TabBarView<T>> createState() => _TabBarViewState<T>();
|
||||
}
|
||||
|
||||
class _TabBarViewState<T extends HorizontalDragGestureRecognizer>
|
||||
extends State<TabBarView<T>> {
|
||||
TabController? _controller;
|
||||
PageController? _pageController;
|
||||
late List<Widget> _childrenWithKey;
|
||||
int? _currentIndex;
|
||||
int _warpUnderwayCount = 0;
|
||||
int _scrollUnderwayCount = 0;
|
||||
bool _debugHasScheduledValidChildrenCountCheck = false;
|
||||
|
||||
// If the TabBarView is rebuilt with a new tab controller, the caller should
|
||||
// dispose the old one. In that case the old controller's animation will be
|
||||
// null and should not be accessed.
|
||||
bool get _controllerIsValid => _controller?.animation != null;
|
||||
|
||||
void _updateTabController() {
|
||||
final TabController? newController =
|
||||
widget.controller ?? DefaultTabController.maybeOf(context);
|
||||
assert(() {
|
||||
if (newController == null) {
|
||||
throw FlutterError(
|
||||
'No TabController for ${widget.runtimeType}.\n'
|
||||
'When creating a ${widget.runtimeType}, you must either provide an explicit '
|
||||
'TabController using the "controller" property, or you must ensure that there '
|
||||
'is a DefaultTabController above the ${widget.runtimeType}.\n'
|
||||
'In this case, there was neither an explicit controller nor a default controller.',
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}());
|
||||
|
||||
if (newController == _controller) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_controllerIsValid) {
|
||||
_controller!.animation!.removeListener(_handleTabControllerAnimationTick);
|
||||
}
|
||||
_controller = newController;
|
||||
if (_controller != null) {
|
||||
_controller!.animation!.addListener(_handleTabControllerAnimationTick);
|
||||
}
|
||||
}
|
||||
|
||||
void _jumpToPage(int page) {
|
||||
_warpUnderwayCount += 1;
|
||||
_pageController!.jumpToPage(page);
|
||||
_warpUnderwayCount -= 1;
|
||||
}
|
||||
|
||||
Future<void> _animateToPage(
|
||||
int page, {
|
||||
required Duration duration,
|
||||
required Curve curve,
|
||||
}) async {
|
||||
_warpUnderwayCount += 1;
|
||||
await _pageController!.animateToPage(
|
||||
page,
|
||||
duration: duration,
|
||||
curve: curve,
|
||||
);
|
||||
_warpUnderwayCount -= 1;
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_updateChildren();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
_updateTabController();
|
||||
_currentIndex = _controller!.index;
|
||||
if (_pageController == null) {
|
||||
_pageController = PageController(
|
||||
initialPage: _currentIndex!,
|
||||
viewportFraction: widget.viewportFraction,
|
||||
);
|
||||
} else {
|
||||
_pageController!.jumpToPage(_currentIndex!);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(TabBarView<T> oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.controller != oldWidget.controller) {
|
||||
_updateTabController();
|
||||
_currentIndex = _controller!.index;
|
||||
_jumpToPage(_currentIndex!);
|
||||
}
|
||||
if (widget.viewportFraction != oldWidget.viewportFraction) {
|
||||
_pageController?.dispose();
|
||||
_pageController = PageController(
|
||||
initialPage: _currentIndex!,
|
||||
viewportFraction: widget.viewportFraction,
|
||||
);
|
||||
}
|
||||
// While a warp is under way, we stop updating the tab page contents.
|
||||
// This is tracked in https://github.com/flutter/flutter/issues/31269.
|
||||
if (widget.children != oldWidget.children && _warpUnderwayCount == 0) {
|
||||
_updateChildren();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (_controllerIsValid) {
|
||||
_controller!.animation!.removeListener(_handleTabControllerAnimationTick);
|
||||
}
|
||||
_controller = null;
|
||||
_pageController?.dispose();
|
||||
// We don't own the _controller Animation, so it's not disposed here.
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _updateChildren() {
|
||||
_childrenWithKey = KeyedSubtree.ensureUniqueKeysForList(
|
||||
widget.children.map<Widget>((Widget child) {
|
||||
return Semantics(role: .tabPanel, child: child);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
void _handleTabControllerAnimationTick() {
|
||||
if (_scrollUnderwayCount > 0 || !_controller!.indexIsChanging) {
|
||||
return;
|
||||
} // This widget is driving the controller's animation.
|
||||
|
||||
if (_controller!.index != _currentIndex) {
|
||||
_currentIndex = _controller!.index;
|
||||
_warpToCurrentIndex();
|
||||
}
|
||||
}
|
||||
|
||||
void _warpToCurrentIndex() {
|
||||
if (!mounted || _pageController!.page == _currentIndex!.toDouble()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final adjacentDestination =
|
||||
(_currentIndex! - _controller!.previousIndex).abs() == 1;
|
||||
if (adjacentDestination) {
|
||||
_warpToAdjacentTab(_controller!.animationDuration);
|
||||
} else {
|
||||
_warpToNonAdjacentTab(_controller!.animationDuration);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _warpToAdjacentTab(Duration duration) async {
|
||||
if (duration == Duration.zero) {
|
||||
_jumpToPage(_currentIndex!);
|
||||
} else {
|
||||
await _animateToPage(
|
||||
_currentIndex!,
|
||||
duration: duration,
|
||||
curve: Curves.ease,
|
||||
);
|
||||
}
|
||||
if (mounted) {
|
||||
setState(_updateChildren);
|
||||
}
|
||||
return Future<void>.value();
|
||||
}
|
||||
|
||||
Future<void> _warpToNonAdjacentTab(Duration duration) async {
|
||||
final int previousIndex = _controller!.previousIndex;
|
||||
assert((_currentIndex! - previousIndex).abs() > 1);
|
||||
|
||||
// initialPage defines which page is shown when starting the animation.
|
||||
// This page is adjacent to the destination page.
|
||||
final int initialPage = _currentIndex! > previousIndex
|
||||
? _currentIndex! - 1
|
||||
: _currentIndex! + 1;
|
||||
|
||||
setState(() {
|
||||
// Needed for `RenderSliverMultiBoxAdaptor.move` and kept alive children.
|
||||
// For motivation, see https://github.com/flutter/flutter/pull/29188 and
|
||||
// https://github.com/flutter/flutter/issues/27010#issuecomment-486475152.
|
||||
_childrenWithKey = List<Widget>.of(_childrenWithKey, growable: false);
|
||||
final Widget temp = _childrenWithKey[initialPage];
|
||||
_childrenWithKey[initialPage] = _childrenWithKey[previousIndex];
|
||||
_childrenWithKey[previousIndex] = temp;
|
||||
});
|
||||
|
||||
// Make a first jump to the adjacent page.
|
||||
_jumpToPage(initialPage);
|
||||
|
||||
// Jump or animate to the destination page.
|
||||
if (duration == Duration.zero) {
|
||||
_jumpToPage(_currentIndex!);
|
||||
} else {
|
||||
await _animateToPage(
|
||||
_currentIndex!,
|
||||
duration: duration,
|
||||
curve: Curves.ease,
|
||||
);
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
setState(_updateChildren);
|
||||
}
|
||||
}
|
||||
|
||||
void _syncControllerOffset() {
|
||||
_controller!.offset = clampDouble(
|
||||
_pageController!.page! - _controller!.index,
|
||||
-1.0,
|
||||
1.0,
|
||||
);
|
||||
}
|
||||
|
||||
// Called when the PageView scrolls
|
||||
bool _handleScrollNotification(ScrollNotification notification) {
|
||||
if (_warpUnderwayCount > 0 || _scrollUnderwayCount > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (notification.depth != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_controllerIsValid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_scrollUnderwayCount += 1;
|
||||
final double page = _pageController!.page!;
|
||||
if (notification is ScrollUpdateNotification &&
|
||||
!_controller!.indexIsChanging) {
|
||||
final bool pageChanged = (page - _controller!.index).abs() > 1.0;
|
||||
if (pageChanged) {
|
||||
_controller!.index = page.round();
|
||||
_currentIndex = _controller!.index;
|
||||
}
|
||||
_syncControllerOffset();
|
||||
} else if (notification is ScrollEndNotification) {
|
||||
_controller!.index = page.round();
|
||||
_currentIndex = _controller!.index;
|
||||
if (!_controller!.indexIsChanging) {
|
||||
_syncControllerOffset();
|
||||
}
|
||||
}
|
||||
_scrollUnderwayCount -= 1;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _debugScheduleCheckHasValidChildrenCount() {
|
||||
if (_debugHasScheduledValidChildrenCountCheck) {
|
||||
return true;
|
||||
}
|
||||
WidgetsBinding.instance.addPostFrameCallback((Duration duration) {
|
||||
_debugHasScheduledValidChildrenCountCheck = false;
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
assert(() {
|
||||
if (_controller!.length != widget.children.length) {
|
||||
throw FlutterError(
|
||||
"Controller's length property (${_controller!.length}) does not match the "
|
||||
"number of children (${widget.children.length}) present in TabBarView's children property.",
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}());
|
||||
}, debugLabel: 'TabBarView.validChildrenCountCheck');
|
||||
_debugHasScheduledValidChildrenCountCheck = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
assert(_debugScheduleCheckHasValidChildrenCount());
|
||||
|
||||
return NotificationListener<ScrollNotification>(
|
||||
onNotification: _handleScrollNotification,
|
||||
child: PageView<T>(
|
||||
dragStartBehavior: widget.dragStartBehavior,
|
||||
clipBehavior: widget.clipBehavior,
|
||||
controller: _pageController,
|
||||
physics: widget.physics == null
|
||||
? const PageScrollPhysics().applyTo(const ClampingScrollPhysics())
|
||||
: const PageScrollPhysics().applyTo(widget.physics),
|
||||
horizontalDragGestureRecognizer: widget.horizontalDragGestureRecognizer,
|
||||
children: _childrenWithKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,9 @@ class CustomHorizontalDragGestureRecognizer
|
||||
lastPosition.global,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Type get runtimeType => HorizontalDragGestureRecognizer;
|
||||
}
|
||||
|
||||
double touchSlopH = Pref.touchSlopH;
|
||||
|
||||
@@ -19,12 +19,12 @@ import 'dart:io' show File, Platform;
|
||||
|
||||
import 'package:PiliPlus/common/widgets/colored_box_transition.dart';
|
||||
import 'package:PiliPlus/common/widgets/dialog/simple_dialog_option.dart';
|
||||
import 'package:PiliPlus/common/widgets/flutter/page/page_view.dart';
|
||||
import 'package:PiliPlus/common/widgets/gesture/image_horizontal_drag_gesture_recognizer.dart';
|
||||
import 'package:PiliPlus/common/widgets/image_viewer/image.dart';
|
||||
import 'package:PiliPlus/common/widgets/image_viewer/loading_indicator.dart';
|
||||
import 'package:PiliPlus/common/widgets/image_viewer/viewer.dart';
|
||||
import 'package:PiliPlus/common/widgets/scroll_physics.dart';
|
||||
import 'package:PiliPlus/common/widgets/scroll_physics.dart'
|
||||
show tabBarScrollPhysics;
|
||||
import 'package:PiliPlus/main.dart' show tmpPadding;
|
||||
import 'package:PiliPlus/models/common/image_preview_type.dart';
|
||||
import 'package:PiliPlus/plugin/pl_player/utils/fullscreen.dart';
|
||||
@@ -40,7 +40,7 @@ import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:cached_network_image_ce/cached_network_image.dart';
|
||||
import 'package:easy_debounce/easy_throttle.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart' hide Image, PageView;
|
||||
import 'package:flutter/material.dart' hide Image;
|
||||
import 'package:flutter/services.dart' show HapticFeedback;
|
||||
import 'package:get/get.dart';
|
||||
import 'package:media_kit/media_kit.dart';
|
||||
@@ -331,11 +331,11 @@ class _GalleryViewerState extends State<GalleryViewer>
|
||||
alignment: .topLeft,
|
||||
animation: _animateController,
|
||||
onTransform: _onTransform,
|
||||
child: PageView<ImageHorizontalDragGestureRecognizer>.builder(
|
||||
child: PageView.builder(
|
||||
controller: _pageController,
|
||||
onPageChanged: _onPageChanged,
|
||||
physics: const CustomTabBarViewScrollPhysics(
|
||||
parent: AlwaysScrollableScrollPhysics(),
|
||||
physics: const AlwaysScrollableScrollPhysics(
|
||||
parent: tabBarScrollPhysics,
|
||||
),
|
||||
itemCount: widget.sources.length,
|
||||
itemBuilder: _itemBuilder,
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
import 'package:PiliPlus/common/widgets/flutter/page/tabs.dart';
|
||||
import 'package:PiliPlus/common/widgets/gesture/horizontal_drag_gesture_recognizer.dart';
|
||||
import 'package:PiliPlus/utils/platform_utils.dart';
|
||||
import 'package:PiliPlus/utils/storage_pref.dart';
|
||||
import 'package:flutter/material.dart' hide TabBarView;
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
Widget tabBarView({
|
||||
required List<Widget> children,
|
||||
TabController? controller,
|
||||
}) => TabBarView<CustomHorizontalDragGestureRecognizer>(
|
||||
HitTestBehavior hitTestBehavior = .opaque,
|
||||
}) => TabBarView(
|
||||
controller: controller,
|
||||
physics: clampingScrollPhysics,
|
||||
physics: tabBarScrollPhysics,
|
||||
hitTestBehavior: hitTestBehavior,
|
||||
horizontalDragGestureRecognizer: CustomHorizontalDragGestureRecognizer.new,
|
||||
children: children,
|
||||
);
|
||||
|
||||
final _springDescription = _customSpringDescription();
|
||||
SpringDescription kSpringDescription = _customSpringDescription();
|
||||
|
||||
SpringDescription _customSpringDescription() {
|
||||
final List<double> springDescription = Pref.springDescription;
|
||||
@@ -25,20 +26,18 @@ SpringDescription _customSpringDescription() {
|
||||
);
|
||||
}
|
||||
|
||||
const clampingScrollPhysics = CustomTabBarViewScrollPhysics(
|
||||
parent: ClampingScrollPhysics(),
|
||||
);
|
||||
const tabBarScrollPhysics = _TabBarViewScrollPhysics();
|
||||
|
||||
class CustomTabBarViewScrollPhysics extends ScrollPhysics {
|
||||
const CustomTabBarViewScrollPhysics({super.parent});
|
||||
class _TabBarViewScrollPhysics extends ClampingScrollPhysics {
|
||||
const _TabBarViewScrollPhysics({super.parent});
|
||||
|
||||
@override
|
||||
CustomTabBarViewScrollPhysics applyTo(ScrollPhysics? ancestor) {
|
||||
return CustomTabBarViewScrollPhysics(parent: buildParent(ancestor));
|
||||
_TabBarViewScrollPhysics applyTo(ScrollPhysics? ancestor) {
|
||||
return _TabBarViewScrollPhysics(parent: buildParent(ancestor));
|
||||
}
|
||||
|
||||
@override
|
||||
SpringDescription get spring => _springDescription;
|
||||
SpringDescription get spring => kSpringDescription;
|
||||
}
|
||||
|
||||
mixin ReloadMixin {
|
||||
|
||||
Reference in New Issue
Block a user