Flutter从入门到实战
一共分为23个系列
①(Flutter、Dart环境搭建篇) 共3个内容 已更新
②(Dart语法1 篇) 共4个内容 已更新
③(Dart语法2 篇) 共2个内容 已更新
④(Flutter案例开发篇) 共4个内容 已更新
⑤(Flutter的StatelessWidget 共3个内容 已更新
⑥(Flutter的基础Widget篇) 共2个内容 已更新
⑦(布局Widget篇) 共1个内容 已更新
⑧(Flex、Row、Column以及Flexible、Stack篇) 共1个内容 已更新
⑨(滚动的Widget篇) 共4个内容 已更新
⑩(Dart的Future和网络篇) 共3个内容 已更新
⑪(豆瓣案例-1篇) 共3个内容 已更新
⑫(豆瓣案例-2篇) 共3个内容 已更新
⑬(Flutter渲染流程篇) 共3个内容 已更新
⑭(状态管理篇) 共3个内容 已更新
⑮(Flutter事件监听-以及路由使用篇) 共2个内容 已更新
⑯(Flutter的动画篇) 共4个内容 已更新
官方文档说明
官方视频教程
Flutter的YouTube视频教程-小部件
https://picsum.photos/
代码 https://picsum.photos/200/300?random=x
其中x是随机数
官方文档
速度曲线 https://api.flutter.dev/flutter/animation/Curves-class.html
Flutter动画包含4个类的知识点
- Animation : 抽象类
监听动画值的改变 value
监听动画状态的改变 status- AnimationController继承Animation
vsync : 同步信号(this) -> with SingleTickerProviderStateMixin
forward() : 向前执行动画
reverse() : 反转执行动画- curvedAnimation :
作用:设置动画执行的速率(速度曲线)- Tween:设置动画执行的value的范围
begin : 开始值
end : 结束值
import 'package:flutter/material.dart'; // runApp在这个material库里面
main() => runApp(MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: YHHomePage(),
);
}
}
// with 混合类型 可以实现接口
// SingleTickerProviderStateMixin 必须是Stateful
/***
* // 动画笔记
*
* class YHiOSHomePage extends StatefulWidget with SingleTickerProviderStateMixin {
* Animation : 抽象类
* 监听动画值的改变
* 监听动画状态的改变
* value
* status
* 2.AnimationController继承Animation
* * vsync : 同步信号(this) -> with SingleTickerProviderStateMixin
* * forward() : 向前执行动画
* * reverse() : 反转执行动画
* 3.curvedAnimation :
* * 作用:设置动画执行的速率(速度曲线)
* 4.Tween:设置动画执行的value的范围
* * begin : 开始值
* end : 结束值
*
* final controller = AnimationController(vsync: this);
final animation = CurvedAnimation(parent: controller,curve: Curves.easeInOut);
final valueAnimation = Tween(begin: 100,end: 200).animate(animation);
*/
// 1.心跳动画
class YHHomePage extends StatefulWidget {
const YHHomePage({Key? key}) : super(key: key);
@override
State<YHHomePage> createState() => _YHHomePageState();
}
class _YHHomePageState extends State<YHHomePage> with SingleTickerProviderStateMixin {
// 1.创建AnimationController
late AnimationController _controller;
late final Animation<double> animation;
late final Animation sizeAnimation;
@override
void initState() {
super.initState();
// 设置CurvedAnimation 的时候 lowerBound 、upperBound 必须是0-1 默认就是0-1
_controller = AnimationController(
vsync: this,
duration: Duration(seconds: 2),
// lowerBound: 0.0,
// upperBound: 1.0
);
// 2. 设置curved的值
animation = CurvedAnimation(parent: _controller, curve: Curves.elasticInOut);
// 3. Tween
sizeAnimation = Tween(begin: 50.0,end: 150.0).animate(animation);
// 监听动画值的改变 - 重新构建widget
_controller.addListener(() {
setState(() {
});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("首页"),
),
body: Center(
child: Icon(Icons.favorite,color: Colors.red,size: sizeAnimation.value,),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.play_arrow),
onPressed: (){
// 向前动画
_controller.forward();
},
),
);
}
}
import 'package:flutter/material.dart'; // runApp在这个material库里面
main() => runApp(MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: YHHomePage(),
);
}
}
// with 混合类型 可以实现接口
// SingleTickerProviderStateMixin 必须是Stateful
/***
* // 动画笔记
*
* class YHiOSHomePage extends StatefulWidget with SingleTickerProviderStateMixin {
* Animation : 抽象类
* 监听动画值的改变
* 监听动画状态的改变
* value
* status
* 2.AnimationController继承Animation
* * vsync : 同步信号(this) -> with SingleTickerProviderStateMixin
* * forward() : 向前执行动画
* * reverse() : 反转执行动画
* 3.curvedAnimation :
* * 作用:设置动画执行的速率(速度曲线)
* 4.Tween:设置动画执行的value的范围
* * begin : 开始值
* end : 结束值
*
* final controller = AnimationController(vsync: this);
final animation = CurvedAnimation(parent: controller,curve: Curves.easeInOut);
final valueAnimation = Tween(begin: 100,end: 200).animate(animation);
*/
// 1.心跳动画
class YHHomePage extends StatefulWidget {
const YHHomePage({Key? key}) : super(key: key);
@override
State<YHHomePage> createState() => _YHHomePageState();
}
class _YHHomePageState extends State<YHHomePage> with SingleTickerProviderStateMixin {
// 1.创建AnimationController
late AnimationController _controller;
late final Animation<double> _animation;
late final Animation _sizeAnimation;
// bool isForward; // 动画是否向前
@override
void initState() {
super.initState();
// 设置CurvedAnimation 的时候 lowerBound 、upperBound 必须是0-1 默认就是0-1
_controller = AnimationController(
vsync: this,
duration: Duration(seconds: 2),
// lowerBound: 0.0,
// upperBound: 1.0
);
// 2. 设置curved的值
_animation = CurvedAnimation(parent: _controller, curve: Curves.linear);
// 3. Tween
_sizeAnimation = Tween(begin: 50.0,end: 150.0).animate(_animation);
// 监听动画值的改变 - 重新构建widget
_controller.addListener(() {
setState(() {
});
});
// 监听动画的变化的改变
_controller.addStatusListener((status) {
// 动画完成时
if (status == AnimationStatus.completed){
_controller.reverse(); // 进行变小
}
else if (status == AnimationStatus.dismissed){
// 动画销毁时 就继续变大
_controller.forward();
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("首页"),
),
body: Center(
child: Icon(Icons.favorite,color: Colors.red,size: _sizeAnimation.value,),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.play_arrow),
onPressed: (){
// 向前动画
// _controller.forward();
// 如果是动画是正在执行 那么 就停止动画
if(_controller.isAnimating){
_controller.stop();
}
else if (_controller.status == AnimationStatus.forward){
// 如果动画是向前的 那么就继续向前
_controller.forward();
}
else if (_controller.status == AnimationStatus.reverse){
_controller.reverse();
}
else {
_controller.forward();
}
},
),
);
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
// 回收
_controller.dispose();
}
}
上面的代码的问题
- 每次写动画、都需要一段代码
- setState -> build
优化方案AnimationWidget
将需要执行动画的Widget放到一个AnimationWidget中的build方法里面进行返回
缺点
1.每次都需要创建一个类
2.如果构建的Widget有子类,那么子类依然会重复构建AnimationBuilder
开发用的比较多
解决AnimationWidget的缺点
创建一个类 继承于 AnimationWidget
必须调用父类方法super(listenable)
import 'package:flutter/material.dart'; // runApp在这个material库里面
main() => runApp(MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: YHHomePage(),
);
}
}
// 1.心跳动画
class YHHomePage extends StatefulWidget {
const YHHomePage({Key? key}) : super(key: key);
@override
State<YHHomePage> createState() => _YHHomePageState();
}
class _YHHomePageState extends State<YHHomePage> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late final Animation<double> _animation;
late final Animation _sizeAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(seconds: 2),
);
_animation = CurvedAnimation(parent: _controller, curve: Curves.linear);
_sizeAnimation = Tween(begin: 50.0,end: 150.0).animate(_animation);
// 监听动画值的改变 - 重新构建widget
_controller.addListener(() {
setState(() {
});
});
// 监听动画的变化的改变
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed){
_controller.reverse();
}
else if (status == AnimationStatus.dismissed){
_controller.forward();
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("首页"),
),
body: Center(
child: YHAnimationIcon(_sizeAnimation), // ⭐️需要做动画的Widget继承于AnimatedWidget 这样性能更高、不会重复setState 和 build
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.play_arrow),
onPressed: (){
if(_controller.isAnimating){
_controller.stop();
}
else if (_controller.status == AnimationStatus.forward){
_controller.forward();
}
else if (_controller.status == AnimationStatus.reverse){
_controller.reverse();
}
else {
_controller.forward();
}
},
),
);
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
// 回收
_controller.dispose();
}
}
/**
* AnimatedWidget
* 缺点
* 1.每次都需要创建一个类
2.如果构建的Widget有子类,那么子类依然会重复构建
* */
class YHAnimationIcon extends AnimatedWidget {
final Animation _sizeAnim; // 可以从外部传递过来
YHAnimationIcon(this._sizeAnim):super(listenable: _sizeAnim);
// YHAnimationIcon(Animation anim):super(listenable: anim); // anim 来自于父类
// @override
Widget build(BuildContext context) {
// Listenable anim = listenable;
// 把需要动画的Widget放到这里
return Icon(Icons.favorite,color: Colors.red,size: _sizeAnim.value,);
}
}
解决了AnimationWidget的缺点
不会重新SetState和build
import 'package:flutter/material.dart'; // runApp在这个material库里面
main() => runApp(MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: YHHomePage(),
);
}
}
// 1.心跳动画
class YHHomePage extends StatefulWidget {
const YHHomePage({Key? key}) : super(key: key);
@override
State<YHHomePage> createState() => _YHHomePageState();
}
class _YHHomePageState extends State<YHHomePage> with SingleTickerProviderStateMixin {
// 1.创建AnimationController
late AnimationController _controller;
late final Animation<double> _animation;
late final Animation _sizeAnimation;
// bool isForward; // 动画是否向前
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(seconds: 2),
);
_animation = CurvedAnimation(parent: _controller, curve: Curves.linear);
_sizeAnimation = Tween(begin: 50.0,end: 150.0).animate(_animation);
// 监听动画值的改变 - 重新构建widget
_controller.addListener(() {
// 使用AnimatedBuilder 不需要setState 同样能执行动画
// setState(() {
//
// });
});
// 监听动画的变化的改变
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed){
_controller.reverse();
}
else if (status == AnimationStatus.dismissed){
_controller.forward();
}
});
}
@override
Widget build(BuildContext context) {
print("执行了_YHHomePageState 的build 方法");
return Scaffold(
appBar: AppBar(
title: Text("首页"),
),
body: Center(
child: AnimatedBuilder(
builder: (ctx,child){
return Icon(Icons.favorite,color: Colors.red,size: _sizeAnimation.value,);
},
animation: _controller,
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.play_arrow),
onPressed: (){
if(_controller.isAnimating){
_controller.stop();
}
else if (_controller.status == AnimationStatus.forward){
_controller.forward();
}
else if (_controller.status == AnimationStatus.reverse){
_controller.reverse();
}
else {
_controller.forward();
}
},
),
);
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
// 回收
_controller.dispose();
}
}
把动画放在一起 叫做交织动画
Staggered animations - 官方文档
就是监听多个Tween的值改变
大小
、颜色
、旋转
、透明度
改变做一个动画大小
、颜色
、旋转
、透明度
改变做一个动画import 'dart:math';
import 'package:flutter/material.dart'; // runApp在这个material库里面
main() => runApp(MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: YHHomePage(),
);
}
}
// 1.心跳动画
class YHHomePage extends StatefulWidget {
const YHHomePage({Key? key}) : super(key: key);
@override
State<YHHomePage> createState() => _YHHomePageState();
}
class _YHHomePageState extends State<YHHomePage> with SingleTickerProviderStateMixin {
// 1.创建AnimationController
late AnimationController _controller;
late final Animation<double> _animation;
late final Animation _sizeAnim;
late final Animation _colorAnim;
late final Animation _opacityAnim;
late final Animation _radiansAnim;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(seconds: 2),
);
_animation = CurvedAnimation(parent: _controller, curve: Curves.linear);
// 3. Tween 的 animate 有些是不支持设置CurvedAnimation。
// _sizeAnim = Tween(begin: 10.0,end: 200.0).animate(_animation); 最好不要这样写 可能会导致报错
// 3.1 创建size的 Tween 大小
_sizeAnim = Tween(begin: 10.0,end: 200.0).animate(_controller);
// 3.2 创建color的 Tween 颜色
_colorAnim = ColorTween(begin: Colors.orange,end: Colors.blue).animate(_controller);
// 3.1 创建opacity的 Tween 透明度
_opacityAnim = Tween(begin: 0.0,end: 1.0).animate(_controller);
// 3.1 创建radians的 Tween 旋转幅度
_radiansAnim = Tween(begin: 0.0,end: 2 *pi).animate(_controller);
//
// _controller.addListener(() {
// setState(() {
//
// });
// });
// 监听动画的变化的改变
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed){
_controller.reverse();
}
else if (status == AnimationStatus.dismissed){
_controller.forward();
}
});
}
@override
Widget build(BuildContext context) {
print("执行了_YHHomePageState 的build 方法");
/**
* 效果
* 1. 大小变化动画
* 2. 颜色变化动画
* 3. 透明度变化动画
* */
return Scaffold(
appBar: AppBar(
title: Text("首页"),
),
body: Center(
child:AnimatedBuilder(
builder: (ctx,child){
return Opacity(
opacity: 0.5, // 透明度
// 将需要旋转的Widget用Transform包裹
child: Transform(
transform: Matrix4.rotationZ(_radiansAnim.value), // pi是180
alignment: Alignment.center,
child: Container(
width: _sizeAnim.value,
height: _sizeAnim.value,
color: _colorAnim.value,
),
),
);
},
animation: _controller,
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.play_arrow),
onPressed: (){
if(_controller.isAnimating){
_controller.stop();
}
else if (_controller.status == AnimationStatus.forward){
_controller.forward();
}
else if (_controller.status == AnimationStatus.reverse){
_controller.reverse();
}
else {
_controller.forward();
}
},
),
);
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
// 回收
_controller.dispose();
}
}
大小
、颜色
、旋转
、透明度
改变做一个动画fullscreenDialog: true // 以模态方式弹出
import 'package:flutter/material.dart';
import 'package:learn_flutter/douban/pages/main/main.dart'; // runApp在这个material库里面
import 'pages/modal_page.dart';
main() => runApp(MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: YHiOSHomePage(),
);
}
}
class YHiOSHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("学习模板_Widget"),
),
body: Center(
child: Text("hello world"),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.pool),
onPressed: (){
Navigator.of(context).push(MaterialPageRoute(builder: (ctx){
return YHModalPage();
},
fullscreenDialog: true // 以模态方式弹出
));
},
),
);
}
}
使用 PageRouteBuilder
import 'package:flutter/material.dart';
import 'package:learn_flutter/douban/pages/main/main.dart'; // runApp在这个material库里面
import 'pages/modal_page.dart';
main() => runApp(MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: YHiOSHomePage(),
);
}
}
class YHiOSHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("学习模板_Widget"),
),
body: Center(
child: Text("hello world"),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.pool),
onPressed: (){
// 渐变动画弹出
Navigator.of(context).push(PageRouteBuilder(
transitionDuration: Duration(seconds: 1),
pageBuilder: (ctx,animation1,animation2){
return FadeTransition(
opacity: animation1,
child: YHModalPage(),
);
}));
},
),
);
}
}
Hero动画官方视频教程
Hero动画官方文档
官方是这样说明的
hero指的是在屏幕之间飞行的小部件。
使用 Flutter 的 Hero 小部件创建英雄动画。
将英雄从一个屏幕飞到另一个屏幕。
将英雄的形状从圆形转换为矩形,同时将其从一个屏幕飞到另一个屏幕。
Flutter 中的 Hero 小部件实现了一种通常称为共享元素转换或 共享元素动画的动画样式。
import 'package:flutter/material.dart';
import 'package:learn_flutter/day12_animation/pages/image_details.dart';
import 'package:learn_flutter/douban/pages/main/main.dart'; // runApp在这个material库里面
import 'pages/modal_page.dart';
main() => runApp(MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: YHiOSHomePage(),
);
}
}
class YHiOSHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("学习模板_Widget"),
),
body: Center(
child: GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 8,
mainAxisSpacing: 8,
childAspectRatio: 16/9,
),
children: List.generate(20, (index){
final imageUrl = "https://picsum.photos/500/500?random=${index}";
return GestureDetector(
onTap: (){
// MaterialPageRoute 看起来像push效果
// 换成PageRouteBuilder 做成一个动画渐变效果
Navigator.of(context).push(PageRouteBuilder(pageBuilder: (ctx,anim1,anim2){
return FadeTransition(opacity: anim1, child: YHImageDetailsPage(imageUrl));
}));
},
child: Hero(
tag:imageUrl,
child: Image.network(imageUrl,fit: BoxFit.cover,)));
}),
),
),
);
}
}
import 'package:flutter/material.dart';
class YHImageDetailsPage extends StatelessWidget {
final String _imageUrl;
YHImageDetailsPage(this._imageUrl);
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Center(
child: GestureDetector(
onTap: (){
Navigator.of(context).pop();
},
child: Hero(tag:_imageUrl, child: Image.network(_imageUrl))),
),
);
}
}