"Fossies" - the Fresh Open Source Software Archive

Member "flutter-1.22.4/packages/flutter/lib/src/widgets/single_child_scroll_view.dart" (13 Nov 2020, 24980 Bytes) of package /linux/misc/flutter-1.22.4.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Dart source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

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