diff --git a/packages/flutter/lib/src/rendering/viewport.dart b/packages/flutter/lib/src/rendering/viewport.dart index 96ab2f32f9..cbf1e2d942 100644 --- a/packages/flutter/lib/src/rendering/viewport.dart +++ b/packages/flutter/lib/src/rendering/viewport.dart @@ -15,6 +15,14 @@ import 'object.dart'; import 'sliver.dart'; import 'viewport_offset.dart'; +/// The unit of measurement for a [Viewport.cacheExtent]. +enum CacheExtentStyle { + /// Treat the [Viewport.cacheExtent] as logical pixels. + pixel, + /// Treat the [Viewport.cacheExtent] as a multiplier of the main axis extent. + viewport, +} + /// An interface for render objects that are bigger on the inside. /// /// Some render objects, such as [RenderViewport], present a portion of their @@ -75,6 +83,7 @@ abstract class RenderAbstractViewport extends RenderObject { /// /// * [RenderViewportBase.cacheExtent] for a definition of the cache extent. @protected + @visibleForTesting static const double defaultCacheExtent = 250.0; } @@ -160,14 +169,18 @@ abstract class RenderViewportBase _cacheExtentStyle; + CacheExtentStyle _cacheExtentStyle; + set cacheExtentStyle(CacheExtentStyle value) { + assert(value != null); + if (value == _cacheExtentStyle) { + return; + } + _cacheExtentStyle = value; + markNeedsLayout(); + } + @override void attach(PipelineOwner owner) { super.attach(owner); @@ -494,20 +535,25 @@ abstract class RenderViewportBase children, RenderSliver center, double cacheExtent, + CacheExtentStyle cacheExtentStyle = CacheExtentStyle.pixel, }) : assert(anchor != null), assert(anchor >= 0.0 && anchor <= 1.0), + assert(cacheExtentStyle != CacheExtentStyle.viewport || cacheExtent != null), _anchor = anchor, _center = center, - super(axisDirection: axisDirection, crossAxisDirection: crossAxisDirection, offset: offset, cacheExtent: cacheExtent) { + super( + axisDirection: axisDirection, + crossAxisDirection: crossAxisDirection, + offset: offset, + cacheExtent: cacheExtent, + cacheExtentStyle: cacheExtentStyle, + ) { addAll(children); if (center == null && firstChild != null) _center = firstChild; @@ -1337,8 +1391,17 @@ class RenderViewport extends RenderViewportBase slivers = const [], }) : assert(offset != null), assert(slivers != null), assert(center == null || slivers.where((Widget child) => child.key == center).length == 1), + assert(cacheExtentStyle != null), + assert(cacheExtentStyle != CacheExtentStyle.viewport || cacheExtent != null), super(key: key, children: slivers); /// The direction in which the [offset]'s [ViewportOffset.pixels] increases. @@ -112,6 +115,9 @@ class Viewport extends MultiChildRenderObjectWidget { /// {@macro flutter.rendering.viewport.cacheExtent} final double cacheExtent; + /// {@macro flutter.rendering.viewport.cacheExtentStyle} + final CacheExtentStyle cacheExtentStyle; + /// Given a [BuildContext] and an [AxisDirection], determine the correct cross /// axis direction. /// @@ -140,6 +146,7 @@ class Viewport extends MultiChildRenderObjectWidget { anchor: anchor, offset: offset, cacheExtent: cacheExtent, + cacheExtentStyle: cacheExtentStyle, ); } @@ -150,7 +157,8 @@ class Viewport extends MultiChildRenderObjectWidget { ..crossAxisDirection = crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(context, axisDirection) ..anchor = anchor ..offset = offset - ..cacheExtent = cacheExtent; + ..cacheExtent = cacheExtent + ..cacheExtentStyle = cacheExtentStyle; } @override @@ -168,6 +176,8 @@ class Viewport extends MultiChildRenderObjectWidget { } else if (children.isNotEmpty && children.first.key != null) { properties.add(DiagnosticsProperty('center', children.first.key, tooltip: 'implicit')); } + properties.add(DiagnosticsProperty('cacheExtent', cacheExtent)); + properties.add(DiagnosticsProperty('cacheExtentStyle', cacheExtentStyle)); } } diff --git a/packages/flutter/test/rendering/rendering_tester.dart b/packages/flutter/test/rendering/rendering_tester.dart index a1b9657c7a..d78d3d000e 100644 --- a/packages/flutter/test/rendering/rendering_tester.dart +++ b/packages/flutter/test/rendering/rendering_tester.dart @@ -190,7 +190,6 @@ class TestCallbackPainter extends CustomPainter { bool shouldRepaint(TestCallbackPainter oldPainter) => true; } - class RenderSizedBox extends RenderBox { RenderSizedBox(this._size); diff --git a/packages/flutter/test/rendering/viewport_caching_test.dart b/packages/flutter/test/rendering/viewport_caching_test.dart new file mode 100644 index 0000000000..94acea3ad9 --- /dev/null +++ b/packages/flutter/test/rendering/viewport_caching_test.dart @@ -0,0 +1,127 @@ +// Copyright 2019 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file is separate from viewport_test.dart because we can't use both +// testWidgets and rendering_tester in the same file - testWidgets will +// initialize a binding, which rendering_tester will attempt to re-initialize +// (or vice versa). + +import 'package:flutter/rendering.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'rendering_tester.dart'; + +void main() { + const double width = 800; + const double height = 600; + Rect rectExpandedOnAxis(double value) => Rect.fromLTRB(0.0, 0.0 - value, width, height + value); + List children; + + setUp(() { + children = [ + RenderSliverToBoxAdapter( + child: RenderSizedBox(const Size(800, 400)), + ), + ]; + }); + + test('Cache extent - null, pixels', () async { + final RenderViewport renderViewport = RenderViewport( + crossAxisDirection: AxisDirection.left, + offset: ViewportOffset.zero(), + children: children, + ); + layout(renderViewport, phase: EnginePhase.flushSemantics); + expect( + renderViewport.describeSemanticsClip(null), + rectExpandedOnAxis(RenderAbstractViewport.defaultCacheExtent), + ); + }); + + test('Cache extent - 0, pixels', () async { + final RenderViewport renderViewport = RenderViewport( + crossAxisDirection: AxisDirection.left, + offset: ViewportOffset.zero(), + cacheExtent: 0.0, + children: children, + ); + layout(renderViewport, phase: EnginePhase.flushSemantics); + expect(renderViewport.describeSemanticsClip(null), rectExpandedOnAxis(0.0)); + }); + + test('Cache extent - 500, pixels', () async { + final RenderViewport renderViewport = RenderViewport( + crossAxisDirection: AxisDirection.left, + offset: ViewportOffset.zero(), + cacheExtent: 500.0, + children: children, + ); + layout(renderViewport, phase: EnginePhase.flushSemantics); + expect(renderViewport.describeSemanticsClip(null), rectExpandedOnAxis(500.0)); + }); + + test('Cache extent - nullx viewport', () async { + await expectLater(() => RenderViewport( + crossAxisDirection: AxisDirection.left, + offset: ViewportOffset.zero(), + cacheExtent: null, + cacheExtentStyle: CacheExtentStyle.viewport, + children: children, + ), + throwsAssertionError + ); + }); + + test('Cache extent - 0x viewport', () async { + final RenderViewport renderViewport = RenderViewport( + crossAxisDirection: AxisDirection.left, + offset: ViewportOffset.zero(), + cacheExtent: 0.0, + cacheExtentStyle: CacheExtentStyle.viewport, + children: children, + ); + + layout(renderViewport); + expect(renderViewport.describeSemanticsClip(null), rectExpandedOnAxis(0)); + }); + + test('Cache extent - .5x viewport', () async { + final RenderViewport renderViewport = RenderViewport( + crossAxisDirection: AxisDirection.left, + offset: ViewportOffset.zero(), + cacheExtent: .5, + cacheExtentStyle: CacheExtentStyle.viewport, + children: children, + ); + + layout(renderViewport); + expect(renderViewport.describeSemanticsClip(null), rectExpandedOnAxis(height / 2)); + }); + + test('Cache extent - 1x viewport', () async { + final RenderViewport renderViewport = RenderViewport( + crossAxisDirection: AxisDirection.left, + offset: ViewportOffset.zero(), + cacheExtent: 1.0, + cacheExtentStyle: CacheExtentStyle.viewport, + children: children, + ); + + layout(renderViewport); + expect(renderViewport.describeSemanticsClip(null), rectExpandedOnAxis(height)); + }); + + test('Cache extent - 2.5x viewport', () async { + final RenderViewport renderViewport = RenderViewport( + crossAxisDirection: AxisDirection.left, + offset: ViewportOffset.zero(), + cacheExtent: 2.5, + cacheExtentStyle: CacheExtentStyle.viewport, + children: children, + ); + + layout(renderViewport); + expect(renderViewport.describeSemanticsClip(null), rectExpandedOnAxis(height * 2.5)); + }); +} diff --git a/packages/flutter/test/rendering/viewport_test.dart b/packages/flutter/test/rendering/viewport_test.dart index 2c437f317e..25f48b71b8 100644 --- a/packages/flutter/test/rendering/viewport_test.dart +++ b/packages/flutter/test/rendering/viewport_test.dart @@ -2,6 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// This file is separate from viewport_caching_test.dart because we can't use +// both testWidgets and rendering_tester in the same file - testWidgets will +// initialize a binding, which rendering_tester will attempt to re-initialize +// (or vice versa). + import 'dart:ui'; import 'package:flutter_test/flutter_test.dart';