mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-05-31 16:18:22 +08:00
opt custom widget
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
@@ -22,16 +22,6 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
/// The signature of a method that provides a [BuildContext] and
|
|
||||||
/// [ScrollController] for building a widget that may overflow the draggable
|
|
||||||
/// [Axis] of the containing [DraggableScrollableSheet].
|
|
||||||
///
|
|
||||||
/// Users should apply the [scrollController] to a [ScrollView] subclass, such
|
|
||||||
/// as a [SingleChildScrollView], [ListView] or [GridView], to have the whole
|
|
||||||
/// sheet be draggable.
|
|
||||||
typedef ScrollableWidgetBuilder = Widget Function(
|
|
||||||
BuildContext context, ScrollController scrollController);
|
|
||||||
|
|
||||||
/// Controls a [DraggableScrollableSheet].
|
/// Controls a [DraggableScrollableSheet].
|
||||||
///
|
///
|
||||||
/// Draggable scrollable controllers are typically stored as member variables in
|
/// Draggable scrollable controllers are typically stored as member variables in
|
||||||
|
|||||||
@@ -30,22 +30,6 @@ import 'package:get/get.dart';
|
|||||||
|
|
||||||
export 'package:flutter/physics.dart' show Tolerance;
|
export 'package:flutter/physics.dart' show Tolerance;
|
||||||
|
|
||||||
// Examples can assume:
|
|
||||||
// late BuildContext context;
|
|
||||||
|
|
||||||
/// Signature used by [CustomScrollable] to build the viewport through which the
|
|
||||||
/// scrollable content is displayed.
|
|
||||||
typedef ViewportBuilder = Widget Function(
|
|
||||||
BuildContext context, ViewportOffset position);
|
|
||||||
|
|
||||||
/// Signature used by [TwoDimensionalScrollable] to build the viewport through
|
|
||||||
/// which the scrollable content is displayed.
|
|
||||||
typedef TwoDimensionalViewportBuilder = Widget Function(
|
|
||||||
BuildContext context,
|
|
||||||
ViewportOffset verticalPosition,
|
|
||||||
ViewportOffset horizontalPosition,
|
|
||||||
);
|
|
||||||
|
|
||||||
// The return type of _performEnsureVisible.
|
// The return type of _performEnsureVisible.
|
||||||
//
|
//
|
||||||
// The list of futures represents each pending ScrollPosition call to
|
// The list of futures represents each pending ScrollPosition call to
|
||||||
@@ -1971,761 +1955,6 @@ enum DiagonalDragBehavior {
|
|||||||
free,
|
free,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A widget that manages scrolling in both the vertical and horizontal
|
|
||||||
/// dimensions and informs the [TwoDimensionalViewport] through which the
|
|
||||||
/// content is viewed.
|
|
||||||
///
|
|
||||||
/// [TwoDimensionalScrollable] implements the interaction model for a scrollable
|
|
||||||
/// widget in both the vertical and horizontal axes, including gesture
|
|
||||||
/// recognition, but does not have an opinion about how the
|
|
||||||
/// [TwoDimensionalViewport], which actually displays the children, is
|
|
||||||
/// constructed.
|
|
||||||
///
|
|
||||||
/// It's rare to construct a [TwoDimensionalScrollable] directly. Instead,
|
|
||||||
/// consider subclassing [TwoDimensionalScrollView], which combines scrolling,
|
|
||||||
/// viewporting, and a layout model in both dimensions.
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// * [TwoDimensionalScrollView], an abstract base class for displaying a
|
|
||||||
/// scrolling array of children in both directions.
|
|
||||||
/// * [TwoDimensionalViewport], which can be used to customize the child layout
|
|
||||||
/// model.
|
|
||||||
class TwoDimensionalScrollable extends StatefulWidget {
|
|
||||||
/// Creates a widget that scrolls in two dimensions.
|
|
||||||
///
|
|
||||||
/// The [horizontalDetails], [verticalDetails], and [viewportBuilder] must not
|
|
||||||
/// be null.
|
|
||||||
const TwoDimensionalScrollable({
|
|
||||||
super.key,
|
|
||||||
required this.horizontalDetails,
|
|
||||||
required this.verticalDetails,
|
|
||||||
required this.viewportBuilder,
|
|
||||||
this.incrementCalculator,
|
|
||||||
this.restorationId,
|
|
||||||
this.excludeFromSemantics = false,
|
|
||||||
this.diagonalDragBehavior = DiagonalDragBehavior.none,
|
|
||||||
this.dragStartBehavior = DragStartBehavior.start,
|
|
||||||
this.hitTestBehavior = HitTestBehavior.opaque,
|
|
||||||
});
|
|
||||||
|
|
||||||
/// How scrolling gestures should lock to one axis, or allow free movement
|
|
||||||
/// in both axes.
|
|
||||||
final DiagonalDragBehavior diagonalDragBehavior;
|
|
||||||
|
|
||||||
/// The configuration of the horizontal [CustomScrollable].
|
|
||||||
///
|
|
||||||
/// These [ScrollableDetails] can be used to set the [AxisDirection],
|
|
||||||
/// [ScrollController], [ScrollPhysics] and more for the horizontal axis.
|
|
||||||
final ScrollableDetails horizontalDetails;
|
|
||||||
|
|
||||||
/// The configuration of the vertical [CustomScrollable].
|
|
||||||
///
|
|
||||||
/// These [ScrollableDetails] can be used to set the [AxisDirection],
|
|
||||||
/// [ScrollController], [ScrollPhysics] and more for the vertical axis.
|
|
||||||
final ScrollableDetails verticalDetails;
|
|
||||||
|
|
||||||
/// Builds the viewport through which the scrollable content is displayed.
|
|
||||||
///
|
|
||||||
/// A [TwoDimensionalViewport] uses two given [ViewportOffset]s to determine
|
|
||||||
/// which part of its content is actually visible through the viewport.
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// * [TwoDimensionalViewport], which is a viewport that displays a span of
|
|
||||||
/// widgets in both dimensions.
|
|
||||||
final TwoDimensionalViewportBuilder viewportBuilder;
|
|
||||||
|
|
||||||
/// {@macro flutter.widgets.Scrollable.incrementCalculator}
|
|
||||||
///
|
|
||||||
/// This value applies in both axes.
|
|
||||||
final ScrollIncrementCalculator? incrementCalculator;
|
|
||||||
|
|
||||||
/// {@macro flutter.widgets.scrollable.restorationId}
|
|
||||||
///
|
|
||||||
/// Internally, the [TwoDimensionalScrollable] will introduce a
|
|
||||||
/// [RestorationScope] that will be assigned this value. The two [CustomScrollable]s
|
|
||||||
/// within will then be given unique IDs within this scope.
|
|
||||||
final String? restorationId;
|
|
||||||
|
|
||||||
/// {@macro flutter.widgets.scrollable.excludeFromSemantics}
|
|
||||||
///
|
|
||||||
/// This value applies to both axes.
|
|
||||||
final bool excludeFromSemantics;
|
|
||||||
|
|
||||||
/// {@macro flutter.widgets.scrollable.hitTestBehavior}
|
|
||||||
///
|
|
||||||
/// This value applies to both axes.
|
|
||||||
final HitTestBehavior hitTestBehavior;
|
|
||||||
|
|
||||||
/// {@macro flutter.widgets.scrollable.dragStartBehavior}
|
|
||||||
///
|
|
||||||
/// This value applies in both axes.
|
|
||||||
final DragStartBehavior dragStartBehavior;
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<TwoDimensionalScrollable> createState() =>
|
|
||||||
TwoDimensionalScrollableState();
|
|
||||||
|
|
||||||
/// The state from the closest instance of this class that encloses the given
|
|
||||||
/// context, or null if none is found.
|
|
||||||
///
|
|
||||||
/// Typical usage is as follows:
|
|
||||||
///
|
|
||||||
/// ```dart
|
|
||||||
/// TwoDimensionalScrollableState? scrollable = TwoDimensionalScrollable.maybeOf(context);
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// Calling this method will create a dependency on the closest
|
|
||||||
/// [TwoDimensionalScrollable] in the [context]. The internal [CustomScrollable]s
|
|
||||||
/// can be accessed through [TwoDimensionalScrollableState.verticalScrollable]
|
|
||||||
/// and [TwoDimensionalScrollableState.horizontalScrollable].
|
|
||||||
///
|
|
||||||
/// Alternatively, [CustomScrollable.maybeOf] can be used by providing the desired
|
|
||||||
/// [Axis] to the `axis` parameter.
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// * [TwoDimensionalScrollable.of], which is similar to this method, but
|
|
||||||
/// asserts if no [CustomScrollable] ancestor is found.
|
|
||||||
static TwoDimensionalScrollableState? maybeOf(BuildContext context) {
|
|
||||||
final _TwoDimensionalScrollableScope? widget = context
|
|
||||||
.dependOnInheritedWidgetOfExactType<_TwoDimensionalScrollableScope>();
|
|
||||||
return widget?.twoDimensionalScrollable;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The state from the closest instance of this class that encloses the given
|
|
||||||
/// context.
|
|
||||||
///
|
|
||||||
/// Typical usage is as follows:
|
|
||||||
///
|
|
||||||
/// ```dart
|
|
||||||
/// TwoDimensionalScrollableState scrollable = TwoDimensionalScrollable.of(context);
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// Calling this method will create a dependency on the closest
|
|
||||||
/// [TwoDimensionalScrollable] in the [context]. The internal [CustomScrollable]s
|
|
||||||
/// can be accessed through [TwoDimensionalScrollableState.verticalScrollable]
|
|
||||||
/// and [TwoDimensionalScrollableState.horizontalScrollable].
|
|
||||||
///
|
|
||||||
/// If no [TwoDimensionalScrollable] ancestor is found, then this method will
|
|
||||||
/// assert in debug mode, and throw an exception in release mode.
|
|
||||||
///
|
|
||||||
/// Alternatively, [CustomScrollable.of] can be used by providing the desired [Axis]
|
|
||||||
/// to the `axis` parameter.
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// * [TwoDimensionalScrollable.maybeOf], which is similar to this method,
|
|
||||||
/// but returns null if no [TwoDimensionalScrollable] ancestor is found.
|
|
||||||
static TwoDimensionalScrollableState of(BuildContext context) {
|
|
||||||
final TwoDimensionalScrollableState? scrollableState = maybeOf(context);
|
|
||||||
assert(() {
|
|
||||||
if (scrollableState == null) {
|
|
||||||
throw FlutterError.fromParts(<DiagnosticsNode>[
|
|
||||||
ErrorSummary(
|
|
||||||
'TwoDimensionalScrollable.of() was called with a context that does '
|
|
||||||
'not contain a TwoDimensionalScrollable widget.\n',
|
|
||||||
),
|
|
||||||
ErrorDescription(
|
|
||||||
'No TwoDimensionalScrollable widget ancestor could be found starting '
|
|
||||||
'from the context that was passed to TwoDimensionalScrollable.of(). '
|
|
||||||
'This can happen because you are using a widget that looks for a '
|
|
||||||
'TwoDimensionalScrollable ancestor, but no such ancestor exists.\n'
|
|
||||||
'The context used was:\n'
|
|
||||||
' $context',
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}());
|
|
||||||
return scrollableState!;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// State object for a [TwoDimensionalScrollable] widget.
|
|
||||||
///
|
|
||||||
/// To manipulate one of the internal [CustomScrollable] widget's scroll position, use
|
|
||||||
/// the object obtained from the [verticalScrollable] or [horizontalScrollable]
|
|
||||||
/// property.
|
|
||||||
///
|
|
||||||
/// To be informed of when a [TwoDimensionalScrollable] widget is scrolling,
|
|
||||||
/// use a [NotificationListener] to listen for [ScrollNotification]s.
|
|
||||||
/// Both axes will have the same viewport depth since there is only one
|
|
||||||
/// viewport, and so should be differentiated by the [Axis] of the
|
|
||||||
/// [ScrollMetrics] provided by the notification.
|
|
||||||
class TwoDimensionalScrollableState extends State<TwoDimensionalScrollable> {
|
|
||||||
ScrollController? _verticalFallbackController;
|
|
||||||
ScrollController? _horizontalFallbackController;
|
|
||||||
final GlobalKey<CustomScrollableState> _verticalOuterScrollableKey =
|
|
||||||
GlobalKey<CustomScrollableState>();
|
|
||||||
final GlobalKey<CustomScrollableState> _horizontalInnerScrollableKey =
|
|
||||||
GlobalKey<CustomScrollableState>();
|
|
||||||
|
|
||||||
/// The [CustomScrollableState] of the vertical axis.
|
|
||||||
///
|
|
||||||
/// Accessible by calling [TwoDimensionalScrollable.of].
|
|
||||||
///
|
|
||||||
/// Alternatively, [CustomScrollable.of] can be used by providing [Axis.vertical]
|
|
||||||
/// to the `axis` parameter.
|
|
||||||
CustomScrollableState get verticalScrollable {
|
|
||||||
assert(_verticalOuterScrollableKey.currentState != null);
|
|
||||||
return _verticalOuterScrollableKey.currentState!;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The [CustomScrollableState] of the horizontal axis.
|
|
||||||
///
|
|
||||||
/// Accessible by calling [TwoDimensionalScrollable.of].
|
|
||||||
///
|
|
||||||
/// Alternatively, [CustomScrollable.of] can be used by providing [Axis.horizontal]
|
|
||||||
/// to the `axis` parameter.
|
|
||||||
CustomScrollableState get horizontalScrollable {
|
|
||||||
assert(_horizontalInnerScrollableKey.currentState != null);
|
|
||||||
return _horizontalInnerScrollableKey.currentState!;
|
|
||||||
}
|
|
||||||
|
|
||||||
@protected
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
if (widget.verticalDetails.controller == null) {
|
|
||||||
_verticalFallbackController = ScrollController();
|
|
||||||
}
|
|
||||||
if (widget.horizontalDetails.controller == null) {
|
|
||||||
_horizontalFallbackController = ScrollController();
|
|
||||||
}
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@protected
|
|
||||||
@override
|
|
||||||
void didUpdateWidget(TwoDimensionalScrollable oldWidget) {
|
|
||||||
super.didUpdateWidget(oldWidget);
|
|
||||||
// Handle changes in the provided/fallback scroll controllers
|
|
||||||
|
|
||||||
// Vertical
|
|
||||||
if (oldWidget.verticalDetails.controller !=
|
|
||||||
widget.verticalDetails.controller) {
|
|
||||||
if (oldWidget.verticalDetails.controller == null) {
|
|
||||||
// The old controller was null, meaning the fallback cannot be null.
|
|
||||||
// Dispose of the fallback.
|
|
||||||
assert(_verticalFallbackController != null);
|
|
||||||
assert(widget.verticalDetails.controller != null);
|
|
||||||
_verticalFallbackController!.dispose();
|
|
||||||
_verticalFallbackController = null;
|
|
||||||
} else if (widget.verticalDetails.controller == null) {
|
|
||||||
// If the new controller is null, we need to set up the fallback
|
|
||||||
// ScrollController.
|
|
||||||
assert(_verticalFallbackController == null);
|
|
||||||
_verticalFallbackController = ScrollController();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Horizontal
|
|
||||||
if (oldWidget.horizontalDetails.controller !=
|
|
||||||
widget.horizontalDetails.controller) {
|
|
||||||
if (oldWidget.horizontalDetails.controller == null) {
|
|
||||||
// The old controller was null, meaning the fallback cannot be null.
|
|
||||||
// Dispose of the fallback.
|
|
||||||
assert(_horizontalFallbackController != null);
|
|
||||||
assert(widget.horizontalDetails.controller != null);
|
|
||||||
_horizontalFallbackController!.dispose();
|
|
||||||
_horizontalFallbackController = null;
|
|
||||||
} else if (widget.horizontalDetails.controller == null) {
|
|
||||||
// If the new controller is null, we need to set up the fallback
|
|
||||||
// ScrollController.
|
|
||||||
assert(_horizontalFallbackController == null);
|
|
||||||
_horizontalFallbackController = ScrollController();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@protected
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
assert(
|
|
||||||
axisDirectionToAxis(widget.verticalDetails.direction) == Axis.vertical,
|
|
||||||
'TwoDimensionalScrollable.verticalDetails are not Axis.vertical.',
|
|
||||||
);
|
|
||||||
assert(
|
|
||||||
axisDirectionToAxis(widget.horizontalDetails.direction) ==
|
|
||||||
Axis.horizontal,
|
|
||||||
'TwoDimensionalScrollable.horizontalDetails are not Axis.horizontal.',
|
|
||||||
);
|
|
||||||
|
|
||||||
final Widget result = RestorationScope(
|
|
||||||
restorationId: widget.restorationId,
|
|
||||||
child: _VerticalOuterDimension(
|
|
||||||
key: _verticalOuterScrollableKey,
|
|
||||||
// For gesture forwarding
|
|
||||||
horizontalKey: _horizontalInnerScrollableKey,
|
|
||||||
axisDirection: widget.verticalDetails.direction,
|
|
||||||
controller:
|
|
||||||
widget.verticalDetails.controller ?? _verticalFallbackController!,
|
|
||||||
physics: widget.verticalDetails.physics,
|
|
||||||
clipBehavior: widget.verticalDetails.clipBehavior ??
|
|
||||||
widget.verticalDetails.decorationClipBehavior ??
|
|
||||||
Clip.hardEdge,
|
|
||||||
incrementCalculator: widget.incrementCalculator,
|
|
||||||
excludeFromSemantics: widget.excludeFromSemantics,
|
|
||||||
restorationId: 'OuterVerticalTwoDimensionalScrollable',
|
|
||||||
dragStartBehavior: widget.dragStartBehavior,
|
|
||||||
diagonalDragBehavior: widget.diagonalDragBehavior,
|
|
||||||
hitTestBehavior: widget.hitTestBehavior,
|
|
||||||
viewportBuilder: (BuildContext context, ViewportOffset verticalOffset) {
|
|
||||||
return _HorizontalInnerDimension(
|
|
||||||
key: _horizontalInnerScrollableKey,
|
|
||||||
verticalOuterKey: _verticalOuterScrollableKey,
|
|
||||||
axisDirection: widget.horizontalDetails.direction,
|
|
||||||
controller: widget.horizontalDetails.controller ??
|
|
||||||
_horizontalFallbackController!,
|
|
||||||
physics: widget.horizontalDetails.physics,
|
|
||||||
clipBehavior: widget.horizontalDetails.clipBehavior ??
|
|
||||||
widget.horizontalDetails.decorationClipBehavior ??
|
|
||||||
Clip.hardEdge,
|
|
||||||
incrementCalculator: widget.incrementCalculator,
|
|
||||||
excludeFromSemantics: widget.excludeFromSemantics,
|
|
||||||
restorationId: 'InnerHorizontalTwoDimensionalScrollable',
|
|
||||||
dragStartBehavior: widget.dragStartBehavior,
|
|
||||||
diagonalDragBehavior: widget.diagonalDragBehavior,
|
|
||||||
hitTestBehavior: widget.hitTestBehavior,
|
|
||||||
viewportBuilder:
|
|
||||||
(BuildContext context, ViewportOffset horizontalOffset) {
|
|
||||||
return widget.viewportBuilder(
|
|
||||||
context, verticalOffset, horizontalOffset);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// TODO(Piinks): Build scrollbars for 2 dimensions instead of 1,
|
|
||||||
// https://github.com/flutter/flutter/issues/122348
|
|
||||||
|
|
||||||
return _TwoDimensionalScrollableScope(
|
|
||||||
twoDimensionalScrollable: this, child: result);
|
|
||||||
}
|
|
||||||
|
|
||||||
@protected
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_verticalFallbackController?.dispose();
|
|
||||||
_horizontalFallbackController?.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enable TwoDimensionalScrollable.of() to work as if
|
|
||||||
// TwoDimensionalScrollableState was an inherited widget.
|
|
||||||
// TwoDimensionalScrollableState.build() always rebuilds its
|
|
||||||
// _TwoDimensionalScrollableScope.
|
|
||||||
class _TwoDimensionalScrollableScope extends InheritedWidget {
|
|
||||||
const _TwoDimensionalScrollableScope({
|
|
||||||
required this.twoDimensionalScrollable,
|
|
||||||
required super.child,
|
|
||||||
});
|
|
||||||
|
|
||||||
final TwoDimensionalScrollableState twoDimensionalScrollable;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool updateShouldNotify(_TwoDimensionalScrollableScope old) => false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Vertical outer scrollable of 2D scrolling
|
|
||||||
class _VerticalOuterDimension extends CustomScrollable {
|
|
||||||
const _VerticalOuterDimension({
|
|
||||||
super.key,
|
|
||||||
required this.horizontalKey,
|
|
||||||
required super.viewportBuilder,
|
|
||||||
required super.axisDirection,
|
|
||||||
super.controller,
|
|
||||||
super.physics,
|
|
||||||
super.clipBehavior,
|
|
||||||
super.incrementCalculator,
|
|
||||||
super.excludeFromSemantics,
|
|
||||||
super.dragStartBehavior,
|
|
||||||
super.restorationId,
|
|
||||||
super.hitTestBehavior,
|
|
||||||
this.diagonalDragBehavior = DiagonalDragBehavior.none,
|
|
||||||
}) : assert(axisDirection == AxisDirection.up ||
|
|
||||||
axisDirection == AxisDirection.down);
|
|
||||||
|
|
||||||
final DiagonalDragBehavior diagonalDragBehavior;
|
|
||||||
final GlobalKey<CustomScrollableState> horizontalKey;
|
|
||||||
|
|
||||||
@override
|
|
||||||
_VerticalOuterDimensionState createState() => _VerticalOuterDimensionState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _VerticalOuterDimensionState extends CustomScrollableState {
|
|
||||||
DiagonalDragBehavior get diagonalDragBehavior =>
|
|
||||||
(widget as _VerticalOuterDimension).diagonalDragBehavior;
|
|
||||||
CustomScrollableState get horizontalScrollable =>
|
|
||||||
(widget as _VerticalOuterDimension).horizontalKey.currentState!;
|
|
||||||
|
|
||||||
Axis? lockedAxis;
|
|
||||||
Offset? lastDragOffset;
|
|
||||||
|
|
||||||
// Implemented in the _HorizontalInnerDimension instead.
|
|
||||||
@override
|
|
||||||
_EnsureVisibleResults _performEnsureVisible(
|
|
||||||
RenderObject object, {
|
|
||||||
double alignment = 0.0,
|
|
||||||
Duration duration = Duration.zero,
|
|
||||||
Curve curve = Curves.ease,
|
|
||||||
ScrollPositionAlignmentPolicy alignmentPolicy =
|
|
||||||
ScrollPositionAlignmentPolicy.explicit,
|
|
||||||
RenderObject? targetRenderObject,
|
|
||||||
}) {
|
|
||||||
assert(
|
|
||||||
false,
|
|
||||||
'The _performEnsureVisible method was called for the vertical scrollable '
|
|
||||||
'of a TwoDimensionalScrollable. This should not happen as the horizontal '
|
|
||||||
'scrollable handles both axes.',
|
|
||||||
);
|
|
||||||
return (<Future<void>>[], this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _evaluateLockedAxis(Offset offset) {
|
|
||||||
assert(lastDragOffset != null);
|
|
||||||
final Offset offsetDelta = lastDragOffset! - offset;
|
|
||||||
final double axisDifferential = offsetDelta.dx.abs() - offsetDelta.dy.abs();
|
|
||||||
if (axisDifferential.abs() >= kTouchSlop) {
|
|
||||||
// We have single axis winner.
|
|
||||||
lockedAxis = axisDifferential > 0.0 ? Axis.horizontal : Axis.vertical;
|
|
||||||
} else {
|
|
||||||
lockedAxis = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void _handleDragDown(DragDownDetails details) {
|
|
||||||
switch (diagonalDragBehavior) {
|
|
||||||
case DiagonalDragBehavior.none:
|
|
||||||
break;
|
|
||||||
case DiagonalDragBehavior.weightedEvent:
|
|
||||||
case DiagonalDragBehavior.weightedContinuous:
|
|
||||||
case DiagonalDragBehavior.free:
|
|
||||||
// Initiate hold. If one or the other wins the gesture, cancel the
|
|
||||||
// opposite axis.
|
|
||||||
horizontalScrollable._handleDragDown(details);
|
|
||||||
}
|
|
||||||
super._handleDragDown(details);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void _handleDragStart(DragStartDetails details) {
|
|
||||||
lastDragOffset = details.globalPosition;
|
|
||||||
switch (diagonalDragBehavior) {
|
|
||||||
case DiagonalDragBehavior.none:
|
|
||||||
break;
|
|
||||||
case DiagonalDragBehavior.free:
|
|
||||||
// Prepare to scroll both.
|
|
||||||
// vertical - will call super below after switch.
|
|
||||||
horizontalScrollable._handleDragStart(details);
|
|
||||||
case DiagonalDragBehavior.weightedEvent:
|
|
||||||
case DiagonalDragBehavior.weightedContinuous:
|
|
||||||
// See if one axis wins the drag.
|
|
||||||
_evaluateLockedAxis(details.globalPosition);
|
|
||||||
switch (lockedAxis) {
|
|
||||||
case null:
|
|
||||||
// Prepare to scroll both, null means no winner yet.
|
|
||||||
// vertical - will call super below after switch.
|
|
||||||
horizontalScrollable._handleDragStart(details);
|
|
||||||
case Axis.horizontal:
|
|
||||||
// Prepare to scroll horizontally.
|
|
||||||
horizontalScrollable._handleDragStart(details);
|
|
||||||
return;
|
|
||||||
case Axis.vertical:
|
|
||||||
// Prepare to scroll vertically - will call super below after switch.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
super._handleDragStart(details);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void _handleDragUpdate(DragUpdateDetails details) {
|
|
||||||
final DragUpdateDetails verticalDragDetails = DragUpdateDetails(
|
|
||||||
sourceTimeStamp: details.sourceTimeStamp,
|
|
||||||
delta: Offset(0.0, details.delta.dy),
|
|
||||||
primaryDelta: details.delta.dy,
|
|
||||||
globalPosition: details.globalPosition,
|
|
||||||
localPosition: details.localPosition,
|
|
||||||
);
|
|
||||||
final DragUpdateDetails horizontalDragDetails = DragUpdateDetails(
|
|
||||||
sourceTimeStamp: details.sourceTimeStamp,
|
|
||||||
delta: Offset(details.delta.dx, 0.0),
|
|
||||||
primaryDelta: details.delta.dx,
|
|
||||||
globalPosition: details.globalPosition,
|
|
||||||
localPosition: details.localPosition,
|
|
||||||
);
|
|
||||||
|
|
||||||
switch (diagonalDragBehavior) {
|
|
||||||
case DiagonalDragBehavior.none:
|
|
||||||
// Default gesture handling from super class.
|
|
||||||
super._handleDragUpdate(verticalDragDetails);
|
|
||||||
return;
|
|
||||||
case DiagonalDragBehavior.free:
|
|
||||||
// Scroll both axes
|
|
||||||
horizontalScrollable._handleDragUpdate(horizontalDragDetails);
|
|
||||||
super._handleDragUpdate(verticalDragDetails);
|
|
||||||
return;
|
|
||||||
case DiagonalDragBehavior.weightedContinuous:
|
|
||||||
// Re-evaluate locked axis for every update.
|
|
||||||
_evaluateLockedAxis(details.globalPosition);
|
|
||||||
lastDragOffset = details.globalPosition;
|
|
||||||
case DiagonalDragBehavior.weightedEvent:
|
|
||||||
// Lock axis only once per gesture.
|
|
||||||
if (lockedAxis == null && lastDragOffset != null) {
|
|
||||||
// A winner has not been declared yet.
|
|
||||||
// See if one axis has won the drag.
|
|
||||||
_evaluateLockedAxis(details.globalPosition);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch (lockedAxis) {
|
|
||||||
case null:
|
|
||||||
// Scroll both - vertical after switch
|
|
||||||
horizontalScrollable._handleDragUpdate(horizontalDragDetails);
|
|
||||||
case Axis.horizontal:
|
|
||||||
// Scroll horizontally
|
|
||||||
horizontalScrollable._handleDragUpdate(horizontalDragDetails);
|
|
||||||
return;
|
|
||||||
case Axis.vertical:
|
|
||||||
// Scroll vertically - after switch
|
|
||||||
}
|
|
||||||
super._handleDragUpdate(verticalDragDetails);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void _handleDragEnd(DragEndDetails details) {
|
|
||||||
lastDragOffset = null;
|
|
||||||
lockedAxis = null;
|
|
||||||
final double dx = details.velocity.pixelsPerSecond.dx;
|
|
||||||
final double dy = details.velocity.pixelsPerSecond.dy;
|
|
||||||
final DragEndDetails verticalDragDetails = DragEndDetails(
|
|
||||||
velocity: Velocity(pixelsPerSecond: Offset(0.0, dy)),
|
|
||||||
primaryVelocity: dy,
|
|
||||||
);
|
|
||||||
final DragEndDetails horizontalDragDetails = DragEndDetails(
|
|
||||||
velocity: Velocity(pixelsPerSecond: Offset(dx, 0.0)),
|
|
||||||
primaryVelocity: dx,
|
|
||||||
);
|
|
||||||
|
|
||||||
switch (diagonalDragBehavior) {
|
|
||||||
case DiagonalDragBehavior.none:
|
|
||||||
break;
|
|
||||||
case DiagonalDragBehavior.weightedEvent:
|
|
||||||
case DiagonalDragBehavior.weightedContinuous:
|
|
||||||
case DiagonalDragBehavior.free:
|
|
||||||
horizontalScrollable._handleDragEnd(horizontalDragDetails);
|
|
||||||
}
|
|
||||||
super._handleDragEnd(verticalDragDetails);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void _handleDragCancel() {
|
|
||||||
lastDragOffset = null;
|
|
||||||
lockedAxis = null;
|
|
||||||
switch (diagonalDragBehavior) {
|
|
||||||
case DiagonalDragBehavior.none:
|
|
||||||
break;
|
|
||||||
case DiagonalDragBehavior.weightedEvent:
|
|
||||||
case DiagonalDragBehavior.weightedContinuous:
|
|
||||||
case DiagonalDragBehavior.free:
|
|
||||||
horizontalScrollable._handleDragCancel();
|
|
||||||
}
|
|
||||||
super._handleDragCancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void setCanDrag(bool value) {
|
|
||||||
switch (diagonalDragBehavior) {
|
|
||||||
case DiagonalDragBehavior.none:
|
|
||||||
// If we aren't scrolling diagonally, the default drag gesture recognizer
|
|
||||||
// is used.
|
|
||||||
super.setCanDrag(value);
|
|
||||||
return;
|
|
||||||
case DiagonalDragBehavior.weightedEvent:
|
|
||||||
case DiagonalDragBehavior.weightedContinuous:
|
|
||||||
case DiagonalDragBehavior.free:
|
|
||||||
if (value) {
|
|
||||||
// Replaces the typical vertical/horizontal drag gesture recognizers
|
|
||||||
// with a pan gesture recognizer to allow bidirectional scrolling.
|
|
||||||
// Based on the diagonalDragBehavior, valid vertical deltas are
|
|
||||||
// applied to this scrollable, while horizontal deltas are routed to
|
|
||||||
// the horizontal scrollable.
|
|
||||||
_gestureRecognizers = <Type, GestureRecognizerFactory>{
|
|
||||||
PanGestureRecognizer:
|
|
||||||
GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>(
|
|
||||||
() => PanGestureRecognizer(
|
|
||||||
supportedDevices: _configuration.dragDevices),
|
|
||||||
(PanGestureRecognizer instance) {
|
|
||||||
instance
|
|
||||||
..onDown = _handleDragDown
|
|
||||||
..onStart = _handleDragStart
|
|
||||||
..onUpdate = _handleDragUpdate
|
|
||||||
..onEnd = _handleDragEnd
|
|
||||||
..onCancel = _handleDragCancel
|
|
||||||
..minFlingDistance = _physics?.minFlingDistance
|
|
||||||
..minFlingVelocity = _physics?.minFlingVelocity
|
|
||||||
..maxFlingVelocity = _physics?.maxFlingVelocity
|
|
||||||
..velocityTrackerBuilder =
|
|
||||||
_configuration.velocityTrackerBuilder(context)
|
|
||||||
..dragStartBehavior = widget.dragStartBehavior
|
|
||||||
..gestureSettings = _mediaQueryGestureSettings;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
};
|
|
||||||
// Cancel the active hold/drag (if any) because the gesture recognizers
|
|
||||||
// will soon be disposed by our RawGestureDetector, and we won't be
|
|
||||||
// receiving pointer up events to cancel the hold/drag.
|
|
||||||
_handleDragCancel();
|
|
||||||
_lastCanDrag = value;
|
|
||||||
_lastAxisDirection = widget.axis;
|
|
||||||
if (_gestureDetectorKey.currentState != null) {
|
|
||||||
_gestureDetectorKey.currentState!
|
|
||||||
.replaceGestureRecognizers(_gestureRecognizers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget _buildChrome(BuildContext context, Widget child) {
|
|
||||||
final ScrollableDetails details = ScrollableDetails(
|
|
||||||
direction: widget.axisDirection,
|
|
||||||
controller: _effectiveScrollController,
|
|
||||||
clipBehavior: widget.clipBehavior,
|
|
||||||
);
|
|
||||||
// Skip building a scrollbar here, the dual scrollbar is added in
|
|
||||||
// TwoDimensionalScrollableState.
|
|
||||||
return _configuration.buildOverscrollIndicator(context, child, details);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Horizontal inner scrollable of 2D scrolling
|
|
||||||
class _HorizontalInnerDimension extends CustomScrollable {
|
|
||||||
const _HorizontalInnerDimension({
|
|
||||||
super.key,
|
|
||||||
required this.verticalOuterKey,
|
|
||||||
required super.viewportBuilder,
|
|
||||||
required super.axisDirection,
|
|
||||||
super.controller,
|
|
||||||
super.physics,
|
|
||||||
super.clipBehavior,
|
|
||||||
super.incrementCalculator,
|
|
||||||
super.excludeFromSemantics,
|
|
||||||
super.dragStartBehavior,
|
|
||||||
super.restorationId,
|
|
||||||
super.hitTestBehavior,
|
|
||||||
this.diagonalDragBehavior = DiagonalDragBehavior.none,
|
|
||||||
}) : assert(axisDirection == AxisDirection.left ||
|
|
||||||
axisDirection == AxisDirection.right);
|
|
||||||
|
|
||||||
final GlobalKey<CustomScrollableState> verticalOuterKey;
|
|
||||||
final DiagonalDragBehavior diagonalDragBehavior;
|
|
||||||
|
|
||||||
@override
|
|
||||||
_HorizontalInnerDimensionState createState() =>
|
|
||||||
_HorizontalInnerDimensionState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _HorizontalInnerDimensionState extends CustomScrollableState {
|
|
||||||
late CustomScrollableState verticalScrollable;
|
|
||||||
|
|
||||||
GlobalKey<CustomScrollableState> get verticalOuterKey =>
|
|
||||||
(widget as _HorizontalInnerDimension).verticalOuterKey;
|
|
||||||
DiagonalDragBehavior get diagonalDragBehavior =>
|
|
||||||
(widget as _HorizontalInnerDimension).diagonalDragBehavior;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didChangeDependencies() {
|
|
||||||
verticalScrollable = CustomScrollable.of(context);
|
|
||||||
assert(
|
|
||||||
axisDirectionToAxis(verticalScrollable.axisDirection) == Axis.vertical);
|
|
||||||
super.didChangeDependencies();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the Future from calling ensureVisible for the ScrollPosition, as
|
|
||||||
// as well as the vertical ScrollableState instance so its context can be
|
|
||||||
// used to check for other ancestor Scrollables in executing ensureVisible.
|
|
||||||
@override
|
|
||||||
_EnsureVisibleResults _performEnsureVisible(
|
|
||||||
RenderObject object, {
|
|
||||||
double alignment = 0.0,
|
|
||||||
Duration duration = Duration.zero,
|
|
||||||
Curve curve = Curves.ease,
|
|
||||||
ScrollPositionAlignmentPolicy alignmentPolicy =
|
|
||||||
ScrollPositionAlignmentPolicy.explicit,
|
|
||||||
RenderObject? targetRenderObject,
|
|
||||||
}) {
|
|
||||||
final List<Future<void>> newFutures = <Future<void>>[
|
|
||||||
position.ensureVisible(
|
|
||||||
object,
|
|
||||||
alignment: alignment,
|
|
||||||
duration: duration,
|
|
||||||
curve: curve,
|
|
||||||
alignmentPolicy: alignmentPolicy,
|
|
||||||
),
|
|
||||||
verticalScrollable.position.ensureVisible(
|
|
||||||
object,
|
|
||||||
alignment: alignment,
|
|
||||||
duration: duration,
|
|
||||||
curve: curve,
|
|
||||||
alignmentPolicy: alignmentPolicy,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
return (newFutures, verticalScrollable);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void setCanDrag(bool value) {
|
|
||||||
switch (diagonalDragBehavior) {
|
|
||||||
case DiagonalDragBehavior.none:
|
|
||||||
// If we aren't scrolling diagonally, the default drag gesture
|
|
||||||
// recognizer is used.
|
|
||||||
super.setCanDrag(value);
|
|
||||||
return;
|
|
||||||
case DiagonalDragBehavior.weightedEvent:
|
|
||||||
case DiagonalDragBehavior.weightedContinuous:
|
|
||||||
case DiagonalDragBehavior.free:
|
|
||||||
if (value) {
|
|
||||||
// If a type of diagonal scrolling is enabled, a panning gesture
|
|
||||||
// recognizer will be created for the _VerticalOuterDimension. So in
|
|
||||||
// this case, the _HorizontalInnerDimension does not require a gesture
|
|
||||||
// recognizer, meanwhile we should ensure the outer dimension has
|
|
||||||
// updated in case it did not have enough content to enable dragging.
|
|
||||||
_gestureRecognizers = const <Type, GestureRecognizerFactory>{};
|
|
||||||
verticalOuterKey.currentState!.setCanDrag(value);
|
|
||||||
// Cancel the active hold/drag (if any) because the gesture recognizers
|
|
||||||
// will soon be disposed by our RawGestureDetector, and we won't be
|
|
||||||
// receiving pointer up events to cancel the hold/drag.
|
|
||||||
_handleDragCancel();
|
|
||||||
_lastCanDrag = value;
|
|
||||||
_lastAxisDirection = widget.axis;
|
|
||||||
if (_gestureDetectorKey.currentState != null) {
|
|
||||||
_gestureDetectorKey.currentState!
|
|
||||||
.replaceGestureRecognizers(_gestureRecognizers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget _buildChrome(BuildContext context, Widget child) {
|
|
||||||
final ScrollableDetails details = ScrollableDetails(
|
|
||||||
direction: widget.axisDirection,
|
|
||||||
controller: _effectiveScrollController,
|
|
||||||
clipBehavior: widget.clipBehavior,
|
|
||||||
);
|
|
||||||
// Skip building a scrollbar here, the dual scrollbar is added in
|
|
||||||
// TwoDimensionalScrollableState.
|
|
||||||
return _configuration.buildOverscrollIndicator(context, child, details);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An auto scroller that scrolls the [scrollable] if a drag gesture drags close
|
/// An auto scroller that scrolls the [scrollable] if a drag gesture drags close
|
||||||
/// to its edge.
|
/// to its edge.
|
||||||
///
|
///
|
||||||
@@ -2894,11 +2123,3 @@ class EdgeDraggingAutoScroller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A typedef for a function that can calculate the offset for a type of scroll
|
|
||||||
/// increment given a [ScrollIncrementDetails].
|
|
||||||
///
|
|
||||||
/// This function is used as the type for [CustomScrollable.incrementCalculator],
|
|
||||||
/// which is called from a [ScrollAction].
|
|
||||||
typedef ScrollIncrementCalculator = double Function(
|
|
||||||
ScrollIncrementDetails details);
|
|
||||||
|
|||||||
Reference in New Issue
Block a user