Flutter AnimatedSize 仅在一个方向上起作用
Flutter AnimatedSize works in one direction only
我从 https://medium.com/flutter-community/flutter-working-with-animatedsize-35253ff8f16a
获得了以下代码
它正在使用 AnimatedSize,但动画仅在容器扩展时起作用,而不是在容器收缩时起作用。这是默认行为吗?我想在展开和收缩的同时进行动画处理。
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
double _height = 80.0;
double _width = 80.0;
var _color = Colors.blue;
bool _resized = false;
@override
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
AnimatedSize(
curve: Curves.easeIn,
vsync: this,
duration: new Duration(seconds: 1),
child: new GestureDetector(
onTap: () {
setState(() {
if (_resized) {
_resized = false;
_color = Colors.blue;
_height = 80.0;
_width = 80.0;
} else {
_resized = true;
_color = Colors.blue;
_height = 320.0;
_width = 320.0;
}
});
},
child: new Container(
width: _width,
height: _height,
color: _color,
),
),
),
],
),
),
);
}
}
AnimatedContainer
是您要查找的内容,不需要 AnimatedSize
。
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
double _height = 80.0;
double _width = 80.0;
var _color = Colors.blue;
bool _resized = false;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
GestureDetector(
onTap: () {
setState(() {
if (_resized) {
_resized = false;
_color = Colors.blue;
_height = 80.0;
_width = 80.0;
} else {
_resized = true;
_color = Colors.blue;
_height = 320.0;
_width = 320.0;
}
});
},
child: AnimatedContainer(
duration: Duration(seconds: 1),
width: _width,
height: _height,
color: _color,
),
),
],
),
),
);
}
}
或者,如果您需要 AnimatedSize
,您可以添加一个新容器,并将颜色作为 AnimatedSize
父容器。
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {
double _height = 320.0;
double _width = 320.0;
var _color = Colors.blue;
bool _resized = false;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: GestureDetector(
onTap: () {
setState(() {
if (_resized) {
_resized = false;
_color = Colors.blue;
_height = 80.0;
_width = 80.0;
} else {
_resized = true;
_color = Colors.blue;
_height = 320.0;
_width = 320.0;
}
});
},
child: Container(
color: _color,
child: AnimatedSize(
curve: Curves.easeIn,
vsync: this,
duration: Duration(seconds: 1),
child: Container(
width: _width,
height: _height,
),
),
),
),
),
);
}
}
AnimatedContainer 在使用文本、列表或其他可变大小的小部件时并不是真正的选择,因为它不允许宽度或高度为 null。此外,它不会让您指定对齐方式。
相反,我使用带有动画控制器的 ClipRect 修复了这个问题。在下面的动画中,您可以看到收盘反弹中中心线左侧的单词 'laboris'。这表明文本在容器关闭期间居中,只需关注 'laboris'.
我创建了一个 AnimatedClipRect 小部件 class,您可以轻松地自己实现它。您可以指定对齐方式、曲线、持续时间以及是否需要水平 and/or 垂直动画。现在它假定您只想完全关闭或打开它:
class AnimatedClipRect extends StatefulWidget {
@override
_AnimatedClipRectState createState() => _AnimatedClipRectState();
final Widget child;
final bool open;
final bool horizontalAnimation;
final bool verticalAnimation;
final Alignment alignment;
final Duration duration;
final Duration? reverseDuration;
final Curve curve;
final Curve? reverseCurve;
///The behavior of the controller when [AccessibilityFeatures.disableAnimations] is true.
final AnimationBehavior animationBehavior;
const AnimatedClipRect({
Key? key,
required this.child,
required this.open,
this.horizontalAnimation = true,
this.verticalAnimation = true,
this.alignment = Alignment.center,
this.duration = const Duration(milliseconds: 500),
this.reverseDuration,
this.curve = Curves.linear,
this.reverseCurve,
this.animationBehavior = AnimationBehavior.normal,
}) : super(key: key);
}
class _AnimatedClipRectState extends State<AnimatedClipRect> with TickerProviderStateMixin {
late AnimationController _animationController;
late Animation _animation;
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
void initState() {
_animationController = AnimationController(
duration: widget.duration,
reverseDuration: widget.reverseDuration ?? widget.duration,
vsync: this,
value: widget.open ? 1.0 : 0.0,
animationBehavior: widget.animationBehavior);
_animation = Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation(
parent: _animationController,
curve: widget.curve,
reverseCurve: widget.reverseCurve ?? widget.curve,
));
super.initState();
}
@override
Widget build(BuildContext context) {
widget.open ? _animationController.forward() : _animationController.reverse();
return ClipRect(
child: AnimatedBuilder(
animation: _animationController,
builder: (_, child) {
return Align(
alignment: widget.alignment,
heightFactor: widget.verticalAnimation ? _animation.value : 1.0,
widthFactor: widget.horizontalAnimation ? _animation.value : 1.0,
child: child,
);
},
child: widget.child,
),
);
}
}
基本用法示例,将您想要的任何动画放入其子项中:
// declare bool _open somewhere
Column(
children: <Widget>[
AnimatedClipRect(
open: _open,
horizontalAnimation: false,
verticalAnimation: true,
alignment: Alignment.center,
duration: const Duration(milliseconds: 1000),
curve: Curves.bounceOut,
reverseCurve: Curves.bounceIn,
child: Container(
color: Colors.lightGreenAccent,
padding: const EdgeInsets.all(8),
child: const Text(
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'),
),
),
ElevatedButton(
child: const Text("open/close"),
onPressed: () {
setState(() => _open ^= true);
}),
],
)
如您所见,您只需更改 _open 并执行 setState((){}) 即可触发动画。
Animated Size 给我带来了和你一样的问题,但后来我查看了 AnimatedCrossFade 的源代码,它使用了 Animated Size 并且在缩小和扩展时都对尺寸进行了动画处理。使用 LayoutBuilder 作为 AnimatedSize 的子项,并将实际的子项放入 LayoutBuilder 中。我不确定它为什么会起作用,但 AnimatedCrossFade 就是这样。
小部件 SizeTransition
对我有用。当子高度或宽度也为 null
时,它会起作用。
这是我的示例代码:
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
@override
Widget build(BuildContext context) {
return const MaterialApp(
title: _title,
home: MyStatefulWidget(),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
@override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget>
with TickerProviderStateMixin {
late final AnimationController _controller = AnimationController(
duration: const Duration(seconds: 1),
vsync: this,
);
late final Animation<double> _animation = CurvedAnimation(
parent: _controller,
curve: Curves.fastOutSlowIn,
);
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizeTransition(
sizeFactor: _animation,
axis: Axis.vertical,
axisAlignment: 1,
child: Container(
color: Colors.amberAccent,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: const [Text("Hello"), Text("World")],
),
),
),
TextButton(
child: const Text('toggle'),
onPressed: () => _controller.isDismissed
? _controller.forward()
: _controller.reverse(),
),
],
),
);
}
}
我从 https://medium.com/flutter-community/flutter-working-with-animatedsize-35253ff8f16a
获得了以下代码它正在使用 AnimatedSize,但动画仅在容器扩展时起作用,而不是在容器收缩时起作用。这是默认行为吗?我想在展开和收缩的同时进行动画处理。
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
double _height = 80.0;
double _width = 80.0;
var _color = Colors.blue;
bool _resized = false;
@override
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
AnimatedSize(
curve: Curves.easeIn,
vsync: this,
duration: new Duration(seconds: 1),
child: new GestureDetector(
onTap: () {
setState(() {
if (_resized) {
_resized = false;
_color = Colors.blue;
_height = 80.0;
_width = 80.0;
} else {
_resized = true;
_color = Colors.blue;
_height = 320.0;
_width = 320.0;
}
});
},
child: new Container(
width: _width,
height: _height,
color: _color,
),
),
),
],
),
),
);
}
}
AnimatedContainer
是您要查找的内容,不需要 AnimatedSize
。
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
double _height = 80.0;
double _width = 80.0;
var _color = Colors.blue;
bool _resized = false;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
GestureDetector(
onTap: () {
setState(() {
if (_resized) {
_resized = false;
_color = Colors.blue;
_height = 80.0;
_width = 80.0;
} else {
_resized = true;
_color = Colors.blue;
_height = 320.0;
_width = 320.0;
}
});
},
child: AnimatedContainer(
duration: Duration(seconds: 1),
width: _width,
height: _height,
color: _color,
),
),
],
),
),
);
}
}
或者,如果您需要 AnimatedSize
,您可以添加一个新容器,并将颜色作为 AnimatedSize
父容器。
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {
double _height = 320.0;
double _width = 320.0;
var _color = Colors.blue;
bool _resized = false;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: GestureDetector(
onTap: () {
setState(() {
if (_resized) {
_resized = false;
_color = Colors.blue;
_height = 80.0;
_width = 80.0;
} else {
_resized = true;
_color = Colors.blue;
_height = 320.0;
_width = 320.0;
}
});
},
child: Container(
color: _color,
child: AnimatedSize(
curve: Curves.easeIn,
vsync: this,
duration: Duration(seconds: 1),
child: Container(
width: _width,
height: _height,
),
),
),
),
),
);
}
}
AnimatedContainer 在使用文本、列表或其他可变大小的小部件时并不是真正的选择,因为它不允许宽度或高度为 null。此外,它不会让您指定对齐方式。
相反,我使用带有动画控制器的 ClipRect 修复了这个问题。在下面的动画中,您可以看到收盘反弹中中心线左侧的单词 'laboris'。这表明文本在容器关闭期间居中,只需关注 'laboris'.
我创建了一个 AnimatedClipRect 小部件 class,您可以轻松地自己实现它。您可以指定对齐方式、曲线、持续时间以及是否需要水平 and/or 垂直动画。现在它假定您只想完全关闭或打开它:
class AnimatedClipRect extends StatefulWidget {
@override
_AnimatedClipRectState createState() => _AnimatedClipRectState();
final Widget child;
final bool open;
final bool horizontalAnimation;
final bool verticalAnimation;
final Alignment alignment;
final Duration duration;
final Duration? reverseDuration;
final Curve curve;
final Curve? reverseCurve;
///The behavior of the controller when [AccessibilityFeatures.disableAnimations] is true.
final AnimationBehavior animationBehavior;
const AnimatedClipRect({
Key? key,
required this.child,
required this.open,
this.horizontalAnimation = true,
this.verticalAnimation = true,
this.alignment = Alignment.center,
this.duration = const Duration(milliseconds: 500),
this.reverseDuration,
this.curve = Curves.linear,
this.reverseCurve,
this.animationBehavior = AnimationBehavior.normal,
}) : super(key: key);
}
class _AnimatedClipRectState extends State<AnimatedClipRect> with TickerProviderStateMixin {
late AnimationController _animationController;
late Animation _animation;
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
void initState() {
_animationController = AnimationController(
duration: widget.duration,
reverseDuration: widget.reverseDuration ?? widget.duration,
vsync: this,
value: widget.open ? 1.0 : 0.0,
animationBehavior: widget.animationBehavior);
_animation = Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation(
parent: _animationController,
curve: widget.curve,
reverseCurve: widget.reverseCurve ?? widget.curve,
));
super.initState();
}
@override
Widget build(BuildContext context) {
widget.open ? _animationController.forward() : _animationController.reverse();
return ClipRect(
child: AnimatedBuilder(
animation: _animationController,
builder: (_, child) {
return Align(
alignment: widget.alignment,
heightFactor: widget.verticalAnimation ? _animation.value : 1.0,
widthFactor: widget.horizontalAnimation ? _animation.value : 1.0,
child: child,
);
},
child: widget.child,
),
);
}
}
基本用法示例,将您想要的任何动画放入其子项中:
// declare bool _open somewhere
Column(
children: <Widget>[
AnimatedClipRect(
open: _open,
horizontalAnimation: false,
verticalAnimation: true,
alignment: Alignment.center,
duration: const Duration(milliseconds: 1000),
curve: Curves.bounceOut,
reverseCurve: Curves.bounceIn,
child: Container(
color: Colors.lightGreenAccent,
padding: const EdgeInsets.all(8),
child: const Text(
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'),
),
),
ElevatedButton(
child: const Text("open/close"),
onPressed: () {
setState(() => _open ^= true);
}),
],
)
如您所见,您只需更改 _open 并执行 setState((){}) 即可触发动画。
Animated Size 给我带来了和你一样的问题,但后来我查看了 AnimatedCrossFade 的源代码,它使用了 Animated Size 并且在缩小和扩展时都对尺寸进行了动画处理。使用 LayoutBuilder 作为 AnimatedSize 的子项,并将实际的子项放入 LayoutBuilder 中。我不确定它为什么会起作用,但 AnimatedCrossFade 就是这样。
小部件 SizeTransition
对我有用。当子高度或宽度也为 null
时,它会起作用。
这是我的示例代码:
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
@override
Widget build(BuildContext context) {
return const MaterialApp(
title: _title,
home: MyStatefulWidget(),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
@override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget>
with TickerProviderStateMixin {
late final AnimationController _controller = AnimationController(
duration: const Duration(seconds: 1),
vsync: this,
);
late final Animation<double> _animation = CurvedAnimation(
parent: _controller,
curve: Curves.fastOutSlowIn,
);
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizeTransition(
sizeFactor: _animation,
axis: Axis.vertical,
axisAlignment: 1,
child: Container(
color: Colors.amberAccent,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: const [Text("Hello"), Text("World")],
),
),
),
TextButton(
child: const Text('toggle'),
onPressed: () => _controller.isDismissed
? _controller.forward()
: _controller.reverse(),
),
],
),
);
}
}