Skip to content

Commit b4bfad8

Browse files
goderbauercaseycrogers
authored andcommitted
Dynamic view sizing (flutter#138648)
Towards flutter#134501. This change is based on flutter/engine#48090. It changes the `RenderView` to be dynamically sized based on its content if the `FlutterView` it is configured with allows it (i.e. the `FlutterView` has loose `FlutterView.physicalConstraints`). For that, it uses those `physicalConstraints` as input to the layout algorithm by passing them on to its child (after translating them to logical constraints via the device pixel ratio). The resulting `Size` that the `RenderView` would like to be is then communicated back to the engine by passing it to the `FlutterView.render` call. Tests will fail until flutter/engine#48090 has rolled into the framework.
1 parent b8435b4 commit b4bfad8

16 files changed

+291
-43
lines changed

packages/flutter/lib/src/rendering/binding.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture
350350
final FlutterView view = renderView.flutterView;
351351
final double devicePixelRatio = view.devicePixelRatio;
352352
return ViewConfiguration(
353-
size: view.physicalSize / devicePixelRatio,
353+
constraints: view.physicalConstraints / devicePixelRatio,
354354
devicePixelRatio: devicePixelRatio,
355355
);
356356
}

packages/flutter/lib/src/rendering/box.dart

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// found in the LICENSE file.
44

55
import 'dart:math' as math;
6-
import 'dart:ui' as ui show lerpDouble;
6+
import 'dart:ui' as ui show ViewConstraints, lerpDouble;
77

88
import 'package:flutter/foundation.dart';
99
import 'package:flutter/gestures.dart';
@@ -153,6 +153,13 @@ class BoxConstraints extends Constraints {
153153
minHeight = height ?? double.infinity,
154154
maxHeight = height ?? double.infinity;
155155

156+
/// Creates box constraints that match the given view constraints.
157+
BoxConstraints.fromViewConstraints(ui.ViewConstraints constraints)
158+
: minWidth = constraints.minWidth,
159+
maxWidth = constraints.maxWidth,
160+
minHeight = constraints.minHeight,
161+
maxHeight = constraints.maxHeight;
162+
156163
/// The minimum width that satisfies the constraints.
157164
final double minWidth;
158165

packages/flutter/lib/src/rendering/view.dart

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// found in the LICENSE file.
44

55
import 'dart:io' show Platform;
6-
import 'dart:ui' as ui show FlutterView, Scene, SceneBuilder, SemanticsUpdate;
6+
import 'dart:ui' as ui show FlutterView, Scene, SceneBuilder, SemanticsUpdate, ViewConstraints;
77

88
import 'package:flutter/foundation.dart';
99
import 'package:flutter/services.dart';
@@ -19,14 +19,15 @@ import 'object.dart';
1919
class ViewConfiguration {
2020
/// Creates a view configuration.
2121
///
22-
/// By default, the view has zero [size] and a [devicePixelRatio] of 1.0.
22+
/// By default, the view has [ViewConstraints] with all dimensions set to zero
23+
/// (i.e. the view is forced to [Size.zero]) and a [devicePixelRatio] of 1.0.
2324
const ViewConfiguration({
24-
this.size = Size.zero,
25+
this.constraints = const ui.ViewConstraints(maxWidth: 0.0, maxHeight: 0.0),
2526
this.devicePixelRatio = 1.0,
2627
});
2728

28-
/// The size of the output surface.
29-
final Size size;
29+
/// The constraints of the output surface in logical pixel.
30+
final ui.ViewConstraints constraints;
3031

3132
/// The pixel density of the output surface.
3233
final double devicePixelRatio;
@@ -40,21 +41,34 @@ class ViewConfiguration {
4041
return Matrix4.diagonal3Values(devicePixelRatio, devicePixelRatio, 1.0);
4142
}
4243

44+
/// Transforms the provided [Size] in logical pixels to physical pixels.
45+
///
46+
/// The [FlutterView.render] method accepts only sizes in physical pixels,
47+
/// but the framework operates in logical pixels. This method is used to
48+
/// transform the logical size calculated for a [RenderView] back to a
49+
/// physical size suitable to be passed to [FlutterView.render].
50+
///
51+
/// By default, this method just multiplies the provided [Size] with the
52+
/// [devicePixelRatio].
53+
Size toPhysicalSize(Size logicalSize) {
54+
return logicalSize * devicePixelRatio;
55+
}
56+
4357
@override
4458
bool operator ==(Object other) {
4559
if (other.runtimeType != runtimeType) {
4660
return false;
4761
}
4862
return other is ViewConfiguration
49-
&& other.size == size
63+
&& other.constraints == constraints
5064
&& other.devicePixelRatio == devicePixelRatio;
5165
}
5266

5367
@override
54-
int get hashCode => Object.hash(size, devicePixelRatio);
68+
int get hashCode => Object.hash(constraints, devicePixelRatio);
5569

5670
@override
57-
String toString() => '$size at ${debugFormatDouble(devicePixelRatio)}x';
71+
String toString() => '$constraints at ${debugFormatDouble(devicePixelRatio)}x';
5872
}
5973

6074
/// The root of the render tree.
@@ -76,8 +90,10 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox>
7690
RenderBox? child,
7791
ViewConfiguration? configuration,
7892
required ui.FlutterView view,
79-
}) : _configuration = configuration,
80-
_view = view {
93+
}) : _view = view {
94+
if (configuration != null) {
95+
this.configuration = configuration;
96+
}
8197
this.child = child;
8298
}
8399

