mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Transform alignment
For those times where you want to spin something around a point relative to the size of your box, but you don't know the size of your box.
This commit is contained in:
parent
cde06cf18a
commit
04ddf92909
@ -862,15 +862,41 @@ class RenderDecoratedBox extends RenderProxyBox {
|
||||
String debugDescribeSettings(String prefix) => '${super.debugDescribeSettings(prefix)}${prefix}decoration:\n${_painter.decoration.toString(prefix + " ")}\n';
|
||||
}
|
||||
|
||||
/// An offset that's expressed as a fraction of a Size.
|
||||
///
|
||||
/// FractionalOffset(0.0, 0.0) represents the top left of the Size,
|
||||
/// FractionalOffset(1.0, 1.0) represents the bottom right of the Size.
|
||||
class FractionalOffset {
|
||||
const FractionalOffset(this.x, this.y);
|
||||
final double x;
|
||||
final double y;
|
||||
bool operator ==(dynamic other) {
|
||||
if (other is! FractionalOffset)
|
||||
return false;
|
||||
final FractionalOffset typedOther = other;
|
||||
return x == typedOther.x &&
|
||||
y == typedOther.y;
|
||||
}
|
||||
int get hashCode {
|
||||
int value = 373;
|
||||
value = 37 * value + x.hashCode;
|
||||
value = 37 * value + y.hashCode;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/// Applies a transformation before painting its child
|
||||
class RenderTransform extends RenderProxyBox {
|
||||
RenderTransform({
|
||||
Matrix4 transform,
|
||||
Offset origin,
|
||||
FractionalOffset alignment,
|
||||
RenderBox child
|
||||
}) : super(child) {
|
||||
assert(transform != null);
|
||||
assert(alignment == null || (alignment.x != null && alignment.y != null));
|
||||
this.transform = transform;
|
||||
this.alignment = alignment;
|
||||
this.origin = origin;
|
||||
}
|
||||
|
||||
@ -888,6 +914,20 @@ class RenderTransform extends RenderProxyBox {
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
/// The alignment of the origin, relative to the size of the box.
|
||||
///
|
||||
/// This is equivalent to setting an origin based on the size of the box.
|
||||
/// If it is specificed at the same time as an offset, both are applied.
|
||||
FractionalOffset get alignment => _alignment;
|
||||
FractionalOffset _alignment;
|
||||
void set alignment (FractionalOffset newAlignment) {
|
||||
assert(newAlignment == null || (newAlignment.x != null && newAlignment.y != null));
|
||||
if (_alignment == newAlignment)
|
||||
return;
|
||||
_alignment = newAlignment;
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
// Note the lack of a getter for transform because Matrix4 is not immutable
|
||||
Matrix4 _transform;
|
||||
|
||||
@ -937,13 +977,19 @@ class RenderTransform extends RenderProxyBox {
|
||||
}
|
||||
|
||||
Matrix4 get _effectiveTransform {
|
||||
if (_origin == null)
|
||||
if (_origin == null && _alignment == null)
|
||||
return _transform;
|
||||
return new Matrix4
|
||||
.identity()
|
||||
.translate(_origin.dx, _origin.dy)
|
||||
.multiply(_transform)
|
||||
.translate(-_origin.dx, -_origin.dy);
|
||||
Matrix4 result = new Matrix4.identity();
|
||||
if (_origin != null)
|
||||
result.translate(_origin.dx, _origin.dy);
|
||||
if (_alignment != null)
|
||||
result.translate(_alignment.x * size.width, _alignment.y * size.height);
|
||||
result.multiply(_transform);
|
||||
if (_alignment != null)
|
||||
result.translate(-_alignment.x * size.width, -_alignment.y * size.height);
|
||||
if (_origin != null)
|
||||
result.translate(-_origin.dx, -_origin.dy);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool hitTest(HitTestResult result, { Point position }) {
|
||||
|
||||
@ -23,6 +23,7 @@ export 'package:flutter/rendering.dart' show
|
||||
FlexAlignItems,
|
||||
FlexDirection,
|
||||
FlexJustifyContent,
|
||||
FractionalOffset,
|
||||
Matrix4,
|
||||
Offset,
|
||||
Paint,
|
||||
@ -171,19 +172,21 @@ class ClipOval extends OneChildRenderObjectWidget {
|
||||
// POSITIONING AND SIZING NODES
|
||||
|
||||
class Transform extends OneChildRenderObjectWidget {
|
||||
Transform({ Key key, this.transform, this.origin, Widget child })
|
||||
Transform({ Key key, this.transform, this.origin, this.alignment, Widget child })
|
||||
: super(key: key, child: child) {
|
||||
assert(transform != null);
|
||||
}
|
||||
|
||||
final Matrix4 transform;
|
||||
final Offset origin;
|
||||
final FractionalOffset alignment;
|
||||
|
||||
RenderTransform createRenderObject() => new RenderTransform(transform: transform, origin: origin);
|
||||
RenderTransform createRenderObject() => new RenderTransform(transform: transform, origin: origin, alignment: alignment);
|
||||
|
||||
void updateRenderObject(RenderTransform renderObject, Transform oldWidget) {
|
||||
renderObject.transform = transform;
|
||||
renderObject.origin = origin;
|
||||
renderObject.alignment = alignment;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -48,4 +48,95 @@ void main() {
|
||||
expect(didReceiveTap, isTrue);
|
||||
});
|
||||
});
|
||||
|
||||
test('Transform alignment', () {
|
||||
testWidgets((WidgetTester tester) {
|
||||
bool didReceiveTap = false;
|
||||
tester.pumpWidget(
|
||||
new Stack([
|
||||
new Positioned(
|
||||
top: 100.0,
|
||||
left: 100.0,
|
||||
child: new Container(
|
||||
width: 100.0,
|
||||
height: 100.0,
|
||||
decoration: new BoxDecoration(
|
||||
backgroundColor: new Color(0xFF0000FF)
|
||||
)
|
||||
)
|
||||
),
|
||||
new Positioned(
|
||||
top: 100.0,
|
||||
left: 100.0,
|
||||
child: new Container(
|
||||
width: 100.0,
|
||||
height: 100.0,
|
||||
child: new Transform(
|
||||
transform: new Matrix4.identity().scale(0.5, 0.5),
|
||||
alignment: new FractionalOffset(1.0, 0.5),
|
||||
child: new GestureDetector(
|
||||
onTap: () {
|
||||
didReceiveTap = true;
|
||||
},
|
||||
child: new Container()
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
])
|
||||
);
|
||||
|
||||
expect(didReceiveTap, isFalse);
|
||||
tester.tapAt(new Point(110.0, 110.0));
|
||||
expect(didReceiveTap, isFalse);
|
||||
tester.tapAt(new Point(190.0, 150.0));
|
||||
expect(didReceiveTap, isTrue);
|
||||
});
|
||||
});
|
||||
|
||||
test('Transform offset + alignment', () {
|
||||
testWidgets((WidgetTester tester) {
|
||||
bool didReceiveTap = false;
|
||||
tester.pumpWidget(
|
||||
new Stack([
|
||||
new Positioned(
|
||||
top: 100.0,
|
||||
left: 100.0,
|
||||
child: new Container(
|
||||
width: 100.0,
|
||||
height: 100.0,
|
||||
decoration: new BoxDecoration(
|
||||
backgroundColor: new Color(0xFF0000FF)
|
||||
)
|
||||
)
|
||||
),
|
||||
new Positioned(
|
||||
top: 100.0,
|
||||
left: 100.0,
|
||||
child: new Container(
|
||||
width: 100.0,
|
||||
height: 100.0,
|
||||
child: new Transform(
|
||||
transform: new Matrix4.identity().scale(0.5, 0.5),
|
||||
origin: new Offset(100.0, 0.0),
|
||||
alignment: new FractionalOffset(0.0, 0.5),
|
||||
child: new GestureDetector(
|
||||
onTap: () {
|
||||
didReceiveTap = true;
|
||||
},
|
||||
child: new Container()
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
])
|
||||
);
|
||||
|
||||
expect(didReceiveTap, isFalse);
|
||||
tester.tapAt(new Point(110.0, 110.0));
|
||||
expect(didReceiveTap, isFalse);
|
||||
tester.tapAt(new Point(190.0, 150.0));
|
||||
expect(didReceiveTap, isTrue);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user