1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/// @docImport 'scroll_position.dart';
6library;
7
8import 'package:flutter/gestures.dart';
9import 'package:flutter/rendering.dart';
10
11import 'focus_manager.dart';
12import 'focus_scope.dart';
13import 'framework.dart';
14import 'notification_listener.dart';
15import 'primary_scroll_controller.dart';
16import 'scroll_configuration.dart';
17import 'scroll_controller.dart';
18import 'scroll_delegate.dart';
19import 'scroll_notification.dart';
20import 'scroll_physics.dart';
21import 'scroll_view.dart';
22import 'scrollable.dart';
23import 'scrollable_helpers.dart';
24import 'two_dimensional_viewport.dart';
25
26/// A widget that combines a [TwoDimensionalScrollable] and a
27/// [TwoDimensionalViewport] to create an interactive scrolling pane of content
28/// in both vertical and horizontal dimensions.
29///
30/// A two-way scrollable widget consist of three pieces:
31///
32/// 1. A [TwoDimensionalScrollable] widget, which listens for various user
33/// gestures and implements the interaction design for scrolling.
34/// 2. A [TwoDimensionalViewport] widget, which implements the visual design
35/// for scrolling by displaying only a portion
36/// of the widgets inside the scroll view.
37/// 3. A [TwoDimensionalChildDelegate], which provides the children visible in
38/// the scroll view.
39///
40/// [TwoDimensionalScrollView] helps orchestrate these pieces by creating the
41/// [TwoDimensionalScrollable] and deferring to its subclass to implement
42/// [buildViewport], which builds a subclass of [TwoDimensionalViewport]. The
43/// [TwoDimensionalChildDelegate] is provided by the [delegate] parameter.
44///
45/// A [TwoDimensionalScrollView] has two different [ScrollPosition]s, one for
46/// each [Axis]. This means that there are also two unique [ScrollController]s
47/// for these positions. To provide a ScrollController to access the
48/// ScrollPosition, use the [ScrollableDetails.controller] property of the
49/// associated axis that is provided to this scroll view.
50abstract class TwoDimensionalScrollView extends StatelessWidget {
51 /// Creates a widget that scrolls in both dimensions.
52 ///
53 /// The [primary] argument is associated with the [mainAxis]. The main axis
54 /// [ScrollableDetails.controller] must be null if [primary] is configured for
55 /// that axis. If [primary] is true, the nearest [PrimaryScrollController]
56 /// surrounding the widget is attached to the scroll position of that axis.
57 const TwoDimensionalScrollView({
58 super.key,
59 this.primary,
60 this.mainAxis = Axis.vertical,
61 this.verticalDetails = const ScrollableDetails.vertical(),
62 this.horizontalDetails = const ScrollableDetails.horizontal(),
63 required this.delegate,
64 this.cacheExtent,
65 this.diagonalDragBehavior = DiagonalDragBehavior.none,
66 this.dragStartBehavior = DragStartBehavior.start,
67 this.keyboardDismissBehavior,
68 this.clipBehavior = Clip.hardEdge,
69 this.hitTestBehavior = HitTestBehavior.opaque,
70 });
71
72 /// A delegate that provides the children for the [TwoDimensionalScrollView].
73 final TwoDimensionalChildDelegate delegate;
74
75 /// {@macro flutter.rendering.RenderViewportBase.cacheExtent}
76 final double? cacheExtent;
77
78 /// Whether scrolling gestures should lock to one axes, allow free movement
79 /// in both axes, or be evaluated on a weighted scale.
80 ///
81 /// Defaults to [DiagonalDragBehavior.none], locking axes to receive input one
82 /// at a time.
83 final DiagonalDragBehavior diagonalDragBehavior;
84
85 /// {@macro flutter.widgets.scroll_view.primary}
86 final bool? primary;
87
88 /// The main axis of the two.
89 ///
90 /// Used to determine how to apply [primary] when true.
91 ///
92 /// This value should also be provided to the subclass of
93 /// [TwoDimensionalViewport], where it is used to determine paint order of
94 /// children.
95 final Axis mainAxis;
96
97 /// The configuration of the vertical Scrollable.
98 ///
99 /// These [ScrollableDetails] can be used to set the [AxisDirection],
100 /// [ScrollController], [ScrollPhysics] and more for the vertical axis.
101 final ScrollableDetails verticalDetails;
102
103 /// The configuration of the horizontal Scrollable.
104 ///
105 /// These [ScrollableDetails] can be used to set the [AxisDirection],
106 /// [ScrollController], [ScrollPhysics] and more for the horizontal axis.
107 final ScrollableDetails horizontalDetails;
108
109 /// {@macro flutter.widgets.scrollable.dragStartBehavior}
110 final DragStartBehavior dragStartBehavior;
111
112 /// {@macro flutter.widgets.scroll_view.keyboardDismissBehavior}
113 ///
114 /// If [keyboardDismissBehavior] is null then it will fallback to the inherited
115 /// [ScrollBehavior.getKeyboardDismissBehavior].
116 final ScrollViewKeyboardDismissBehavior? keyboardDismissBehavior;
117
118 /// {@macro flutter.widgets.scrollable.hitTestBehavior}
119 ///
120 /// This value applies to both axes.
121 final HitTestBehavior hitTestBehavior;
122
123 /// {@macro flutter.material.Material.clipBehavior}
124 ///
125 /// Defaults to [Clip.hardEdge].
126 final Clip clipBehavior;
127
128 /// Build the two dimensional viewport.
129 ///
130 /// Subclasses may override this method to change how the viewport is built,
131 /// likely a subclass of [TwoDimensionalViewport].
132 ///
133 /// The `verticalOffset` and `horizontalOffset` arguments are the values
134 /// obtained from [TwoDimensionalScrollable.viewportBuilder].
135 Widget buildViewport(
136 BuildContext context,
137 ViewportOffset verticalOffset,
138 ViewportOffset horizontalOffset,
139 );
140
141 @override
142 Widget build(BuildContext context) {
143 assert(
144 axisDirectionToAxis(verticalDetails.direction) == Axis.vertical,
145 'TwoDimensionalScrollView.verticalDetails are not Axis.vertical.',
146 );
147 assert(
148 axisDirectionToAxis(horizontalDetails.direction) == Axis.horizontal,
149 'TwoDimensionalScrollView.horizontalDetails are not Axis.horizontal.',
150 );
151
152 ScrollableDetails mainAxisDetails = switch (mainAxis) {
153 Axis.vertical => verticalDetails,
154 Axis.horizontal => horizontalDetails,
155 };
156
157 final bool effectivePrimary =
158 primary ??
159 mainAxisDetails.controller == null &&
160 PrimaryScrollController.shouldInherit(context, mainAxis);
161
162 if (effectivePrimary) {
163 // Using PrimaryScrollController for mainAxis.
164 assert(
165 mainAxisDetails.controller == null,
166 'TwoDimensionalScrollView.primary was explicitly set to true, but a '
167 'ScrollController was provided in the ScrollableDetails of the '
168 'TwoDimensionalScrollView.mainAxis.',
169 );
170 mainAxisDetails = mainAxisDetails.copyWith(controller: PrimaryScrollController.of(context));
171 }
172
173 final TwoDimensionalScrollable scrollable = TwoDimensionalScrollable(
174 horizontalDetails: switch (mainAxis) {
175 Axis.horizontal => mainAxisDetails,
176 Axis.vertical => horizontalDetails,
177 },
178 verticalDetails: switch (mainAxis) {
179 Axis.vertical => mainAxisDetails,
180 Axis.horizontal => verticalDetails,
181 },
182 diagonalDragBehavior: diagonalDragBehavior,
183 viewportBuilder: buildViewport,
184 dragStartBehavior: dragStartBehavior,
185 hitTestBehavior: hitTestBehavior,
186 );
187
188 final Widget scrollableResult =
189 effectivePrimary
190 // Further descendant ScrollViews will not inherit the same PrimaryScrollController
191 ? PrimaryScrollController.none(child: scrollable)
192 : scrollable;
193
194 final ScrollViewKeyboardDismissBehavior effectiveKeyboardDismissBehavior =
195 keyboardDismissBehavior ??
196 ScrollConfiguration.of(context).getKeyboardDismissBehavior(context);
197
198 if (effectiveKeyboardDismissBehavior == ScrollViewKeyboardDismissBehavior.onDrag) {
199 return NotificationListener<ScrollUpdateNotification>(
200 child: scrollableResult,
201 onNotification: (ScrollUpdateNotification notification) {
202 final FocusScopeNode currentScope = FocusScope.of(context);
203 if (notification.dragDetails != null &&
204 !currentScope.hasPrimaryFocus &&
205 currentScope.hasFocus) {
206 FocusManager.instance.primaryFocus?.unfocus();
207 }
208 return false;
209 },
210 );
211 }
212 return scrollableResult;
213 }
214
215 @override
216 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
217 super.debugFillProperties(properties);
218 properties.add(EnumProperty<Axis>('mainAxis', mainAxis));
219 properties.add(
220 EnumProperty<DiagonalDragBehavior>('diagonalDragBehavior', diagonalDragBehavior),
221 );
222 properties.add(
223 FlagProperty('primary', value: primary, ifTrue: 'using primary controller', showName: true),
224 );
225 properties.add(
226 DiagnosticsProperty<ScrollableDetails>('verticalDetails', verticalDetails, showName: false),
227 );
228 properties.add(
229 DiagnosticsProperty<ScrollableDetails>(
230 'horizontalDetails',
231 horizontalDetails,
232 showName: false,
233 ),
234 );
235 }
236}
237

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com