@@ -105,6 +121,7 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox>
105121
}
106122
final ViewConfiguration? oldConfiguration = _configuration;
107123
_configuration = value;
124+
_constraints = BoxConstraints.fromViewConstraints(configuration.constraints);
108125
if (_rootTransform == null) {
109126
// [prepareInitialFrame] has not been called yet, nothing to do for now.
110127
return;
@@ -119,6 +136,15 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox>
119136
/// Whether a [configuration] has been set.
120137
bool get hasConfiguration => _configuration != null;
121138

139+
@override
140+
BoxConstraints get constraints {
141+
if (_constraints == null) {
142+
throw StateError('Constraints are not available because RenderView has not been given a configuration yet.');
143+
}
144+
return _constraints!;
145+
}
146+
BoxConstraints? _constraints;
147+
122148
/// The [FlutterView] into which this [RenderView] will render.
123149
ui.FlutterView get flutterView => _view;
124150
final ui.FlutterView _view;
@@ -188,12 +214,13 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox>
188214
@override
189215
void performLayout() {
190216
assert(_rootTransform != null);
191-
_size = configuration.size;
192-
assert(_size.isFinite);
193-
217+
final bool sizedByChild = !constraints.isTight;
194218
if (child != null) {
195-
child!.layout(BoxConstraints.tight(_size));
219+
child!.layout(constraints, parentUsesSize: sizedByChild);
196220
}
221+
_size = sizedByChild && child != null ? child!.size : constraints.smallest;
222+
assert(size.isFinite);
223+
assert(constraints.isSatisfiedBy(size));
197224
}
198225

199226
/// Determines the set of render objects located at the given position.
@@ -253,7 +280,8 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox>
253280
if (automaticSystemUiAdjustment) {
254281
_updateSystemChrome();
255282
}
256-
_view.render(scene);
283+
assert(configuration.constraints.isSatisfiedBy(size));
284+
_view.render(scene, size: configuration.toPhysicalSize(size));
257285
scene.dispose();
258286
assert(() {
259287
if (debugRepaintRainbowEnabled || debugRepaintTextRainbowEnabled) {

packages/flutter/test/foundation/service_extensions_test.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,8 @@ void main() {
259259
r' debug mode enabled - [a-zA-Z]+\n'
260260
r' view size: Size\(2400\.0, 1800\.0\) \(in physical pixels\)\n'
261261
r' device pixel ratio: 3\.0 \(physical pixels per logical pixel\)\n'
262-
r' configuration: Size\(800\.0, 600\.0\) at 3\.0x \(in logical pixels\)\n'
262+
r' configuration: ViewConstraints\(w=800\.0, h=600\.0\) at 3\.0x \(in\n'
263+
r' logical pixels\)\n'
263264
r'$',
264265
),
265266
});

packages/flutter/test/rendering/box_constraints_test.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
import 'dart:ui';
6+
57
import 'package:flutter/rendering.dart';
68
import 'package:flutter_test/flutter_test.dart';
79

@@ -167,4 +169,17 @@ void main() {
167169
expect(copy.minHeight, 11.0);
168170
expect(copy.maxHeight, 18.0);
169171
});
172+
173+
test('BoxConstraints.fromViewConstraints', () {
174+
final BoxConstraints unconstrained = BoxConstraints.fromViewConstraints(
175+
const ViewConstraints(),
176+
);
177+
expect(unconstrained, const BoxConstraints());
178+
179+
final BoxConstraints constraints = BoxConstraints.fromViewConstraints(
180+
const ViewConstraints(minWidth: 1, maxWidth: 2, minHeight: 3, maxHeight: 4),
181+
);
182+
expect(constraints, const BoxConstraints(minWidth: 1, maxWidth: 2, minHeight: 3, maxHeight: 4));
183+
});
184+
170185
}

