Commit 95803ebb authored by Adam Barth's avatar Adam Barth

Merge pull request #1690 from abarth/transition_container

Adds EnterExitTransition widget
parents a6c10185 d54d35d3
// 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/animation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
final List<Map<int, Color>> _kColors = [
Colors.amber,
Colors.yellow,
Colors.blue,
Colors.purple,
Colors.indigo,
Colors.deepOrange,
];
class SmoothBlock extends StatefulComponent {
SmoothBlock({ this.color });
final Map<int, Color> color;
SmoothBlockState createState() => new SmoothBlockState();
}
class CardTransition extends StatelessComponent {
CardTransition({
this.child,
this.performance,
this.x,
this.opacity,
this.scale
});
final Widget child;
final Performance performance;
final AnimatedValue<double> x;
final AnimatedValue<double> opacity;
final AnimatedValue<double> scale;
Widget build(BuildContext context) {
return new BuilderTransition(
performance: performance,
variables: [x, opacity, scale],
builder: (BuildContext context) {
Matrix4 transform = new Matrix4.identity()
..translate(x.value)
..scale(scale.value, scale.value);
return new Opacity(
opacity: opacity.value,
child: new Transform(
transform: transform,
child: child
)
);
}
);
}
}
class SmoothBlockState extends State<SmoothBlock> {
double _height = 100.0;
Widget _handleEnter(PerformanceView performance, Widget child) {
return new CardTransition(
x: new AnimatedValue<double>(-200.0, end: 0.0, curve: Curves.ease),
opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: Curves.ease),
scale: new AnimatedValue<double>(0.8, end: 1.0, curve: Curves.ease),
performance: performance,
child: child
);
}
Widget _handleExit(PerformanceView performance, Widget child) {
return new CardTransition(
x: new AnimatedValue<double>(0.0, end: 200.0, curve: Curves.ease),
opacity: new AnimatedValue<double>(1.0, end: 0.0, curve: Curves.ease),
scale: new AnimatedValue<double>(1.0, end: 0.8, curve: Curves.ease),
performance: performance,
child: child
);
}
Widget build(BuildContext context) {
return new GestureDetector(
onTap: () {
setState(() {
_height = _height == 100.0 ? 200.0 : 100.0;
});
},
child: new EnterExitTransition(
duration: const Duration(milliseconds: 1500),
onEnter: _handleEnter,
onExit: _handleExit,
child: new Container(
key: new ValueKey(_height),
height: _height,
decoration: new BoxDecoration(backgroundColor: config.color[_height.floor() * 4])
)
)
);
}
}
class SmoothResizeDemo extends StatelessComponent {
Widget build(BuildContext context) {
return new Block(_kColors.map((Map<int, Color> color) => new SmoothBlock(color: color)).toList());
}
}
void main() {
runApp(new SmoothResizeDemo());
}
...@@ -146,6 +146,67 @@ class RenderOverflowBox extends RenderBox with RenderObjectWithChildMixin<Render ...@@ -146,6 +146,67 @@ class RenderOverflowBox extends RenderBox with RenderObjectWithChildMixin<Render
} }
} }
/// 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({
RenderBox child,
Size requestedSize
}) : _requestedSize = requestedSize {
assert(requestedSize != null);
this.child = child;
}
/// The size this render box should attempt to be.
Size get requestedSize => _requestedSize;
Size _requestedSize;
void set requestedSize (Size value) {
assert(value != null);
if (_requestedSize == value)
return;
_requestedSize = value;
markNeedsLayout();
}
double getMinIntrinsicWidth(BoxConstraints constraints) {
return constraints.constrainWidth(_requestedSize.width);
}
double getMaxIntrinsicWidth(BoxConstraints constraints) {
return constraints.constrainWidth(_requestedSize.width);
}
double getMinIntrinsicHeight(BoxConstraints constraints) {
return constraints.constrainWidth(_requestedSize.height);
}
double getMaxIntrinsicHeight(BoxConstraints constraints) {
return constraints.constrainWidth(_requestedSize.height);
}
double computeDistanceToActualBaseline(TextBaseline baseline) {
if (child != null)
return child.getDistanceToActualBaseline(baseline);
return super.computeDistanceToActualBaseline(baseline);
}
void performLayout() {
size = constraints.constrain(_requestedSize);
if (child != null)
child.layout(constraints);
}
void hitTestChildren(HitTestResult result, { Point position }) {
if (child != null)
child.hitTest(result, position: position);
else
super.hitTestChildren(result, position: position);
}
void paint(PaintingContext context, Offset offset) {
if (child != null)
context.paintChild(child, offset.toPoint());
}
}
/// Lays the child out as if it was in the tree, but without painting anything, /// Lays the child out as if it was in the tree, but without painting anything,
/// without making the child available for hit testing, and without taking any /// without making the child available for hit testing, and without taking any
......
...@@ -861,8 +861,12 @@ class RenderSizeObserver extends RenderProxyBox { ...@@ -861,8 +861,12 @@ class RenderSizeObserver extends RenderProxyBox {
void performLayout() { void performLayout() {
Size oldSize = hasSize ? size : null; Size oldSize = hasSize ? size : null;
super.performLayout(); super.performLayout();
if (oldSize != size) if (oldSize != size) {
onSizeChanged(size); // We make a copy of the Size object here because if we leak a _DebugSize
// object out of the render tree, we can get confused later if it comes
// back and gets set as the size property of a RenderBox.
onSizeChanged(new Size(size.width, size.height));
}
} }
} }
......
...@@ -346,6 +346,19 @@ class OverflowBox extends OneChildRenderObjectWidget { ...@@ -346,6 +346,19 @@ class OverflowBox extends OneChildRenderObjectWidget {
} }
} }
class SizedOverflowBox extends OneChildRenderObjectWidget {
SizedOverflowBox({ Key key, this.size, Widget child })
: super(key: key, child: child);
final Size size;
RenderSizedOverflowBox createRenderObject() => new RenderSizedOverflowBox(requestedSize: size);
void updateRenderObject(RenderSizedOverflowBox renderObject, SizedOverflowBox oldWidget) {
renderObject.requestedSize = size;
}
}
class OffStage extends OneChildRenderObjectWidget { class OffStage extends OneChildRenderObjectWidget {
OffStage({ Key key, Widget child }) OffStage({ Key key, Widget child })
: super(key: key, child: child); : super(key: key, child: child);
......
// 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 'dart:async';
import 'package:flutter/animation.dart';
import 'basic.dart';
import 'framework.dart';
class SmoothlyResizingOverflowBox extends StatefulComponent {
SmoothlyResizingOverflowBox({
Key key,
this.child,
this.size,
this.duration,
this.curve: Curves.linear
}) : super(key: key) {
assert(duration != null);
assert(curve != null);
}
final Widget child;
final Size size;
final Duration duration;
final Curve curve;
_SmoothlyResizingOverflowBoxState createState() => new _SmoothlyResizingOverflowBoxState();
}
class _SmoothlyResizingOverflowBoxState extends State<SmoothlyResizingOverflowBox> {
ValuePerformance<Size> _size;
void initState() {
super.initState();
_size = new ValuePerformance(
variable: new AnimatedSizeValue(config.size, curve: config.curve),
duration: config.duration
)..addListener(() {
setState(() {});
});
}
void didUpdateConfig(SmoothlyResizingOverflowBox oldConfig) {
_size.duration = config.duration;
_size.variable.curve = config.curve;
if (config.size != oldConfig.size) {
AnimatedSizeValue variable = _size.variable;
variable.begin = variable.value;
variable.end = config.size;
_size.progress = 0.0;
_size.play();
}
}
void dispose() {
_size.stop();
super.dispose();
}
Widget build(BuildContext context) {
return new SizedOverflowBox(
size: _size.value,
child: config.child
);
}
}
class _Entry {
_Entry({
this.child,
this.enterPerformance,
this.enterTransition
});
final Widget child;
final Performance enterPerformance;
final Widget enterTransition;
Size childSize = Size.zero;
Performance exitPerformance;
Widget exitTransition;
Widget get currentTransition => exitTransition ?? enterTransition;
void dispose() {
enterPerformance?.stop();
exitPerformance?.stop();
}
}
typedef Widget TransitionBuilderCallback(PerformanceView performance, Widget child);
class EnterExitTransition extends StatefulComponent {
EnterExitTransition({
Key key,
this.child,
this.duration,
this.curve: Curves.linear,
this.onEnter,
this.onExit
}) : super(key: key) {
assert(child != null);
assert(duration != null);
assert(curve != null);
assert(onEnter != null);
assert(onExit != null);
}
final Widget child;
final Duration duration;
final Curve curve;
final TransitionBuilderCallback onEnter;
final TransitionBuilderCallback onExit;
_EnterExitTransitionState createState() => new _EnterExitTransitionState();
}
class _EnterExitTransitionState extends State<EnterExitTransition> {
final List<_Entry> _entries = new List<_Entry>();
void initState() {
super.initState();
_entries.add(_createEnterTransition());
}
_Entry _createEnterTransition() {
Performance enterPerformance = new Performance(duration: config.duration)..play();
return new _Entry(
child: config.child,
enterPerformance: enterPerformance,
enterTransition: config.onEnter(enterPerformance, new KeyedSubtree(
key: new GlobalKey(),
child: config.child
))
);
}
Future _createExitTransition(_Entry entry) async {
Performance exitPerformance = new Performance(duration: config.duration);
entry
..exitPerformance = exitPerformance
..exitTransition = config.onExit(exitPerformance, entry.enterTransition);
await exitPerformance.play();
if (!mounted)
return;
setState(() {
_entries.remove(entry);
});
}
void didUpdateConfig(EnterExitTransition oldConfig) {
if (config.child.key != oldConfig.child.key) {
_createExitTransition(_entries.last);
_entries.add(_createEnterTransition());
}
}
void dispose() {
for (_Entry entry in new List<_Entry>.from(_entries))
entry.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return new SmoothlyResizingOverflowBox(
size: _entries.last.childSize,
duration: config.duration,
curve: config.curve,
child: new Stack(_entries.map((_Entry entry) {
return new SizeObserver(
key: new ObjectKey(entry),
onSizeChanged: (Size newSize) {
setState(() {
entry.childSize = newSize;
});
},
child: entry.currentTransition
);
}).toList())
);
}
}
...@@ -11,6 +11,7 @@ export 'src/widgets/binding.dart'; ...@@ -11,6 +11,7 @@ export 'src/widgets/binding.dart';
export 'src/widgets/dismissable.dart'; export 'src/widgets/dismissable.dart';
export 'src/widgets/drag_target.dart'; export 'src/widgets/drag_target.dart';
export 'src/widgets/editable_text.dart'; export 'src/widgets/editable_text.dart';
export 'src/widgets/enter_exit_transition.dart';
export 'src/widgets/focus.dart'; export 'src/widgets/focus.dart';
export 'src/widgets/framework.dart'; export 'src/widgets/framework.dart';
export 'src/widgets/gesture_detector.dart'; export 'src/widgets/gesture_detector.dart';
......
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