Merge pull request #2607 from abarth/rotated_box
Add RotatedBox which applies a rotation before layout
This commit is contained in:
commit
d24a7d3f93
@ -26,6 +26,7 @@ export 'src/rendering/overflow.dart';
|
||||
export 'src/rendering/paragraph.dart';
|
||||
export 'src/rendering/performance_overlay.dart';
|
||||
export 'src/rendering/proxy_box.dart';
|
||||
export 'src/rendering/rotated_box.dart';
|
||||
export 'src/rendering/semantics.dart';
|
||||
export 'src/rendering/shifted_box.dart';
|
||||
export 'src/rendering/stack.dart';
|
||||
|
@ -154,6 +154,16 @@ class BoxConstraints extends Constraints {
|
||||
maxHeight: height == null ? maxHeight : height.clamp(minHeight, maxHeight));
|
||||
}
|
||||
|
||||
/// A box constraints with the width and height constraints flipped.
|
||||
BoxConstraints get flipped {
|
||||
return new BoxConstraints(
|
||||
minWidth: minHeight,
|
||||
maxWidth: maxHeight,
|
||||
minHeight: minWidth,
|
||||
maxHeight: maxWidth
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns box constraints with the same width constraints but with
|
||||
/// unconstrainted height.
|
||||
BoxConstraints widthConstraints() => new BoxConstraints(minWidth: minWidth, maxWidth: maxWidth);
|
||||
|
110
packages/flutter/lib/src/rendering/rotated_box.dart
Normal file
110
packages/flutter/lib/src/rendering/rotated_box.dart
Normal file
@ -0,0 +1,110 @@
|
||||
// Copyright 2016 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 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:vector_math/vector_math_64.dart';
|
||||
|
||||
import 'box.dart';
|
||||
import 'object.dart';
|
||||
|
||||
const double _kQuarterTurnsInRadians = math.PI / 2.0;
|
||||
|
||||
/// Rotates its child by a integral number of quarter turns.
|
||||
///
|
||||
/// Unlike [RenderTransform], which applies a transform just prior to painting,
|
||||
/// this object applies its rotation prior to layout, which means the entire
|
||||
/// rotated box consumes only as much space as required by the rotated child.
|
||||
class RenderRotatedBox extends RenderBox with RenderObjectWithChildMixin<RenderBox> {
|
||||
RenderRotatedBox({
|
||||
int quarterTurns,
|
||||
RenderBox child
|
||||
}) : _quarterTurns = quarterTurns {
|
||||
assert(quarterTurns != null);
|
||||
this.child = child;
|
||||
}
|
||||
|
||||
/// The number of clockwise quarter turns the child should be rotated.
|
||||
int get quarterTurns => _quarterTurns;
|
||||
int _quarterTurns;
|
||||
void set quarterTurns(int value) {
|
||||
assert(value != null);
|
||||
if (_quarterTurns == value)
|
||||
return;
|
||||
_quarterTurns = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
bool get _isVertical => quarterTurns % 2 == 1;
|
||||
|
||||
double getMinIntrinsicWidth(BoxConstraints constraints) {
|
||||
assert(constraints.debugAssertIsNormalized);
|
||||
if (child != null)
|
||||
return _isVertical ? child.getMinIntrinsicHeight(constraints.flipped) : child.getMinIntrinsicWidth(constraints);
|
||||
return super.getMinIntrinsicWidth(constraints);
|
||||
}
|
||||
|
||||
double getMaxIntrinsicWidth(BoxConstraints constraints) {
|
||||
assert(constraints.debugAssertIsNormalized);
|
||||
if (child != null)
|
||||
return _isVertical ? child.getMaxIntrinsicHeight(constraints.flipped) : child.getMaxIntrinsicWidth(constraints);
|
||||
return super.getMaxIntrinsicWidth(constraints);
|
||||
}
|
||||
|
||||
double getMinIntrinsicHeight(BoxConstraints constraints) {
|
||||
assert(constraints.debugAssertIsNormalized);
|
||||
if (child != null)
|
||||
return _isVertical ? child.getMinIntrinsicWidth(constraints.flipped) : child.getMinIntrinsicHeight(constraints);
|
||||
return super.getMinIntrinsicHeight(constraints);
|
||||
}
|
||||
|
||||
double getMaxIntrinsicHeight(BoxConstraints constraints) {
|
||||
assert(constraints.debugAssertIsNormalized);
|
||||
if (child != null)
|
||||
return _isVertical ? child.getMaxIntrinsicWidth(constraints.flipped) : child.getMaxIntrinsicHeight(constraints);
|
||||
return super.getMaxIntrinsicHeight(constraints);
|
||||
}
|
||||
|
||||
Matrix4 _paintTransform;
|
||||
|
||||
void performLayout() {
|
||||
_paintTransform = null;
|
||||
if (child != null) {
|
||||
child.layout(_isVertical ? constraints.flipped : constraints, parentUsesSize: true);
|
||||
size = _isVertical ? new Size(child.size.height, child.size.width) : child.size;
|
||||
_paintTransform = new Matrix4.identity()
|
||||
..translate(size.width / 2.0, size.height / 2.0)
|
||||
..rotateZ(_kQuarterTurnsInRadians * (quarterTurns % 4))
|
||||
..translate(-child.size.width / 2.0, -child.size.height / 2.0);
|
||||
} else {
|
||||
performResize();
|
||||
}
|
||||
}
|
||||
|
||||
bool hitTestChildren(HitTestResult result, { Point position }) {
|
||||
assert(_paintTransform != null || needsLayout || child == null);
|
||||
if (child == null || _paintTransform == null)
|
||||
return false;
|
||||
Matrix4 inverse = new Matrix4.inverted(_paintTransform);
|
||||
Vector3 position3 = new Vector3(position.x, position.y, 0.0);
|
||||
Vector3 transformed3 = inverse.transform3(position3);
|
||||
return child.hitTest(result, position: new Point(transformed3.x, transformed3.y));
|
||||
}
|
||||
|
||||
void _paintChild(PaintingContext context, Offset offset) {
|
||||
context.paintChild(child, offset);
|
||||
}
|
||||
|
||||
void paint(PaintingContext context, Offset offset) {
|
||||
if (child != null)
|
||||
context.pushTransform(needsCompositing, offset, _paintTransform, _paintChild);
|
||||
}
|
||||
|
||||
void applyPaintTransform(RenderBox child, Matrix4 transform) {
|
||||
if (_paintTransform != null)
|
||||
transform.multiply(_paintTransform);
|
||||
super.applyPaintTransform(child, transform);
|
||||
}
|
||||
}
|
@ -320,6 +320,27 @@ class FractionalTranslation extends OneChildRenderObjectWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// Rotates its child by a integral number of quarter turns.
|
||||
///
|
||||
/// Unlike [Transform], which applies a transform just prior to painting,
|
||||
/// this object applies its rotation prior to layout, which means the entire
|
||||
/// rotated box consumes only as much space as required by the rotated child.
|
||||
class RotatedBox extends OneChildRenderObjectWidget {
|
||||
RotatedBox({ Key key, this.quarterTurns, Widget child })
|
||||
: super(key: key, child: child) {
|
||||
assert(quarterTurns != null);
|
||||
}
|
||||
|
||||
/// The number of clockwise quarter turns the child should be rotated.
|
||||
final int quarterTurns;
|
||||
|
||||
RenderRotatedBox createRenderObject(BuildContext context) => new RenderRotatedBox(quarterTurns: quarterTurns);
|
||||
|
||||
void updateRenderObject(BuildContext context, RenderRotatedBox renderObject) {
|
||||
renderObject.quarterTurns = quarterTurns;
|
||||
}
|
||||
}
|
||||
|
||||
/// Insets its child by the given padding.
|
||||
///
|
||||
/// When passing layout constraints to its child, padding shrinks the
|
||||
|
58
packages/flutter/test/widget/rotated_box_test.dart
Normal file
58
packages/flutter/test/widget/rotated_box_test.dart
Normal file
@ -0,0 +1,58 @@
|
||||
// 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/material.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
test('Rotated box control test', () {
|
||||
testWidgets((WidgetTester tester) {
|
||||
List<String> log = <String>[];
|
||||
Key rotatedBoxKey = new UniqueKey();
|
||||
|
||||
tester.pumpWidget(
|
||||
new Center(
|
||||
child: new RotatedBox(
|
||||
key: rotatedBoxKey,
|
||||
quarterTurns: 1,
|
||||
child: new Row(
|
||||
justifyContent: FlexJustifyContent.collapse,
|
||||
children: <Widget>[
|
||||
new GestureDetector(
|
||||
onTap: () { log.add('left'); },
|
||||
child: new Container(
|
||||
width: 100.0,
|
||||
height: 40.0,
|
||||
decoration: new BoxDecoration(backgroundColor: Colors.blue[500])
|
||||
)
|
||||
),
|
||||
new GestureDetector(
|
||||
onTap: () { log.add('right'); },
|
||||
child: new Container(
|
||||
width: 75.0,
|
||||
height: 65.0,
|
||||
decoration: new BoxDecoration(backgroundColor: Colors.blue[500])
|
||||
)
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
RenderBox box = tester.findElementByKey(rotatedBoxKey).renderObject;
|
||||
expect(box.size.width, equals(65.0));
|
||||
expect(box.size.height, equals(175.0));
|
||||
|
||||
tester.tapAt(new Point(420.0, 280.0));
|
||||
expect(log, equals(['left']));
|
||||
log.clear();
|
||||
|
||||
tester.tapAt(new Point(380.0, 320.0));
|
||||
expect(log, equals(['right']));
|
||||
log.clear();
|
||||
});
|
||||
});
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user