packages/flutter/test/rendering/independent_layout_test.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
import 'dart:ui' show ViewConstraints;
6+
57
import 'package:flutter/rendering.dart';
68
import 'package:flutter_test/flutter_test.dart';
79

@@ -32,8 +34,8 @@ class TestLayout {
3234
void main() {
3335
TestRenderingFlutterBinding.ensureInitialized();
3436

35-
const ViewConfiguration testConfiguration = ViewConfiguration(
36-
size: Size(800.0, 600.0),
37+
final ViewConfiguration testConfiguration = ViewConfiguration(
38+
constraints: ViewConstraints.tight(const Size(800.0, 600.0)),
3739
);
3840

3941
test('onscreen layout does not affect offscreen', () {

packages/flutter/test/rendering/multi_view_binding_test.dart

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ void main() {
1818
binding.addRenderView(view);
1919
expect(binding.renderViews, contains(view));
2020
expect(view.configuration.devicePixelRatio, flutterView.devicePixelRatio);
21-
expect(view.configuration.size, flutterView.physicalSize / flutterView.devicePixelRatio);
21+
expect(view.configuration.constraints, ViewConstraints.tight(flutterView.physicalSize) / flutterView.devicePixelRatio);
2222

2323
binding.removeRenderView(view);
2424
expect(binding.renderViews, isEmpty);
@@ -51,13 +51,17 @@ void main() {
5151
final RenderView view = RenderView(view: flutterView);
5252
binding.addRenderView(view);
5353
expect(view.configuration.devicePixelRatio, 2.5);
54-
expect(view.configuration.size, const Size(160.0, 240.0));
54+
expect(view.configuration.constraints.isTight, isTrue);
55+
expect(view.configuration.constraints.minWidth, 160.0);
56+
expect(view.configuration.constraints.minHeight, 240.0);
5557

5658
flutterView.devicePixelRatio = 3.0;
5759
flutterView.physicalSize = const Size(300, 300);
5860
binding.handleMetricsChanged();
5961
expect(view.configuration.devicePixelRatio, 3.0);
60-
expect(view.configuration.size, const Size(100.0, 100.0));
62+
expect(view.configuration.constraints.isTight, isTrue);
63+
expect(view.configuration.constraints.minWidth, 100.0);
64+
expect(view.configuration.constraints.minHeight, 100.0);
6165

6266
binding.removeRenderView(view);
6367
});
@@ -183,6 +187,8 @@ class FakeFlutterView extends Fake implements FlutterView {
183187
@override
184188
Size physicalSize;
185189
@override
190+
ViewConstraints get physicalConstraints => ViewConstraints.tight(physicalSize);
191+
@override
186192
ViewPadding padding;
187193

188194
List<Scene> renderedScenes = <Scene>[];

packages/flutter/test/rendering/view_test.dart

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
import 'dart:ui' show ViewConstraints;
6+
57
import 'package:flutter/rendering.dart';
68
import 'package:flutter_test/flutter_test.dart';
79

@@ -16,14 +18,14 @@ void main() {
1618
Size size = const Size(20, 20),
1719
double devicePixelRatio = 2.0,
1820
}) {
19-
return ViewConfiguration(size: size, devicePixelRatio: devicePixelRatio);
21+
return ViewConfiguration(constraints: ViewConstraints.tight(size), devicePixelRatio: devicePixelRatio);
2022
}
2123

2224
group('RenderView', () {
2325
test('accounts for device pixel ratio in paintBounds', () {
2426
layout(RenderAspectRatio(aspectRatio: 1.0));
2527
pumpFrame();
26-
final Size logicalSize = TestRenderingFlutterBinding.instance.renderView.configuration.size;
28+
final Size logicalSize = TestRenderingFlutterBinding.instance.renderView.size;
2729
final double devicePixelRatio = TestRenderingFlutterBinding.instance.renderView.configuration.devicePixelRatio;
2830
final Size physicalSize = logicalSize * devicePixelRatio;
2931
expect(TestRenderingFlutterBinding.instance.renderView.paintBounds, Offset.zero & physicalSize);
@@ -126,11 +128,38 @@ void main() {
126128
final RenderView view = RenderView(
127129
view: RendererBinding.instance.platformDispatcher.views.single,
128130
);
129-
view.configuration = const ViewConfiguration(size: Size(100, 200), devicePixelRatio: 3.0);
130-
view.configuration = const ViewConfiguration(size: Size(200, 300), devicePixelRatio: 2.0);
131+
view.configuration = ViewConfiguration(constraints: ViewConstraints.tight(const Size(100, 200)), devicePixelRatio: 3.0);
132+
view.configuration = ViewConfiguration(constraints: ViewConstraints.tight(const Size(200, 300)), devicePixelRatio: 2.0);
131133
PipelineOwner().rootNode = view;
132134
view.prepareInitialFrame();
133135
});
136+
137+
test('Constraints are derived from configuration', () {
138+
const ViewConfiguration config = ViewConfiguration(
139+
constraints: ViewConstraints(minWidth: 1, maxWidth: 2, minHeight: 3, maxHeight: 4),
140+
devicePixelRatio: 3.0,
141+
);
142+
const BoxConstraints constraints = BoxConstraints(minWidth: 1, maxWidth: 2, minHeight: 3, maxHeight: 4);
143+
144+
// Configuration set via setter.
145+
final RenderView view = RenderView(
146+
view: RendererBinding.instance.platformDispatcher.views.single,
147+
);
148+
expect(() => view.constraints, throwsA(isA<StateError>().having(
149+
(StateError e) => e.message,
150+
'message',
151+
contains('RenderView has not been given a configuration yet'),
152+
)));
153+
view.configuration = config;
154+
expect(view.constraints, constraints);
155+
156+
// Configuration set in constructor.
157+
final RenderView view2 = RenderView(
158+
view: RendererBinding.instance.platformDispatcher.views.single,
159+
configuration: config,
160+
);
161+
expect(view2.constraints, constraints);
162+
});
134163
}
135164

136165
const Color orange = Color(0xFFFF9000);

packages/flutter/test/widgets/independent_widget_layout_test.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5-
import 'dart:ui' show FlutterView;
5+
import 'dart:ui' show FlutterView, ViewConstraints;
66

77
import 'package:flutter/material.dart';
88
import 'package:flutter/rendering.dart';
@@ -36,7 +36,7 @@ class ScheduledFrameTrackingBindings extends AutomatedTestWidgetsFlutterBinding
3636

3737
class OffscreenRenderView extends RenderView {
3838
OffscreenRenderView({required super.view}) : super(
39-
configuration: const ViewConfiguration(size: _kTestViewSize),
39+
configuration: ViewConfiguration(constraints: ViewConstraints.tight(_kTestViewSize)),
4040
);
4141

4242
@override

packages/flutter/test/widgets/keep_alive_test.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,8 @@ void main() {
218218
' │ debug mode enabled - ${Platform.operatingSystem}\n'
219219
' │ view size: Size(2400.0, 1800.0) (in physical pixels)\n'
220220
' │ device pixel ratio: 3.0 (physical pixels per logical pixel)\n'
221-
' │ configuration: Size(800.0, 600.0) at 3.0x (in logical pixels)\n'
221+
' │ configuration: ViewConstraints(w=800.0, h=600.0) at 3.0x (in\n'
222+
' │ logical pixels)\n'
222223
' │\n'
223224
' └─child: RenderRepaintBoundary#00000\n'
224225
' │ needs compositing\n'
@@ -392,7 +393,8 @@ void main() {
392393
' │ debug mode enabled - ${Platform.operatingSystem}\n'
393394
' │ view size: Size(2400.0, 1800.0) (in physical pixels)\n'
394395
' │ device pixel ratio: 3.0 (physical pixels per logical pixel)\n'
395-
' │ configuration: Size(800.0, 600.0) at 3.0x (in logical pixels)\n'
396+
' │ configuration: ViewConstraints(w=800.0, h=600.0) at 3.0x (in\n'
397+
' │ logical pixels)\n'
396398
' │\n'
397399
' └─child: RenderRepaintBoundary#00000\n'
398400
' │ needs compositing\n'

0 commit comments

Comments
 (0)