Commit d54d35d3 authored by Adam Barth's avatar Adam Barth

Adds EnterExitTransition widget

This widget smoothly resizes as its child changes and lets you provide entrance
and exit transitions for its children.
parent 37b48007
// 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
}
}
/// 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,
/// without making the child available for hit testing, and without taking any
......
......@@ -861,8 +861,12 @@ class RenderSizeObserver extends RenderProxyBox {
void performLayout() {
Size oldSize = hasSize ? size : null;
super.performLayout();
if (oldSize != size)
onSizeChanged(size);
if (oldSize != 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 {
}
}
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 {
OffStage({ Key key, Widget 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';
export 'src/widgets/dismissable.dart';
export 'src/widgets/drag_target.dart';
export 'src/widgets/editable_text.dart';
export 'src/widgets/enter_exit_transition.dart';
export 'src/widgets/focus.dart';
export 'src/widgets/framework.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