mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-06 09:13:48 +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/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].
|
||||
///
|
||||
/// 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;
|
||||
|
||||
// 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 list of futures represents each pending ScrollPosition call to
|
||||
@@ -1971,761 +1955,6 @@ enum DiagonalDragBehavior {
|
||||
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
|
||||
/// 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