Automatically caching viewport (#45327)
This commit is contained in:
parent
84ce3f6097
commit
e766ae740e
@ -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);
|
||||
|
||||
|
127
packages/flutter/test/rendering/viewport_caching_test.dart
Normal file
127
packages/flutter/test/rendering/viewport_caching_test.dart
Normal file
@ -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<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';
|
||||
|
Loading…
x
Reference in New Issue
Block a user