Unverified Commit e766ae74 authored by Dan Field's avatar Dan Field Committed by GitHub

Automatically caching viewport (#45327)

parent 84ce3f60
......@@ -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<ParentDataClass extends ContainerParentDataMix
@required AxisDirection crossAxisDirection,
@required ViewportOffset offset,
double cacheExtent,
CacheExtentStyle cacheExtentStyle = CacheExtentStyle.pixel,
}) : assert(axisDirection != null),
assert(crossAxisDirection != null),
assert(offset != null),
assert(axisDirectionToAxis(axisDirection) != axisDirectionToAxis(crossAxisDirection)),
assert(cacheExtentStyle != null),
assert(cacheExtent != null || cacheExtentStyle == CacheExtentStyle.pixel),
_axisDirection = axisDirection,
_crossAxisDirection = crossAxisDirection,
_offset = offset,
_cacheExtent = cacheExtent ?? RenderAbstractViewport.defaultCacheExtent;
_cacheExtent = cacheExtent ?? RenderAbstractViewport.defaultCacheExtent,
_cacheExtentStyle = cacheExtentStyle;
@override
void describeSemanticsConfiguration(SemanticsConfiguration config) {
......@@ -272,6 +285,34 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
markNeedsLayout();
}
/// This value is set during layout based on the [CacheExtentStyle].
///
/// When the style is [CacheExtentStyle.viewport], it is the main axis extent
/// of the viewport multiplied by the requested cache extent, which is still
/// expressed in pixels.
double _calculatedCacheExtent;
/// {@template flutter.rendering.viewport.cacheExtentStyle}
/// Controls how the [cacheExtent] is interpreted.
///
/// If set to [CacheExtentStyle.pixels], the [cacheExtent] will be treated as
/// a logical pixels.
///
/// If set to [CacheExtentStyle.viewport], the [cacheExtent] will be treated
/// as a multiplier for the main axis extent of the viewport. In this case,
/// the [cacheExtent] must not be null.
/// {@endtemplate}
CacheExtentStyle get cacheExtentStyle => _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<ParentDataClass extends ContainerParentDataMix
@override
Rect describeSemanticsClip(RenderSliver child) {
assert (axis != null);
assert(axis != null);
if (_calculatedCacheExtent == null) {
return semanticBounds;
}
switch (axis) {
case Axis.vertical:
return Rect.fromLTRB(
semanticBounds.left,
semanticBounds.top - cacheExtent,
semanticBounds.top - _calculatedCacheExtent,
semanticBounds.right,
semanticBounds.bottom + cacheExtent,
semanticBounds.bottom + _calculatedCacheExtent,
);
case Axis.horizontal:
return Rect.fromLTRB(
semanticBounds.left - cacheExtent,
semanticBounds.left - _calculatedCacheExtent,
semanticBounds.top,
semanticBounds.right + cacheExtent,
semanticBounds.right + _calculatedCacheExtent,
semanticBounds.bottom,
);
}
......@@ -1076,11 +1122,19 @@ class RenderViewport extends RenderViewportBase<SliverPhysicalContainerParentDat
List<RenderSliver> 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<SliverPhysicalContainerParentDat
final double reverseDirectionRemainingPaintExtent = centerOffset.clamp(0.0, mainAxisExtent);
final double forwardDirectionRemainingPaintExtent = (mainAxisExtent - centerOffset).clamp(0.0, mainAxisExtent);
final double fullCacheExtent = mainAxisExtent + 2 * cacheExtent;
final double centerCacheOffset = centerOffset + cacheExtent;
switch (cacheExtentStyle) {
case CacheExtentStyle.pixel:
_calculatedCacheExtent = cacheExtent;
break;
case CacheExtentStyle.viewport:
_calculatedCacheExtent = mainAxisExtent * cacheExtent;
break;
}
final double fullCacheExtent = mainAxisExtent + 2 * _calculatedCacheExtent;
final double centerCacheOffset = centerOffset + _calculatedCacheExtent;
final double reverseDirectionRemainingCacheExtent = centerCacheOffset.clamp(0.0, fullCacheExtent);
final double forwardDirectionRemainingCacheExtent = (fullCacheExtent - centerCacheOffset).clamp(0.0, fullCacheExtent);
......@@ -1357,7 +1420,7 @@ class RenderViewport extends RenderViewportBase<SliverPhysicalContainerParentDat
growthDirection: GrowthDirection.reverse,
advance: childBefore,
remainingCacheExtent: reverseDirectionRemainingCacheExtent,
cacheOrigin: (mainAxisExtent - centerOffset).clamp(-cacheExtent, 0.0),
cacheOrigin: (mainAxisExtent - centerOffset).clamp(-_calculatedCacheExtent, 0.0),
);
if (result != 0.0)
return -result;
......@@ -1375,7 +1438,7 @@ class RenderViewport extends RenderViewportBase<SliverPhysicalContainerParentDat
growthDirection: GrowthDirection.forward,
advance: childAfter,
remainingCacheExtent: forwardDirectionRemainingCacheExtent,
cacheOrigin: centerOffset.clamp(-cacheExtent, 0.0),
cacheOrigin: centerOffset.clamp(-_calculatedCacheExtent, 0.0),
);
}
......
......@@ -57,10 +57,13 @@ class Viewport extends MultiChildRenderObjectWidget {
@required this.offset,
this.center,
this.cacheExtent,
this.cacheExtentStyle = CacheExtentStyle.pixel,
List<Widget> slivers = const <Widget>[],
}) : 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<Key>('center', children.first.key, tooltip: 'implicit'));
}
properties.add(DiagnosticsProperty<double>('cacheExtent', cacheExtent));
properties.add(DiagnosticsProperty<CacheExtentStyle>('cacheExtentStyle', cacheExtentStyle));
}
}
......
......@@ -190,7 +190,6 @@ class TestCallbackPainter extends CustomPainter {
bool shouldRepaint(TestCallbackPainter oldPainter) => true;
}
class RenderSizedBox extends RenderBox {
RenderSizedBox(this._size);
......
// 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<RenderSliver> children;
setUp(() {
children = <RenderSliver>[
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));
});
}
......@@ -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';
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment