mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Add an alignment property to OverflowBox
This patch changes the default alignment of OverflowBox to center rather than having a bias towards the upper-left corner. Fixes #1236
This commit is contained in:
parent
4dc19e3fd1
commit
fa03df2d82
@ -7,146 +7,6 @@ import 'object.dart';
|
||||
|
||||
export 'package:flutter/src/painting/box_painter.dart';
|
||||
|
||||
/// A render object that imposes different constraints on its child than it gets
|
||||
/// from its parent, possibly allowing the child to overflow the parent.
|
||||
///
|
||||
/// A render overflow box proxies most functions in the render box protocol to
|
||||
/// its child, except that when laying out its child, it passes constraints
|
||||
/// based on the minWidth, maxWidth, minHeight, and maxHeight fields instead of
|
||||
/// just passing the parent's constraints in. Specifically, it overrides any of
|
||||
/// the equivalent fields on the constraints given by the parent with the
|
||||
/// constraints given by these fields for each such field that is not null. It
|
||||
/// then sizes itself based on the parent's constraints' maxWidth and maxHeight,
|
||||
/// ignoring the child's dimensions.
|
||||
///
|
||||
/// For example, if you wanted a box to always render 50 pixels high, regardless
|
||||
/// of where it was rendered, you would wrap it in a RenderOverflow with
|
||||
/// minHeight and maxHeight set to 50.0. Generally speaking, to avoid confusing
|
||||
/// behaviour around hit testing, a RenderOverflowBox should usually be wrapped
|
||||
/// in a RenderClipRect.
|
||||
///
|
||||
/// The child is positioned at the top left of the box. To position a smaller
|
||||
/// child inside a larger parent, use [RenderPositionedBox] and
|
||||
/// [RenderConstrainedBox] rather than RenderOverflowBox.
|
||||
class RenderOverflowBox extends RenderBox with RenderObjectWithChildMixin<RenderBox> {
|
||||
RenderOverflowBox({
|
||||
RenderBox child,
|
||||
double minWidth,
|
||||
double maxWidth,
|
||||
double minHeight,
|
||||
double maxHeight
|
||||
}) : _minWidth = minWidth, _maxWidth = maxWidth, _minHeight = minHeight, _maxHeight = maxHeight {
|
||||
this.child = child;
|
||||
}
|
||||
|
||||
/// The minimum width constraint to give the child. Set this to null (the
|
||||
/// default) to use the constraint from the parent instead.
|
||||
double get minWidth => _minWidth;
|
||||
double _minWidth;
|
||||
void set minWidth (double value) {
|
||||
if (_minWidth == value)
|
||||
return;
|
||||
_minWidth = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
/// The maximum width constraint to give the child. Set this to null (the
|
||||
/// default) to use the constraint from the parent instead.
|
||||
double get maxWidth => _maxWidth;
|
||||
double _maxWidth;
|
||||
void set maxWidth (double value) {
|
||||
if (_maxWidth == value)
|
||||
return;
|
||||
_maxWidth = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
/// The minimum height constraint to give the child. Set this to null (the
|
||||
/// default) to use the constraint from the parent instead.
|
||||
double get minHeight => _minHeight;
|
||||
double _minHeight;
|
||||
void set minHeight (double value) {
|
||||
if (_minHeight == value)
|
||||
return;
|
||||
_minHeight = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
/// The maximum height constraint to give the child. Set this to null (the
|
||||
/// default) to use the constraint from the parent instead.
|
||||
double get maxHeight => _maxHeight;
|
||||
double _maxHeight;
|
||||
void set maxHeight (double value) {
|
||||
if (_maxHeight == value)
|
||||
return;
|
||||
_maxHeight = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
BoxConstraints _getInnerConstraints(BoxConstraints constraints) {
|
||||
return new BoxConstraints(
|
||||
minWidth: _minWidth ?? constraints.minWidth,
|
||||
maxWidth: _maxWidth ?? constraints.maxWidth,
|
||||
minHeight: _minHeight ?? constraints.minHeight,
|
||||
maxHeight: _maxHeight ?? constraints.maxHeight
|
||||
);
|
||||
}
|
||||
|
||||
double getMinIntrinsicWidth(BoxConstraints constraints) {
|
||||
assert(constraints.isNormalized);
|
||||
return constraints.constrainWidth();
|
||||
}
|
||||
|
||||
double getMaxIntrinsicWidth(BoxConstraints constraints) {
|
||||
assert(constraints.isNormalized);
|
||||
return constraints.constrainWidth();
|
||||
}
|
||||
|
||||
double getMinIntrinsicHeight(BoxConstraints constraints) {
|
||||
assert(constraints.isNormalized);
|
||||
return constraints.constrainHeight();
|
||||
}
|
||||
|
||||
double getMaxIntrinsicHeight(BoxConstraints constraints) {
|
||||
assert(constraints.isNormalized);
|
||||
return constraints.constrainHeight();
|
||||
}
|
||||
|
||||
double computeDistanceToActualBaseline(TextBaseline baseline) {
|
||||
if (child != null)
|
||||
return child.getDistanceToActualBaseline(baseline);
|
||||
return super.computeDistanceToActualBaseline(baseline);
|
||||
}
|
||||
|
||||
bool get sizedByParent => true;
|
||||
|
||||
void performResize() {
|
||||
size = constraints.biggest;
|
||||
}
|
||||
|
||||
void performLayout() {
|
||||
if (child != null)
|
||||
child.layout(_getInnerConstraints(constraints));
|
||||
}
|
||||
|
||||
bool hitTestChildren(HitTestResult result, { Point position }) {
|
||||
return child?.hitTest(result, position: position) ?? false;
|
||||
}
|
||||
|
||||
void paint(PaintingContext context, Offset offset) {
|
||||
if (child != null)
|
||||
context.paintChild(child, offset);
|
||||
}
|
||||
|
||||
void debugDescribeSettings(List<String> settings) {
|
||||
super.debugDescribeSettings(settings);
|
||||
settings.add('minWidth: ${minWidth ?? "use parent minWidth constraint"}');
|
||||
settings.add('maxWidth: ${maxWidth ?? "use parent maxWidth constraint"}');
|
||||
settings.add('minHeight: ${minHeight ?? "use parent minHeight constraint"}');
|
||||
settings.add('maxHeight: ${maxHeight ?? "use parent maxHeight constraint"}');
|
||||
}
|
||||
}
|
||||
|
||||
/// A render box that's a specific size but passes its original constraints through to its child, which will probably overflow
|
||||
class RenderSizedOverflowBox extends RenderBox with RenderObjectWithChildMixin<RenderBox> {
|
||||
RenderSizedOverflowBox({
|
||||
|
||||
@ -356,6 +356,158 @@ class RenderPositionedBox extends RenderShiftedBox {
|
||||
}
|
||||
}
|
||||
|
||||
/// A render object that imposes different constraints on its child than it gets
|
||||
/// from its parent, possibly allowing the child to overflow the parent.
|
||||
///
|
||||
/// A render overflow box proxies most functions in the render box protocol to
|
||||
/// its child, except that when laying out its child, it passes constraints
|
||||
/// based on the minWidth, maxWidth, minHeight, and maxHeight fields instead of
|
||||
/// just passing the parent's constraints in. Specifically, it overrides any of
|
||||
/// the equivalent fields on the constraints given by the parent with the
|
||||
/// constraints given by these fields for each such field that is not null. It
|
||||
/// then sizes itself based on the parent's constraints' maxWidth and maxHeight,
|
||||
/// ignoring the child's dimensions.
|
||||
///
|
||||
/// For example, if you wanted a box to always render 50 pixels high, regardless
|
||||
/// of where it was rendered, you would wrap it in a RenderOverflow with
|
||||
/// minHeight and maxHeight set to 50.0. Generally speaking, to avoid confusing
|
||||
/// behaviour around hit testing, a RenderOverflowBox should usually be wrapped
|
||||
/// in a RenderClipRect.
|
||||
///
|
||||
/// The child is positioned at the top left of the box. To position a smaller
|
||||
/// child inside a larger parent, use [RenderPositionedBox] and
|
||||
/// [RenderConstrainedBox] rather than RenderOverflowBox.
|
||||
class RenderOverflowBox extends RenderShiftedBox {
|
||||
RenderOverflowBox({
|
||||
RenderBox child,
|
||||
double minWidth,
|
||||
double maxWidth,
|
||||
double minHeight,
|
||||
double maxHeight,
|
||||
FractionalOffset alignment: const FractionalOffset(0.5, 0.5)
|
||||
}) : _minWidth = minWidth,
|
||||
_maxWidth = maxWidth,
|
||||
_minHeight = minHeight,
|
||||
_maxHeight = maxHeight,
|
||||
_alignment = alignment,
|
||||
super(child);
|
||||
|
||||
/// The minimum width constraint to give the child. Set this to null (the
|
||||
/// default) to use the constraint from the parent instead.
|
||||
double get minWidth => _minWidth;
|
||||
double _minWidth;
|
||||
void set minWidth (double value) {
|
||||
if (_minWidth == value)
|
||||
return;
|
||||
_minWidth = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
/// The maximum width constraint to give the child. Set this to null (the
|
||||
/// default) to use the constraint from the parent instead.
|
||||
double get maxWidth => _maxWidth;
|
||||
double _maxWidth;
|
||||
void set maxWidth (double value) {
|
||||
if (_maxWidth == value)
|
||||
return;
|
||||
_maxWidth = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
/// The minimum height constraint to give the child. Set this to null (the
|
||||
/// default) to use the constraint from the parent instead.
|
||||
double get minHeight => _minHeight;
|
||||
double _minHeight;
|
||||
void set minHeight (double value) {
|
||||
if (_minHeight == value)
|
||||
return;
|
||||
_minHeight = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
/// The maximum height constraint to give the child. Set this to null (the
|
||||
/// default) to use the constraint from the parent instead.
|
||||
double get maxHeight => _maxHeight;
|
||||
double _maxHeight;
|
||||
void set maxHeight (double value) {
|
||||
if (_maxHeight == value)
|
||||
return;
|
||||
_maxHeight = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
/// How to align the child.
|
||||
///
|
||||
/// The x and y values of the alignment control the horizontal and vertical
|
||||
/// alignment, respectively. An x value of 0.0 means that the left edge of
|
||||
/// the child is aligned with the left edge of the parent whereas an x value
|
||||
/// of 1.0 means that the right edge of the child is aligned with the right
|
||||
/// edge of the parent. Other values interpolate (and extrapolate) linearly.
|
||||
/// For example, a value of 0.5 means that the center of the child is aligned
|
||||
/// with the center of the parent.
|
||||
FractionalOffset get alignment => _alignment;
|
||||
FractionalOffset _alignment;
|
||||
void set alignment (FractionalOffset newAlignment) {
|
||||
assert(newAlignment != null && newAlignment.dx != null && newAlignment.dy != null);
|
||||
if (_alignment == newAlignment)
|
||||
return;
|
||||
_alignment = newAlignment;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
BoxConstraints _getInnerConstraints(BoxConstraints constraints) {
|
||||
return new BoxConstraints(
|
||||
minWidth: _minWidth ?? constraints.minWidth,
|
||||
maxWidth: _maxWidth ?? constraints.maxWidth,
|
||||
minHeight: _minHeight ?? constraints.minHeight,
|
||||
maxHeight: _maxHeight ?? constraints.maxHeight
|
||||
);
|
||||
}
|
||||
|
||||
double getMinIntrinsicWidth(BoxConstraints constraints) {
|
||||
assert(constraints.isNormalized);
|
||||
return constraints.minWidth;
|
||||
}
|
||||
|
||||
double getMaxIntrinsicWidth(BoxConstraints constraints) {
|
||||
assert(constraints.isNormalized);
|
||||
return constraints.minWidth;
|
||||
}
|
||||
|
||||
double getMinIntrinsicHeight(BoxConstraints constraints) {
|
||||
assert(constraints.isNormalized);
|
||||
return constraints.minHeight;
|
||||
}
|
||||
|
||||
double getMaxIntrinsicHeight(BoxConstraints constraints) {
|
||||
assert(constraints.isNormalized);
|
||||
return constraints.minHeight;
|
||||
}
|
||||
|
||||
bool get sizedByParent => true;
|
||||
|
||||
void performResize() {
|
||||
size = constraints.biggest;
|
||||
}
|
||||
|
||||
void performLayout() {
|
||||
if (child != null) {
|
||||
child.layout(_getInnerConstraints(constraints), parentUsesSize: true);
|
||||
final BoxParentData childParentData = child.parentData;
|
||||
childParentData.offset = _alignment.alongOffset(size - child.size);
|
||||
}
|
||||
}
|
||||
|
||||
void debugDescribeSettings(List<String> settings) {
|
||||
super.debugDescribeSettings(settings);
|
||||
settings.add('minWidth: ${minWidth ?? "use parent minWidth constraint"}');
|
||||
settings.add('maxWidth: ${maxWidth ?? "use parent maxWidth constraint"}');
|
||||
settings.add('minHeight: ${minHeight ?? "use parent minHeight constraint"}');
|
||||
settings.add('maxHeight: ${maxHeight ?? "use parent maxHeight constraint"}');
|
||||
settings.add('alignment: $alignment');
|
||||
}
|
||||
}
|
||||
|
||||
/// A delegate for computing the layout of a render object with a single child.
|
||||
class OneChildLayoutDelegate {
|
||||
/// Returns the size of this object given the incoming constraints.
|
||||
|
||||
@ -597,8 +597,15 @@ class FractionallySizedBox extends OneChildRenderObjectWidget {
|
||||
///
|
||||
/// See [RenderOverflowBox] for details.
|
||||
class OverflowBox extends OneChildRenderObjectWidget {
|
||||
OverflowBox({ Key key, this.minWidth, this.maxWidth, this.minHeight, this.maxHeight, Widget child })
|
||||
: super(key: key, child: child);
|
||||
OverflowBox({
|
||||
Key key,
|
||||
this.minWidth,
|
||||
this.maxWidth,
|
||||
this.minHeight,
|
||||
this.maxHeight,
|
||||
this.alignment: const FractionalOffset(0.5, 0.5),
|
||||
Widget child
|
||||
}) : super(key: key, child: child);
|
||||
|
||||
/// The minimum width constraint to give the child. Set this to null (the
|
||||
/// default) to use the constraint from the parent instead.
|
||||
@ -616,18 +623,32 @@ class OverflowBox extends OneChildRenderObjectWidget {
|
||||
/// default) to use the constraint from the parent instead.
|
||||
final double maxHeight;
|
||||
|
||||
/// How to align the child.
|
||||
///
|
||||
/// The x and y values of the alignment control the horizontal and vertical
|
||||
/// alignment, respectively. An x value of 0.0 means that the left edge of
|
||||
/// the child is aligned with the left edge of the parent whereas an x value
|
||||
/// of 1.0 means that the right edge of the child is aligned with the right
|
||||
/// edge of the parent. Other values interpolate (and extrapolate) linearly.
|
||||
/// For example, a value of 0.5 means that the center of the child is aligned
|
||||
/// with the center of the parent.
|
||||
final FractionalOffset alignment;
|
||||
|
||||
RenderOverflowBox createRenderObject() => new RenderOverflowBox(
|
||||
minWidth: minWidth,
|
||||
maxWidth: maxWidth,
|
||||
minHeight: minHeight,
|
||||
maxHeight: maxHeight
|
||||
maxHeight: maxHeight,
|
||||
alignment: alignment
|
||||
);
|
||||
|
||||
void updateRenderObject(RenderOverflowBox renderObject, OverflowBox oldWidget) {
|
||||
renderObject.minWidth = minWidth;
|
||||
renderObject.maxWidth = maxWidth;
|
||||
renderObject.minHeight = minHeight;
|
||||
renderObject.maxHeight = maxHeight;
|
||||
renderObject
|
||||
..minWidth = minWidth
|
||||
..maxWidth = maxWidth
|
||||
..minHeight = minHeight
|
||||
..maxHeight = maxHeight
|
||||
..alignment = alignment;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2047,7 +2068,7 @@ class Semantics extends OneChildRenderObjectWidget {
|
||||
/// If 'container' is true, this Widget will introduce a new node in
|
||||
/// the semantics tree. Otherwise, the semantics will be merged with
|
||||
/// the semantics of any ancestors.
|
||||
///
|
||||
///
|
||||
/// The 'container' flag is implicitly set to true on the immediate
|
||||
/// semantics-providing descendants of a node where multiple
|
||||
/// children have semantics or have descendants providing semantics.
|
||||
@ -2080,7 +2101,7 @@ class Semantics extends OneChildRenderObjectWidget {
|
||||
super.debugFillDescription(description);
|
||||
description.add('container: $container');
|
||||
if (checked != null);
|
||||
description.add('checked: $checked');
|
||||
description.add('checked: $checked');
|
||||
if (label != null);
|
||||
description.add('label: "$label"');
|
||||
}
|
||||
|
||||
@ -30,7 +30,8 @@ void main() {
|
||||
child: new RenderCustomPaint(
|
||||
child: child = new RenderOverflowBox(
|
||||
child: text = new RenderParagraph(new PlainTextSpan('Hello World')),
|
||||
maxHeight: height1 / 2.0
|
||||
maxHeight: height1 / 2.0,
|
||||
alignment: const FractionalOffset(0.0, 0.0)
|
||||
),
|
||||
painter: new TestCallbackPainter(
|
||||
onPaint: () {
|
||||
|
||||
@ -17,6 +17,7 @@ void main() {
|
||||
maxWidth: 100.0,
|
||||
minHeight: 0.0,
|
||||
maxHeight: 100.0,
|
||||
alignment: const FractionalOffset(0.0, 0.0),
|
||||
child: new Center(
|
||||
child: new FractionallySizedBox(
|
||||
width: 0.5,
|
||||
|
||||
35
packages/flutter/test/widget/overflow_box_test.dart
Normal file
35
packages/flutter/test/widget/overflow_box_test.dart
Normal file
@ -0,0 +1,35 @@
|
||||
// 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';
|
||||
|
||||
void main() {
|
||||
test('OverflowBox control test', () {
|
||||
testWidgets((WidgetTester tester) {
|
||||
GlobalKey inner = new GlobalKey();
|
||||
tester.pumpWidget(new Align(
|
||||
alignment: const FractionalOffset(1.0, 1.0),
|
||||
child: new SizedBox(
|
||||
width: 10.0,
|
||||
height: 20.0,
|
||||
child: new OverflowBox(
|
||||
minWidth: 0.0,
|
||||
maxWidth: 100.0,
|
||||
minHeight: 0.0,
|
||||
maxHeight: 50.0,
|
||||
child: new Container(
|
||||
key: inner
|
||||
)
|
||||
)
|
||||
)
|
||||
));
|
||||
RenderBox box = inner.currentContext.findRenderObject();
|
||||
expect(box.localToGlobal(Point.origin), equals(const Point(745.0, 565.0)));
|
||||
expect(box.size, equals(const Size(100.0, 50.0)));
|
||||
});
|
||||
});
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user