opt custom widget

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-05-26 22:01:53 +08:00
parent b2a4875ba7
commit 1d91b183fd
2 changed files with 0 additions and 789 deletions

View File

@@ -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

View File

@@ -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);