Commit 15f6480b authored by Adam Barth's avatar Adam Barth

Merge pull request #2628 from abarth/aspect_ratio

AspectRatio should attempt to fit its height
parents a237b47c 6a54e122
......@@ -345,22 +345,32 @@ class RenderFractionallySizedBox extends RenderProxyBox {
}
}
/// Forces child to layout at a specific aspect ratio.
/// Attempts to size the child to a specific aspect ratio.
///
/// The width of this render object is the largest width permited by the layout
/// The render object first tries the largest width permited by the layout
/// constraints. The height of the render object is determined by applying the
/// given aspect ratio to the width, expressed as a ratio of width to height.
/// For example, a 16:9 width:height aspect ratio would have a value of 16.0/9.0.
///
/// For example, given an aspect ratio of 2.0 and layout constraints that
/// require the width to be between 0.0 and 100.0 and the height to be between
/// 0.0 and 100.0, we'll select a width of 100.0 (the biggest allowed) and a
/// height of 50.0 (to match the aspect ratio).
/// For example, a 16:9 width:height aspect ratio would have a value of
/// 16.0/9.0. If the maximum width is infinite, the initial width is determined
/// by applying the aspect ratio to the maximum height.
///
/// Now consider a second example, this time with an aspect ratio of 2.0 and
/// layout constraints that require the width to be between 0.0 and 100.0 and
/// the height to be between 0.0 and 100.0. We'll select a width of 100.0 (the
/// biggest allowed) and a height of 50.0 (to match the aspect ratio).
///
/// In that same situation, if the aspect ratio is 0.5, we'll also select a
/// width of 100.0 (still the biggest allowed) and we'll attempt to use a height
/// of 200.0. Unfortunately, that violates the constraints and we'll end up with
/// a height of 100.0 instead.
/// of 200.0. Unfortunately, that violates the constraints because the child can
/// be at most 100.0 pixels tall. The render object will then take that value
/// and apply the aspect ratio again to obtain a width of 50.0. That width is
/// permitted by the constraints and the child receives a width of 50.0 and a
/// height of 100.0. If the width were not permitted, the render object would
/// continue iterating through the constraints. If the render object does not
/// find a feasible size after consulting each constraint, the render object
/// will eventually select a size for the child that meets the layout
/// constraints but fails to meet the aspect ratio constraints.
class RenderAspectRatio extends RenderProxyBox {
RenderAspectRatio({
RenderBox child,
......@@ -369,7 +379,7 @@ class RenderAspectRatio extends RenderProxyBox {
assert(_aspectRatio != null);
}
/// The aspect ratio to use when computing the height from the width.
/// The aspect ratio to attempt to use.
///
/// The aspect ratio is expressed as a ratio of width to height. For example,
/// a 16:9 width:height aspect ratio would have a value of 16.0/9.0.
......@@ -383,28 +393,83 @@ class RenderAspectRatio extends RenderProxyBox {
markNeedsLayout();
}
double getMinIntrinsicWidth(BoxConstraints constraints) {
return constraints.minWidth;
}
double getMaxIntrinsicWidth(BoxConstraints constraints) {
return constraints.maxWidth;
}
double getMinIntrinsicHeight(BoxConstraints constraints) {
return _applyAspectRatio(constraints).height;
return constraints.minHeight;
}
double getMaxIntrinsicHeight(BoxConstraints constraints) {
return _applyAspectRatio(constraints).height;
return constraints.maxHeight;
}
Size _applyAspectRatio(BoxConstraints constraints) {
assert(constraints.debugAssertIsNormalized);
double width = constraints.constrainWidth();
double height = constraints.constrainHeight(width / _aspectRatio);
return new Size(width, height);
}
assert(() {
if (!constraints.hasBoundedWidth && !constraints.hasBoundedHeight) {
throw new RenderingError(
'$runtimeType has unbounded constraints.\n'
'This $runtimeType was given an aspect ratio of $aspectRatio but was given '
'both unbounded width and unbounded height constraints. Because both '
'constraints were unbounded, this render object doesn\'t know how much '
'size to consume.'
);
}
return true;
});
bool get sizedByParent => true;
if (constraints.isTight)
return constraints.smallest;
void performResize() {
size = _applyAspectRatio(constraints);
double width = constraints.maxWidth;
double height;
// We default to picking the height based on the width, but if the width
// would be infinite, that's not sensible so we try to infer the height
// from the width.
if (width.isFinite) {
height = width / _aspectRatio;
} else {
height = constraints.maxHeight;
width = height * _aspectRatio;
}
// Similar to RenderImage, we iteratively attempt to fit within the given
// constraings while maintaining the given aspect ratio. The order of
// applying the constraints is also biased towards inferring the height
// from the width.
if (width > constraints.maxWidth) {
width = constraints.maxWidth;
height = width / _aspectRatio;
}
if (height > constraints.maxHeight) {
height = constraints.maxHeight;
width = height * _aspectRatio;
}
if (width < constraints.minWidth) {
width = constraints.minWidth;
height = width / _aspectRatio;
}
if (height < constraints.minHeight) {
height = constraints.minHeight;
width = height * _aspectRatio;
}
return constraints.constrain(new Size(width, height));
}
void performLayout() {
size = _applyAspectRatio(constraints);
if (child != null)
child.layout(new BoxConstraints.tight(size));
}
......
......@@ -702,16 +702,39 @@ class OffStage extends OneChildRenderObjectWidget {
RenderOffStage createRenderObject(BuildContext context) => new RenderOffStage();
}
/// Forces child to layout at a specific aspect ratio.
/// Attempts to size the child to a specific aspect ratio.
///
/// See [RenderAspectRatio] for details.
/// The widget first tries the largest width permited by the layout
/// constraints. The height of the widget is determined by applying the
/// given aspect ratio to the width, expressed as a ratio of width to height.
///
/// For example, a 16:9 width:height aspect ratio would have a value of
/// 16.0/9.0. If the maximum width is infinite, the initial width is determined
/// by applying the aspect ratio to the maximum height.
///
/// Now consider a second example, this time with an aspect ratio of 2.0 and
/// layout constraints that require the width to be between 0.0 and 100.0 and
/// the height to be between 0.0 and 100.0. We'll select a width of 100.0 (the
/// biggest allowed) and a height of 50.0 (to match the aspect ratio).
///
/// In that same situation, if the aspect ratio is 0.5, we'll also select a
/// width of 100.0 (still the biggest allowed) and we'll attempt to use a height
/// of 200.0. Unfortunately, that violates the constraints because the child can
/// be at most 100.0 pixels tall. The widget will then take that value
/// and apply the aspect ratio again to obtain a width of 50.0. That width is
/// permitted by the constraints and the child receives a width of 50.0 and a
/// height of 100.0. If the width were not permitted, the widget would
/// continue iterating through the constraints. If the widget does not
/// find a feasible size after consulting each constraint, the widget
/// will eventually select a size for the child that meets the layout
/// constraints but fails to meet the aspect ratio constraints.
class AspectRatio extends OneChildRenderObjectWidget {
AspectRatio({ Key key, this.aspectRatio, Widget child })
: super(key: key, child: child) {
assert(aspectRatio != null);
}
/// The aspect ratio to use when computing the height from the width.
/// The aspect ratio to attempt to use.
///
/// The aspect ratio is expressed as a ratio of width to height. For example,
/// a 16:9 width:height aspect ratio would have a value of 16.0/9.0.
......
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:test/test.dart';
Size _getSize(WidgetTester tester, BoxConstraints constraints, double aspectRatio) {
Key childKey = new UniqueKey();
tester.pumpWidget(
new Center(
child: new ConstrainedBox(
constraints: constraints,
child: new AspectRatio(
aspectRatio: aspectRatio,
child: new Container(
key: childKey
)
)
)
)
);
RenderBox box = tester.findElementByKey(childKey).renderObject;
return box.size;
}
void main() {
test('Aspect ratio control test', () {
testWidgets((WidgetTester tester) {
expect(_getSize(tester, new BoxConstraints.loose(new Size(500.0, 500.0)), 2.0), equals(new Size(500.0, 250.0)));
expect(_getSize(tester, new BoxConstraints.loose(new Size(500.0, 500.0)), 0.5), equals(new Size(250.0, 500.0)));
});
});
test('Aspect ratio infinite width', () {
testWidgets((WidgetTester tester) {
Key childKey = new UniqueKey();
tester.pumpWidget(
new Center(
child: new Viewport(
scrollDirection: Axis.horizontal,
child: new AspectRatio(
aspectRatio: 2.0,
child: new Container(
key: childKey
)
)
)
)
);
RenderBox box = tester.findElementByKey(childKey).renderObject;
expect(box.size, equals(new Size(1200.0, 600.0)));
});
});
}
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