Automatically caching viewport (#45327)
This commit is contained in:
parent
84ce3f6097
commit
e766ae740e
@ -15,6 +15,14 @@ import 'object.dart';
|
|||||||
import 'sliver.dart';
|
import 'sliver.dart';
|
||||||
import 'viewport_offset.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.
|
/// An interface for render objects that are bigger on the inside.
|
||||||
///
|
///
|
||||||
/// Some render objects, such as [RenderViewport], present a portion of their
|
/// 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.
|
/// * [RenderViewportBase.cacheExtent] for a definition of the cache extent.
|
||||||
@protected
|
@protected
|
||||||
|
@visibleForTesting
|
||||||
static const double defaultCacheExtent = 250.0;
|
static const double defaultCacheExtent = 250.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,14 +169,18 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
|
|||||||
@required AxisDirection crossAxisDirection,
|
@required AxisDirection crossAxisDirection,
|
||||||
@required ViewportOffset offset,
|
@required ViewportOffset offset,
|
||||||
double cacheExtent,
|
double cacheExtent,
|
||||||
|
CacheExtentStyle cacheExtentStyle = CacheExtentStyle.pixel,
|
||||||
}) : assert(axisDirection != null),
|
}) : assert(axisDirection != null),
|
||||||
assert(crossAxisDirection != null),
|
assert(crossAxisDirection != null),
|
||||||
assert(offset != null),
|
assert(offset != null),
|
||||||
assert(axisDirectionToAxis(axisDirection) != axisDirectionToAxis(crossAxisDirection)),
|
assert(axisDirectionToAxis(axisDirection) != axisDirectionToAxis(crossAxisDirection)),
|
||||||
|
assert(cacheExtentStyle != null),
|
||||||
|
assert(cacheExtent != null || cacheExtentStyle == CacheExtentStyle.pixel),
|
||||||
_axisDirection = axisDirection,
|
_axisDirection = axisDirection,
|
||||||
_crossAxisDirection = crossAxisDirection,
|
_crossAxisDirection = crossAxisDirection,
|
||||||
_offset = offset,
|
_offset = offset,
|
||||||
_cacheExtent = cacheExtent ?? RenderAbstractViewport.defaultCacheExtent;
|
_cacheExtent = cacheExtent ?? RenderAbstractViewport.defaultCacheExtent,
|
||||||
|
_cacheExtentStyle = cacheExtentStyle;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void describeSemanticsConfiguration(SemanticsConfiguration config) {
|
void describeSemanticsConfiguration(SemanticsConfiguration config) {
|
||||||
@ -272,6 +285,34 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
|
|||||||
markNeedsLayout();
|
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
|
@override
|
||||||
void attach(PipelineOwner owner) {
|
void attach(PipelineOwner owner) {
|
||||||
super.attach(owner);
|
super.attach(owner);
|
||||||
@ -494,20 +535,25 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Rect describeSemanticsClip(RenderSliver child) {
|
Rect describeSemanticsClip(RenderSliver child) {
|
||||||
assert (axis != null);
|
assert(axis != null);
|
||||||
|
|
||||||
|
if (_calculatedCacheExtent == null) {
|
||||||
|
return semanticBounds;
|
||||||
|
}
|
||||||
|
|
||||||
switch (axis) {
|
switch (axis) {
|
||||||
case Axis.vertical:
|
case Axis.vertical:
|
||||||
return Rect.fromLTRB(
|
return Rect.fromLTRB(
|
||||||
semanticBounds.left,
|
semanticBounds.left,
|
||||||
semanticBounds.top - cacheExtent,
|
semanticBounds.top - _calculatedCacheExtent,
|
||||||
semanticBounds.right,
|
semanticBounds.right,
|
||||||
semanticBounds.bottom + cacheExtent,
|
semanticBounds.bottom + _calculatedCacheExtent,
|
||||||
);
|
);
|
||||||
case Axis.horizontal:
|
case Axis.horizontal:
|
||||||
return Rect.fromLTRB(
|
return Rect.fromLTRB(
|
||||||
semanticBounds.left - cacheExtent,
|
semanticBounds.left - _calculatedCacheExtent,
|
||||||
semanticBounds.top,
|
semanticBounds.top,
|
||||||
semanticBounds.right + cacheExtent,
|
semanticBounds.right + _calculatedCacheExtent,
|
||||||
semanticBounds.bottom,
|
semanticBounds.bottom,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1076,11 +1122,19 @@ class RenderViewport extends RenderViewportBase<SliverPhysicalContainerParentDat
|
|||||||
List<RenderSliver> children,
|
List<RenderSliver> children,
|
||||||
RenderSliver center,
|
RenderSliver center,
|
||||||
double cacheExtent,
|
double cacheExtent,
|
||||||
|
CacheExtentStyle cacheExtentStyle = CacheExtentStyle.pixel,
|
||||||
}) : assert(anchor != null),
|
}) : assert(anchor != null),
|
||||||
assert(anchor >= 0.0 && anchor <= 1.0),
|
assert(anchor >= 0.0 && anchor <= 1.0),
|
||||||
|
assert(cacheExtentStyle != CacheExtentStyle.viewport || cacheExtent != null),
|
||||||
_anchor = anchor,
|
_anchor = anchor,
|
||||||
_center = center,
|
_center = center,
|
||||||
super(axisDirection: axisDirection, crossAxisDirection: crossAxisDirection, offset: offset, cacheExtent: cacheExtent) {
|
super(
|
||||||
|
axisDirection: axisDirection,
|
||||||
|
crossAxisDirection: crossAxisDirection,
|
||||||
|
offset: offset,
|
||||||
|
cacheExtent: cacheExtent,
|
||||||
|
cacheExtentStyle: cacheExtentStyle,
|
||||||
|
) {
|
||||||
addAll(children);
|
addAll(children);
|
||||||
if (center == null && firstChild != null)
|
if (center == null && firstChild != null)
|
||||||
_center = firstChild;
|
_center = firstChild;
|
||||||
@ -1337,8 +1391,17 @@ class RenderViewport extends RenderViewportBase<SliverPhysicalContainerParentDat
|
|||||||
final double reverseDirectionRemainingPaintExtent = centerOffset.clamp(0.0, mainAxisExtent);
|
final double reverseDirectionRemainingPaintExtent = centerOffset.clamp(0.0, mainAxisExtent);
|
||||||
final double forwardDirectionRemainingPaintExtent = (mainAxisExtent - centerOffset).clamp(0.0, mainAxisExtent);
|
final double forwardDirectionRemainingPaintExtent = (mainAxisExtent - centerOffset).clamp(0.0, mainAxisExtent);
|
||||||
|
|
||||||
final double fullCacheExtent = mainAxisExtent + 2 * cacheExtent;
|
switch (cacheExtentStyle) {
|
||||||
final double centerCacheOffset = centerOffset + cacheExtent;
|
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 reverseDirectionRemainingCacheExtent = centerCacheOffset.clamp(0.0, fullCacheExtent);
|
||||||
final double forwardDirectionRemainingCacheExtent = (fullCacheExtent - 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,
|
growthDirection: GrowthDirection.reverse,
|
||||||
advance: childBefore,
|
advance: childBefore,
|
||||||
remainingCacheExtent: reverseDirectionRemainingCacheExtent,
|
remainingCacheExtent: reverseDirectionRemainingCacheExtent,
|
||||||
cacheOrigin: (mainAxisExtent - centerOffset).clamp(-cacheExtent, 0.0),
|
cacheOrigin: (mainAxisExtent - centerOffset).clamp(-_calculatedCacheExtent, 0.0),
|
||||||
);
|
);
|
||||||
if (result != 0.0)
|
if (result != 0.0)
|
||||||
return -result;
|
return -result;
|
||||||
@ -1375,7 +1438,7 @@ class RenderViewport extends RenderViewportBase<SliverPhysicalContainerParentDat
|
|||||||
growthDirection: GrowthDirection.forward,
|
growthDirection: GrowthDirection.forward,
|
||||||
advance: childAfter,
|
advance: childAfter,
|
||||||
remainingCacheExtent: forwardDirectionRemainingCacheExtent,
|
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,
|
@required this.offset,
|
||||||
this.center,
|
this.center,
|
||||||
this.cacheExtent,
|
this.cacheExtent,
|
||||||
|
this.cacheExtentStyle = CacheExtentStyle.pixel,
|
||||||
List<Widget> slivers = const <Widget>[],
|
List<Widget> slivers = const <Widget>[],
|
||||||
}) : assert(offset != null),
|
}) : assert(offset != null),
|
||||||
assert(slivers != null),
|
assert(slivers != null),
|
||||||
assert(center == null || slivers.where((Widget child) => child.key == center).length == 1),
|
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);
|
super(key: key, children: slivers);
|
||||||
|
|
||||||
/// The direction in which the [offset]'s [ViewportOffset.pixels] increases.
|
/// The direction in which the [offset]'s [ViewportOffset.pixels] increases.
|
||||||
@ -112,6 +115,9 @@ class Viewport extends MultiChildRenderObjectWidget {
|
|||||||
/// {@macro flutter.rendering.viewport.cacheExtent}
|
/// {@macro flutter.rendering.viewport.cacheExtent}
|
||||||
final double cacheExtent;
|
final double cacheExtent;
|
||||||
|
|
||||||
|
/// {@macro flutter.rendering.viewport.cacheExtentStyle}
|
||||||
|
final CacheExtentStyle cacheExtentStyle;
|
||||||
|
|
||||||
/// Given a [BuildContext] and an [AxisDirection], determine the correct cross
|
/// Given a [BuildContext] and an [AxisDirection], determine the correct cross
|
||||||
/// axis direction.
|
/// axis direction.
|
||||||
///
|
///
|
||||||
@ -140,6 +146,7 @@ class Viewport extends MultiChildRenderObjectWidget {
|
|||||||
anchor: anchor,
|
anchor: anchor,
|
||||||
offset: offset,
|
offset: offset,
|
||||||
cacheExtent: cacheExtent,
|
cacheExtent: cacheExtent,
|
||||||
|
cacheExtentStyle: cacheExtentStyle,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,7 +157,8 @@ class Viewport extends MultiChildRenderObjectWidget {
|
|||||||
..crossAxisDirection = crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(context, axisDirection)
|
..crossAxisDirection = crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(context, axisDirection)
|
||||||
..anchor = anchor
|
..anchor = anchor
|
||||||
..offset = offset
|
..offset = offset
|
||||||
..cacheExtent = cacheExtent;
|
..cacheExtent = cacheExtent
|
||||||
|
..cacheExtentStyle = cacheExtentStyle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -168,6 +176,8 @@ class Viewport extends MultiChildRenderObjectWidget {
|
|||||||
} else if (children.isNotEmpty && children.first.key != null) {
|
} else if (children.isNotEmpty && children.first.key != null) {
|
||||||
properties.add(DiagnosticsProperty<Key>('center', children.first.key, tooltip: 'implicit'));
|
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;
|
bool shouldRepaint(TestCallbackPainter oldPainter) => true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class RenderSizedBox extends RenderBox {
|
class RenderSizedBox extends RenderBox {
|
||||||
RenderSizedBox(this._size);
|
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
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// 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 'dart:ui';
|
||||||
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
Loading…
x
Reference in New Issue
Block a user