Flutter动画组合插件combined_animation的使用
Flutter动画组合插件combined_animation的使用
combined_animation
是一个用于创建复杂且优雅的进入和离开动画组合的Flutter插件。它允许开发者轻松地将对齐、透明度、变换(如旋转、缩放等)、大小等动画效果应用到小部件上,从而实现丰富的视觉体验。
Features
- ✅ 对齐动画
- ✅ 透明度动画
- ✅ 变换动画
- ✅ 大小动画
- ✅ 进入动画配置
- ✅ 离开动画配置
- ✅ 消失时的大小动画
Getting Started
在开始之前,请确保你已经在项目的pubspec.yaml
文件中添加了combined_animation
依赖:
flutter pub add combined_animation
然后,在你的Dart代码中导入这个包:
import 'package:combined_animation/combined_animation.dart';
Usage
下面是一个简单的例子,展示了如何使用CombinedAnimation
来创建带有动画效果的小部件:
示例Demo
我们将创建一个包含多个动画项的应用程序,每个项都可以通过点击按钮进行删除,并伴有相应的动画效果。此外,我们还会演示如何通过状态和控制器来控制动画。
main.dart
// ignore_for_file: avoid_print
import 'dart:math' as math;
import 'package:combined_animation/combined_animation.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Animation Demo Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final widgets = <Widget>[];
final configs = <AnimationConfig>[];
final keys = <ValueKey<int>>[];
final configList = [
AnimationConfig.slideInFrom(const Alignment(0, -2)),
AnimationConfig.slideInFrom(const Alignment(0, 2)),
AnimationConfig.slideInFrom(const Alignment(-2, 0)),
AnimationConfig.slideInFrom(const Alignment(2, 0)),
AnimationConfig.slideAndFadeInFrom(const Alignment(0, -2)),
AnimationConfig.slideAndFadeInFrom(const Alignment(0, 2)),
AnimationConfig.slideAndFadeInFrom(const Alignment(-2, 0)),
AnimationConfig.slideAndFadeInFrom(const Alignment(2, 0)),
AnimationConfig.zoomIn,
AnimationConfig.fadeIn,
AnimationConfig.fadeAndZoomIn,
AnimationConfig.vFlipIn,
AnimationConfig.hFlipIn,
AnimationConfig.zoomIn.copyWith(curve: Curves.bounceOut),
AnimationConfig.fadeAndZoomIn.copyWith(curve: Curves.bounceOut),
AnimationConfig.vFlipIn.copyWith(curve: Curves.bounceOut),
AnimationConfig.hFlipIn.copyWith(curve: Curves.bounceOut),
AnimationConfig.fadeIn.copyWith(
startTransform: Matrix4.identity()..rotateZ(math.pi / 2),
endTransform: Matrix4.identity(),
curve: Curves.bounceOut,
)
];
final controller = ScrollController();
/// generate a new animation item
void _incrementCounter() {
setState(() {
final index = widgets.length;
widgets.add(Container(
width: 100.0,
height: 40.0,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Colors.primaries[index % 18],
Colors.primaries[(index + 1) % 18],
Colors.primaries[(index + 2) % 18],
],
),
borderRadius: const BorderRadius.all(Radius.circular(10)),
),
));
configs.add(configList[index % configList.length]);
keys.add(ValueKey(index == 0 ? 0 : keys.last.value + 1));
});
WidgetsBinding.instance.scheduleFrameCallback((timeStamp) {
controller.animateTo(
controller.position.maxScrollExtent,
duration: const Duration(milliseconds: 200),
curve: Curves.easeIn,
);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: SafeArea(
child: Column(
children: [
const StateDemoWidget(),
const ControlDemoWidget(),
Expanded(
child: ListView.builder(
controller: controller,
itemCount: widgets.length,
padding: const EdgeInsets.only(bottom: 80),
findChildIndexCallback: (key) {
final index = keys.indexOf(key as ValueKey<int>);
return index > -1 ? index : null;
},
itemBuilder: (context, index) {
return AnimateItem(
key: keys[index],
animate: configs[index],
onDismiss: () {
setState(() {
widgets.removeAt(index);
configs.removeAt(index);
keys.removeAt(index);
});
},
child: widgets[index],
);
},
),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'AddItem',
child: const Icon(Icons.add),
),
);
}
}
/// Generate an Anitate Widget control by button
class AnimateItem extends StatefulWidget {
const AnimateItem({
Key? key,
required this.child,
required this.animate,
this.onDismiss,
this.height = 60.0,
}) : super(key: key);
final Widget child;
final VoidCallback? onDismiss;
final double height;
final AnimationConfig animate;
@override
State<AnimateItem> createState() => _AnimateItemState();
}
class _AnimateItemState extends State<AnimateItem> {
bool isDissmissing = false;
bool isLeave = false;
@override
Widget build(BuildContext context) {
return Stack(
children: [
ConstrainedBox(
constraints: isDissmissing
? BoxConstraints.loose(Size.infinite)
: BoxConstraints.tight(Size.fromHeight(widget.height)),
child: Center(
child: CombinedAnimation(
state: isLeave ? AnimationType.end : AnimationType.start,
onLeaved: () {
setState(() {
isDissmissing = true;
});
},
onDismiss: widget.onDismiss,
config: widget.animate,
child: widget.child,
),
),
),
if (!isLeave)
Positioned(
right: 8,
top: 0,
bottom: 0,
child: Center(
child: OutlinedButton.icon(
style: OutlinedButton.styleFrom(
shape: const StadiumBorder(),
),
label: const Text('delete'),
onPressed: () {
if (isLeave) return;
setState(() {
isLeave = true;
});
},
icon: const Icon(Icons.remove),
),
),
),
],
);
}
}
class StateDemoWidget extends StatefulWidget {
const StateDemoWidget({Key? key}) : super(key: key);
@override
State<StateDemoWidget> createState() => _StateDemoWidgetState();
}
class _StateDemoWidgetState extends State<StateDemoWidget> {
AnimationType? state = AnimationType.start;
@override
Widget build(BuildContext context) {
return Stack(
children: [
Container(
alignment: Alignment.center,
height: 60,
child: CombinedAnimation(
state: state,
config: AnimationConfig.vFlipIn,
child: Container(
width: 100.0,
height: 40.0,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Colors.primaries[0][200]!,
Colors.primaries[1][200]!,
Colors.primaries[2][200]!,
],
),
borderRadius: const BorderRadius.all(Radius.circular(10)),
),
),
),
),
const Positioned(
left: 8,
top: 0,
bottom: 0,
child: Align(
alignment: Alignment.centerLeft,
child: Text(
'Control by state',
),
),
),
Positioned(
right: 8,
top: 0,
bottom: 0,
child: Center(
child: OutlinedButton.icon(
style: OutlinedButton.styleFrom(
shape: const StadiumBorder(),
),
label: Text(
state == null
? 'Show'
: state == AnimationType.start
? 'Hide'
: 'Reset',
),
onPressed: () {
final cState = (state?.index ?? -1) + 1;
setState(() {
state = cState > 1 ? null : AnimationType.values[cState];
});
},
icon: const Icon(Icons.animation),
),
),
),
],
);
}
}
/// control animation by controller
class ControlDemoWidget extends StatefulWidget {
const ControlDemoWidget({Key? key}) : super(key: key);
@override
State<ControlDemoWidget> createState() => _ControlDemoWidgetState();
}
class _ControlDemoWidgetState extends State<ControlDemoWidget> {
late CombinedAnimationController caController =
CombinedAnimationController()
..addListener(() {
print(
'${caController.state} ${caController.isEntered} ${caController.isLeaved}');
setState(() {});
});
@override
void dispose() {
caController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
Container(
alignment: Alignment.center,
height: 60,
child: CombinedAnimation(
config: AnimationConfig.vFlipIn,
controller: caController,
isControlled: true,
child: Container(
width: 100.0,
height: 40.0,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Colors.primaries[9][200]!,
Colors.primaries[10][200]!,
Colors.primaries[11][200]!,
],
),
borderRadius: const BorderRadius.all(Radius.circular(10)),
),
),
),
),
const Positioned(
left: 8,
top: 0,
bottom: 0,
child: Align(
alignment: Alignment.centerLeft,
child: Text(
'Control by controller',
),
),
),
Positioned(
right: 8,
top: 0,
bottom: 0,
child: Center(
child: OutlinedButton.icon(
style: OutlinedButton.styleFrom(
shape: const StadiumBorder(),
),
label: Text((caController.isLeaved)
? 'Reset'
: (caController.isEntered)
? 'Hide'
: 'Show'),
onPressed: () {
print(caController.isEntered);
if (caController.isEntered) {
caController.leave();
} else if (caController.isLeaved) {
caController.init();
setState(() {});
} else {
caController.enter();
}
},
icon: const Icon(Icons.animation),
),
),
),
],
);
}
}
在这个示例中,我们创建了一个可以动态添加和删除带动画效果的小部件的应用程序。每个小部件都有自己的动画配置,并且可以通过点击“delete”按钮触发消失动画。同时,我们还提供了两个演示区域,分别展示了如何通过状态和控制器来控制动画。
更多关于Flutter动画组合插件combined_animation的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter动画组合插件combined_animation的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
在Flutter中,combined_animation
并不是一个官方的插件或标准库中的组件。不过,基于你的需求,我们可以实现一个自定义的动画组合示例,通过组合多个动画来实现复杂的动画效果。
通常,我们会使用 AnimationController
、Tween
以及多个 Animation
来实现组合动画。下面是一个简单的示例,展示了如何组合多个动画(例如,缩放和旋转)来实现一个复杂的动画效果。
首先,确保你的 Flutter 环境已经设置好,并且你已经在 pubspec.yaml
文件中添加了必要的依赖(虽然这个示例不需要额外的依赖,但一般开发过程中会用到一些)。
dependencies:
flutter:
sdk: flutter
接下来,我们编写一个包含组合动画的 Flutter 应用。
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Combined Animation Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: CombinedAnimationScreen(),
);
}
}
class CombinedAnimationScreen extends StatefulWidget {
@override
_CombinedAnimationScreenState createState() => _CombinedAnimationScreenState();
}
class _CombinedAnimationScreenState extends State<CombinedAnimationScreen> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _scaleAnimation;
late Animation<double> _rotateAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
)..repeat(reverse: true); // 无限循环动画,反向播放
_scaleAnimation = Tween<double>(begin: 1.0, end: 1.5).animate(
CurvedAnimation(
parent: _controller,
curve: Interval(0.0, 0.5, curve: Curves.easeInOut),
),
);
_rotateAnimation = Tween<double>(begin: 0.0, end: 2 * 3.141592653589793).animate(
CurvedAnimation(
parent: _controller,
curve: Interval(0.0, 1.0, curve: Curves.linear),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Combined Animation Example'),
),
body: Center(
child: AnimatedBuilder(
animation: _controller,
child: Transform.scale(
scale: 1.0,
child: Container(
width: 100,
height: 100,
color: Colors.blue,
child: Center(
child: Text(
'Flutter',
style: TextStyle(color: Colors.white, fontSize: 24),
),
),
),
),
builder: (context, child) {
return Transform.rotate(
angle: _rotateAnimation.value,
child: Transform.scale(
scale: _scaleAnimation.value,
child: child,
),
);
},
),
),
);
}
}
代码解析
-
引入必要的包:我们引入了
flutter/material.dart
包。 -
创建应用入口:
MyApp
是一个StatelessWidget
,它创建了MaterialApp
实例。 -
创建主屏幕:
CombinedAnimationScreen
是一个StatefulWidget
,用于包含和管理动画状态。 -
初始化动画控制器和动画:
AnimationController
用于控制动画的时长和循环。Tween
用于定义动画的起始值和结束值。CurvedAnimation
和Interval
用于定义动画的曲线和播放时间区间。
-
组合动画:
- 使用
AnimatedBuilder
来构建动画。 - 将缩放动画和旋转动画组合在一起。
- 使用
-
释放资源:在
dispose
方法中释放AnimationController
。
这个示例展示了如何通过组合多个动画来实现复杂的动画效果。在实际开发中,你可以根据需要调整动画的曲线、时长和区间,以及组合更多的动画效果。