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 'layout_builder.dart';
6/// @docImport 'page_storage.dart';
7/// @docImport 'page_view.dart';
8library;
9
10import 'dart:math' as math;
11
12import 'package:flutter/gestures.dart' show DragStartBehavior;
13import 'package:flutter/rendering.dart';
14
15import 'basic.dart';
16import 'focus_manager.dart';
17import 'focus_scope.dart';
18import 'framework.dart';
19import 'notification_listener.dart';
20import 'primary_scroll_controller.dart';
21import 'scroll_configuration.dart';
22import 'scroll_controller.dart';
23import 'scroll_notification.dart';
24import 'scroll_physics.dart';
25import 'scroll_view.dart';
26import 'scrollable.dart';
27
28/// A box in which a single widget can be scrolled.
29///
30/// This widget is useful when you have a single box that will normally be
31/// entirely visible, for example a clock face in a time picker, but you need to
32/// make sure it can be scrolled if the container gets too small in one axis
33/// (the scroll direction).
34///
35/// It is also useful if you need to shrink-wrap in both axes (the main
36/// scrolling direction as well as the cross axis), as one might see in a dialog
37/// or pop-up menu. In that case, you might pair the [SingleChildScrollView]
38/// with a [ListBody] child.
39///
40/// When you have a list of children and do not require cross-axis
41/// shrink-wrapping behavior, for example a scrolling list that is always the
42/// width of the screen, consider [ListView], which is vastly more efficient
43/// than a [SingleChildScrollView] containing a [ListBody] or [Column] with
44/// many children.
45///
46/// ## Sample code: Using [SingleChildScrollView] with a [Column]
47///
48/// Sometimes a layout is designed around the flexible properties of a
49/// [Column], but there is the concern that in some cases, there might not
50/// be enough room to see the entire contents. This could be because some
51/// devices have unusually small screens, or because the application can
52/// be used in landscape mode where the aspect ratio isn't what was
53/// originally envisioned, or because the application is being shown in a
54/// small window in split-screen mode. In any case, as a result, it might
55/// make sense to wrap the layout in a [SingleChildScrollView].
56///
57/// Doing so, however, usually results in a conflict between the [Column],
58/// which typically tries to grow as big as it can, and the [SingleChildScrollView],
59/// which provides its children with an infinite amount of space.
60///
61/// To resolve this apparent conflict, there are a couple of techniques, as
62/// discussed below. These techniques should only be used when the content is
63/// normally expected to fit on the screen, so that the lazy instantiation of a
64/// sliver-based [ListView] or [CustomScrollView] is not expected to provide any
65/// performance benefit. If the viewport is expected to usually contain content
66/// beyond the dimensions of the screen, then [SingleChildScrollView] would be
67/// very expensive (in which case [ListView] may be a better choice than
68/// [Column]).
69///
70/// ### Centering, spacing, or aligning fixed-height content
71///
72/// If the content has fixed (or intrinsic) dimensions but needs to be spaced out,
73/// centered, or otherwise positioned using the [Flex] layout model of a [Column],
74/// the following technique can be used to provide the [Column] with a minimum
75/// dimension while allowing it to shrink-wrap the contents when there isn't enough
76/// room to apply these spacing or alignment needs.
77///
78/// A [LayoutBuilder] is used to obtain the size of the viewport (implicitly via
79/// the constraints that the [SingleChildScrollView] sees, since viewports
80/// typically grow to fit their maximum height constraint). Then, inside the
81/// scroll view, a [ConstrainedBox] is used to set the minimum height of the
82/// [Column].
83///
84/// The [Column] has no [Expanded] children, so rather than take on the infinite
85/// height from its [BoxConstraints.maxHeight], (the viewport provides no maximum height
86/// constraint), it automatically tries to shrink to fit its children. It cannot
87/// be smaller than its [BoxConstraints.minHeight], though, and It therefore
88/// becomes the bigger of the minimum height provided by the
89/// [ConstrainedBox] and the sum of the heights of the children.
90///
91/// If the children aren't enough to fit that minimum size, the [Column] ends up
92/// with some remaining space to allocate as specified by its
93/// [Column.mainAxisAlignment] argument.
94///
95/// {@tool dartpad}
96/// In this example, the children are spaced out equally, unless there's no more
97/// room, in which case they stack vertically and scroll.
98///
99/// When using this technique, [Expanded] and [Flexible] are not useful, because
100/// in both cases the "available space" is infinite (since this is in a viewport).
101/// The next section describes a technique for providing a maximum height constraint.
102///
103/// ** See code in examples/api/lib/widgets/single_child_scroll_view/single_child_scroll_view.0.dart **
104/// {@end-tool}
105///
106/// ### Expanding content to fit the viewport
107///
108/// The following example builds on the previous one. In addition to providing a
109/// minimum dimension for the child [Column], an [IntrinsicHeight] widget is used
110/// to force the column to be exactly as big as its contents. This constraint
111/// combines with the [ConstrainedBox] constraints discussed previously to ensure
112/// that the column becomes either as big as viewport, or as big as the contents,
113/// whichever is biggest.
114///
115/// Both constraints must be used to get the desired effect. If only the
116/// [IntrinsicHeight] was specified, then the column would not grow to fit the
117/// entire viewport when its children were smaller than the whole screen. If only
118/// the size of the viewport was used, then the [Column] would overflow if the
119/// children were bigger than the viewport.
120///
121/// The widget that is to grow to fit the remaining space so provided is wrapped
122/// in an [Expanded] widget.
123///
124/// This technique is quite expensive, as it more or less requires that the contents
125/// of the viewport be laid out twice (once to find their intrinsic dimensions, and
126/// once to actually lay them out). The number of widgets within the column should
127/// therefore be kept small. Alternatively, subsets of the children that have known
128/// dimensions can be wrapped in a [SizedBox] that has tight vertical constraints,
129/// so that the intrinsic sizing algorithm can short-circuit the computation when it
130/// reaches those parts of the subtree.
131///
132/// {@tool dartpad}
133/// In this example, the column becomes either as big as viewport, or as big as
134/// the contents, whichever is biggest.
135///
136/// ** See code in examples/api/lib/widgets/single_child_scroll_view/single_child_scroll_view.1.dart **
137/// {@end-tool}
138///
139/// {@macro flutter.widgets.ScrollView.PageStorage}
140///
141/// See also:
142///
143/// * [ListView], which handles multiple children in a scrolling list.
144/// * [GridView], which handles multiple children in a scrolling grid.
145/// * [PageView], for a scrollable that works page by page.
146/// * [Scrollable], which handles arbitrary scrolling effects.
147class SingleChildScrollView extends StatelessWidget {
148 /// Creates a box in which a single widget can be scrolled.
149 const SingleChildScrollView({
150 super.key,
151 this.scrollDirection = Axis.vertical,
152 this.reverse = false,
153 this.padding,
154 this.primary,
155 this.physics,
156 this.controller,
157 this.child,
158 this.dragStartBehavior = DragStartBehavior.start,
159 this.clipBehavior = Clip.hardEdge,
160 this.hitTestBehavior = HitTestBehavior.opaque,
161 this.restorationId,
162 this.keyboardDismissBehavior,
163 }) : assert(
164 !(controller != null && (primary ?? false)),
165 'Primary ScrollViews obtain their ScrollController via inheritance '
166 'from a PrimaryScrollController widget. You cannot both set primary to '
167 'true and pass an explicit controller.',
168 );
169
170 /// {@macro flutter.widgets.scroll_view.scrollDirection}
171 final Axis scrollDirection;
172
173 /// Whether the scroll view scrolls in the reading direction.
174 ///
175 /// For example, if the reading direction is left-to-right and
176 /// [scrollDirection] is [Axis.horizontal], then the scroll view scrolls from
177 /// left to right when [reverse] is false and from right to left when
178 /// [reverse] is true.
179 ///
180 /// Similarly, if [scrollDirection] is [Axis.vertical], then the scroll view
181 /// scrolls from top to bottom when [reverse] is false and from bottom to top
182 /// when [reverse] is true.
183 ///
184 /// Defaults to false.
185 final bool reverse;
186
187 /// The amount of space by which to inset the child.
188 final EdgeInsetsGeometry? padding;
189
190 /// An object that can be used to control the position to which this scroll
191 /// view is scrolled.
192 ///
193 /// Must be null if [primary] is true.
194 ///
195 /// A [ScrollController] serves several purposes. It can be used to control
196 /// the initial scroll position (see [ScrollController.initialScrollOffset]).
197 /// It can be used to control whether the scroll view should automatically
198 /// save and restore its scroll position in the [PageStorage] (see
199 /// [ScrollController.keepScrollOffset]). It can be used to read the current
200 /// scroll position (see [ScrollController.offset]), or change it (see
201 /// [ScrollController.animateTo]).
202 final ScrollController? controller;
203
204 /// {@macro flutter.widgets.scroll_view.primary}
205 final bool? primary;
206
207 /// How the scroll view should respond to user input.
208 ///
209 /// For example, determines how the scroll view continues to animate after the
210 /// user stops dragging the scroll view.
211 ///
212 /// Defaults to matching platform conventions.
213 final ScrollPhysics? physics;
214
215 /// The widget that scrolls.
216 ///
217 /// {@macro flutter.widgets.ProxyWidget.child}
218 final Widget? child;
219
220 /// {@macro flutter.widgets.scrollable.dragStartBehavior}
221 final DragStartBehavior dragStartBehavior;
222
223 /// {@macro flutter.material.Material.clipBehavior}
224 ///
225 /// Defaults to [Clip.hardEdge].
226 final Clip clipBehavior;
227
228 /// {@macro flutter.widgets.scrollable.hitTestBehavior}
229 ///
230 /// Defaults to [HitTestBehavior.opaque].
231 final HitTestBehavior hitTestBehavior;
232
233 /// {@macro flutter.widgets.scrollable.restorationId}
234 final String? restorationId;
235
236 /// {@macro flutter.widgets.scroll_view.keyboardDismissBehavior}
237 ///
238 /// If [keyboardDismissBehavior] is null then it will fallback to the inherited
239 /// [ScrollBehavior.getKeyboardDismissBehavior].
240 final ScrollViewKeyboardDismissBehavior? keyboardDismissBehavior;
241
242 AxisDirection _getDirection(BuildContext context) {
243 return getAxisDirectionFromAxisReverseAndDirectionality(context, scrollDirection, reverse);
244 }
245
246 @override
247 Widget build(BuildContext context) {
248 final AxisDirection axisDirection = _getDirection(context);
249 Widget? contents = child;
250 if (padding != null) {
251 contents = Padding(padding: padding!, child: contents);
252 }
253 final bool effectivePrimary =
254 primary ??
255 controller == null && PrimaryScrollController.shouldInherit(context, scrollDirection);
256
257 final ScrollController? scrollController =
258 effectivePrimary ? PrimaryScrollController.maybeOf(context) : controller;
259
260 Widget scrollable = Scrollable(
261 dragStartBehavior: dragStartBehavior,
262 axisDirection: axisDirection,
263 controller: scrollController,
264 physics: physics,
265 restorationId: restorationId,
266 clipBehavior: clipBehavior,
267 hitTestBehavior: hitTestBehavior,
268 viewportBuilder: (BuildContext context, ViewportOffset offset) {
269 return _SingleChildViewport(
270 axisDirection: axisDirection,
271 offset: offset,
272 clipBehavior: clipBehavior,
273 child: contents,
274 );
275 },
276 );
277
278 final ScrollViewKeyboardDismissBehavior effectiveKeyboardDismissBehavior =
279 keyboardDismissBehavior ??
280 ScrollConfiguration.of(context).getKeyboardDismissBehavior(context);
281
282 if (effectiveKeyboardDismissBehavior == ScrollViewKeyboardDismissBehavior.onDrag) {
283 scrollable = NotificationListener<ScrollUpdateNotification>(
284 child: scrollable,
285 onNotification: (ScrollUpdateNotification notification) {
286 final FocusScopeNode currentScope = FocusScope.of(context);
287 if (notification.dragDetails != null &&
288 !currentScope.hasPrimaryFocus &&
289 currentScope.hasFocus) {
290 FocusManager.instance.primaryFocus?.unfocus();
291 }
292 return false;
293 },
294 );
295 }
296
297 return effectivePrimary && scrollController != null
298 // Further descendant ScrollViews will not inherit the same
299 // PrimaryScrollController
300 ? PrimaryScrollController.none(child: scrollable)
301 : scrollable;
302 }
303}
304
305class _SingleChildViewport extends SingleChildRenderObjectWidget {
306 const _SingleChildViewport({
307 this.axisDirection = AxisDirection.down,
308 required this.offset,
309 super.child,
310 required this.clipBehavior,
311 });
312
313 final AxisDirection axisDirection;
314 final ViewportOffset offset;
315 final Clip clipBehavior;
316
317 @override
318 _RenderSingleChildViewport createRenderObject(BuildContext context) {
319 return _RenderSingleChildViewport(
320 axisDirection: axisDirection,
321 offset: offset,
322 clipBehavior: clipBehavior,
323 );
324 }
325
326 @override
327 void updateRenderObject(BuildContext context, _RenderSingleChildViewport renderObject) {
328 // Order dependency: The offset setter reads the axis direction.
329 renderObject
330 ..axisDirection = axisDirection
331 ..offset = offset
332 ..clipBehavior = clipBehavior;
333 }
334
335 @override
336 SingleChildRenderObjectElement createElement() {
337 return _SingleChildViewportElement(this);
338 }
339}
340
341class _SingleChildViewportElement extends SingleChildRenderObjectElement
342 with NotifiableElementMixin, ViewportElementMixin {
343 _SingleChildViewportElement(_SingleChildViewport super.widget);
344}
345
346class _RenderSingleChildViewport extends RenderBox
347 with RenderObjectWithChildMixin<RenderBox>
348 implements RenderAbstractViewport {
349 _RenderSingleChildViewport({
350 AxisDirection axisDirection = AxisDirection.down,
351 required ViewportOffset offset,
352 RenderBox? child,
353 required Clip clipBehavior,
354 }) : _axisDirection = axisDirection,
355 _offset = offset,
356 _clipBehavior = clipBehavior {
357 this.child = child;
358 }
359
360 AxisDirection get axisDirection => _axisDirection;
361 AxisDirection _axisDirection;
362 set axisDirection(AxisDirection value) {
363 if (value == _axisDirection) {
364 return;
365 }
366 _axisDirection = value;
367 markNeedsLayout();
368 }
369
370 Axis get axis => axisDirectionToAxis(axisDirection);
371
372 ViewportOffset get offset => _offset;
373 ViewportOffset _offset;
374 set offset(ViewportOffset value) {
375 if (value == _offset) {
376 return;
377 }
378 if (attached) {
379 _offset.removeListener(_hasScrolled);
380 }
381 _offset = value;
382 if (attached) {
383 _offset.addListener(_hasScrolled);
384 }
385 markNeedsLayout();
386 }
387
388 /// {@macro flutter.material.Material.clipBehavior}
389 ///
390 /// Defaults to [Clip.none].
391 Clip get clipBehavior => _clipBehavior;
392 Clip _clipBehavior = Clip.none;
393 set clipBehavior(Clip value) {
394 if (value != _clipBehavior) {
395 _clipBehavior = value;
396 markNeedsPaint();
397 markNeedsSemanticsUpdate();
398 }
399 }
400
401 void _hasScrolled() {
402 markNeedsPaint();
403 markNeedsSemanticsUpdate();
404 }
405
406 @override
407 void setupParentData(RenderObject child) {
408 // We don't actually use the offset argument in BoxParentData, so let's
409 // avoid allocating it at all.
410 if (child.parentData is! ParentData) {
411 child.parentData = ParentData();
412 }
413 }
414
415 @override
416 void attach(PipelineOwner owner) {
417 super.attach(owner);
418 _offset.addListener(_hasScrolled);
419 }
420
421 @override
422 void detach() {
423 _offset.removeListener(_hasScrolled);
424 super.detach();
425 }
426
427 @override
428 bool get isRepaintBoundary => true;
429
430 double get _viewportExtent {
431 assert(hasSize);
432 return switch (axis) {
433 Axis.horizontal => size.width,
434 Axis.vertical => size.height,
435 };
436 }
437
438 double get _minScrollExtent {
439 assert(hasSize);
440 return 0.0;
441 }
442
443 double get _maxScrollExtent {
444 assert(hasSize);
445 if (child == null) {
446 return 0.0;
447 }
448 return math.max(0.0, switch (axis) {
449 Axis.horizontal => child!.size.width - size.width,
450 Axis.vertical => child!.size.height - size.height,
451 });
452 }
453
454 BoxConstraints _getInnerConstraints(BoxConstraints constraints) {
455 return switch (axis) {
456 Axis.horizontal => constraints.heightConstraints(),
457 Axis.vertical => constraints.widthConstraints(),
458 };
459 }
460
461 @override
462 double computeMinIntrinsicWidth(double height) {
463 return child?.getMinIntrinsicWidth(height) ?? 0.0;
464 }
465
466 @override
467 double computeMaxIntrinsicWidth(double height) {
468 return child?.getMaxIntrinsicWidth(height) ?? 0.0;
469 }
470
471 @override
472 double computeMinIntrinsicHeight(double width) {
473 return child?.getMinIntrinsicHeight(width) ?? 0.0;
474 }
475
476 @override
477 double computeMaxIntrinsicHeight(double width) {
478 return child?.getMaxIntrinsicHeight(width) ?? 0.0;
479 }
480
481 // We don't override computeDistanceToActualBaseline(), because we
482 // want the default behavior (returning null). Otherwise, as you
483 // scroll, it would shift in its parent if the parent was baseline-aligned,
484 // which makes no sense.
485
486 @override
487 Size computeDryLayout(BoxConstraints constraints) {
488 if (child == null) {
489 return constraints.smallest;
490 }
491 final Size childSize = child!.getDryLayout(_getInnerConstraints(constraints));
492 return constraints.constrain(childSize);
493 }
494
495 @override
496 void performLayout() {
497 final BoxConstraints constraints = this.constraints;
498 if (child == null) {
499 size = constraints.smallest;
500 } else {
501 child!.layout(_getInnerConstraints(constraints), parentUsesSize: true);
502 size = constraints.constrain(child!.size);
503 }
504
505 if (offset.hasPixels) {
506 if (offset.pixels > _maxScrollExtent) {
507 offset.correctBy(_maxScrollExtent - offset.pixels);
508 } else if (offset.pixels < _minScrollExtent) {
509 offset.correctBy(_minScrollExtent - offset.pixels);
510 }
511 }
512
513 offset.applyViewportDimension(_viewportExtent);
514 offset.applyContentDimensions(_minScrollExtent, _maxScrollExtent);
515 }
516
517 Offset get _paintOffset => _paintOffsetForPosition(offset.pixels);
518
519 Offset _paintOffsetForPosition(double position) {
520 return switch (axisDirection) {
521 AxisDirection.up => Offset(0.0, position - child!.size.height + size.height),
522 AxisDirection.left => Offset(position - child!.size.width + size.width, 0.0),
523 AxisDirection.right => Offset(-position, 0.0),
524 AxisDirection.down => Offset(0.0, -position),
525 };
526 }
527
528 bool _shouldClipAtPaintOffset(Offset paintOffset) {
529 assert(child != null);
530 switch (clipBehavior) {
531 case Clip.none:
532 return false;
533 case Clip.hardEdge:
534 case Clip.antiAlias:
535 case Clip.antiAliasWithSaveLayer:
536 return paintOffset.dx < 0 ||
537 paintOffset.dy < 0 ||
538 paintOffset.dx + child!.size.width > size.width ||
539 paintOffset.dy + child!.size.height > size.height;
540 }
541 }
542
543 @override
544 void paint(PaintingContext context, Offset offset) {
545 if (child != null) {
546 final Offset paintOffset = _paintOffset;
547
548 void paintContents(PaintingContext context, Offset offset) {
549 context.paintChild(child!, offset + paintOffset);
550 }
551
552 if (_shouldClipAtPaintOffset(paintOffset)) {
553 _clipRectLayer.layer = context.pushClipRect(
554 needsCompositing,
555 offset,
556 Offset.zero & size,
557 paintContents,
558 clipBehavior: clipBehavior,
559 oldLayer: _clipRectLayer.layer,
560 );
561 } else {
562 _clipRectLayer.layer = null;
563 paintContents(context, offset);
564 }
565 }
566 }
567
568 final LayerHandle<ClipRectLayer> _clipRectLayer = LayerHandle<ClipRectLayer>();
569
570 @override
571 void dispose() {
572 _clipRectLayer.layer = null;
573 super.dispose();
574 }
575
576 @override
577 void applyPaintTransform(RenderBox child, Matrix4 transform) {
578 final Offset paintOffset = _paintOffset;
579 transform.translate(paintOffset.dx, paintOffset.dy);
580 }
581
582 @override
583 Rect? describeApproximatePaintClip(RenderObject? child) {
584 if (child != null && _shouldClipAtPaintOffset(_paintOffset)) {
585 return Offset.zero & size;
586 }
587 return null;
588 }
589
590 @override
591 bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
592 if (child != null) {
593 return result.addWithPaintOffset(
594 offset: _paintOffset,
595 position: position,
596 hitTest: (BoxHitTestResult result, Offset transformed) {
597 assert(transformed == position + -_paintOffset);
598 return child!.hitTest(result, position: transformed);
599 },
600 );
601 }
602 return false;
603 }
604
605 @override
606 RevealedOffset getOffsetToReveal(
607 RenderObject target,
608 double alignment, {
609 Rect? rect,
610 Axis? axis,
611 }) {
612 // One dimensional viewport has only one axis, override if it was
613 // provided/may be mismatched.
614 axis = this.axis;
615
616 rect ??= target.paintBounds;
617 if (target is! RenderBox) {
618 return RevealedOffset(offset: offset.pixels, rect: rect);
619 }
620
621 final RenderBox targetBox = target;
622 final Matrix4 transform = targetBox.getTransformTo(child);
623 final Rect bounds = MatrixUtils.transformRect(transform, rect);
624 final Size contentSize = child!.size;
625
626 final (
627 double mainAxisExtent,
628 double leadingScrollOffset,
629 double targetMainAxisExtent,
630 ) = switch (axisDirection) {
631 AxisDirection.up => (size.height, contentSize.height - bounds.bottom, bounds.height),
632 AxisDirection.left => (size.width, contentSize.width - bounds.right, bounds.width),
633 AxisDirection.right => (size.width, bounds.left, bounds.width),
634 AxisDirection.down => (size.height, bounds.top, bounds.height),
635 };
636
637 final double targetOffset =
638 leadingScrollOffset - (mainAxisExtent - targetMainAxisExtent) * alignment;
639 final Rect targetRect = bounds.shift(_paintOffsetForPosition(targetOffset));
640 return RevealedOffset(offset: targetOffset, rect: targetRect);
641 }
642
643 @override
644 void showOnScreen({
645 RenderObject? descendant,
646 Rect? rect,
647 Duration duration = Duration.zero,
648 Curve curve = Curves.ease,
649 }) {
650 if (!offset.allowImplicitScrolling) {
651 return super.showOnScreen(
652 descendant: descendant,
653 rect: rect,
654 duration: duration,
655 curve: curve,
656 );
657 }
658
659 final Rect? newRect = RenderViewportBase.showInViewport(
660 descendant: descendant,
661 viewport: this,
662 offset: offset,
663 rect: rect,
664 duration: duration,
665 curve: curve,
666 );
667 super.showOnScreen(rect: newRect, duration: duration, curve: curve);
668 }
669
670 @override
671 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
672 super.debugFillProperties(properties);
673 properties.add(DiagnosticsProperty<Offset>('offset', _paintOffset));
674 }
675
676 @override
677 Rect describeSemanticsClip(RenderObject child) {
678 final double remainingOffset = _maxScrollExtent - offset.pixels;
679 switch (axisDirection) {
680 case AxisDirection.up:
681 return Rect.fromLTRB(
682 semanticBounds.left,
683 semanticBounds.top - remainingOffset,
684 semanticBounds.right,
685 semanticBounds.bottom + offset.pixels,
686 );
687 case AxisDirection.right:
688 return Rect.fromLTRB(
689 semanticBounds.left - offset.pixels,
690 semanticBounds.top,
691 semanticBounds.right + remainingOffset,
692 semanticBounds.bottom,
693 );
694 case AxisDirection.down:
695 return Rect.fromLTRB(
696 semanticBounds.left,
697 semanticBounds.top - offset.pixels,
698 semanticBounds.right,
699 semanticBounds.bottom + remainingOffset,
700 );
701 case AxisDirection.left:
702 return Rect.fromLTRB(
703 semanticBounds.left - remainingOffset,
704 semanticBounds.top,
705 semanticBounds.right + offset.pixels,
706 semanticBounds.bottom,
707 );
708 }
709 }
710}
711

Provided by KDAB

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