Merge pull request #1690 from abarth/transition_container
Adds EnterExitTransition widget
This commit is contained in:
commit
95803ebbf8
115
examples/widgets/smooth_resize.dart
Normal file
115
examples/widgets/smooth_resize.dart
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
// 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,
|
/// 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 {
|
|||||||
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 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
185
packages/flutter/lib/src/widgets/enter_exit_transition.dart
Normal file
185
packages/flutter/lib/src/widgets/enter_exit_transition.dart
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
// 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/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';
|
||||||
|
Loading…
x
Reference in New Issue
Block a user