1. flutter编程范式
和vue很像,flutter也是采用声明式编程. 有状态管理的概念
2. StatefulWidget
- StatefulWidget是有 状态变化的widget
- 状态交给State类来管理
- 更改完状态后必须用setState()提交
3. StatelessWidget
- StatelessWidget是无状态变化的widget
- StatelessWidget里的变量应该都用final修饰
- 必须实现build方法
二. 布局
- colum和row是会尽量占更大空间的
- 如果想让colum和row内容居中,应该
mainAxisAlignment: MainAxisAlignment.center,
三. 生命周期
- 创建完成后会初始化
- 当有setState时,会把状态管理器设置为脏,下一次循环时,会触发build重构
- 当父组件进行重绘时,子组件会调用didUpdateConfig来把状态管理器设置为脏,然后重新build
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: Scaffold(
appBar: AppBar(
title: Text("data"),
),
body: MyWidget(),
),
);
}
}
class MyWidget extends StatefulWidget {
MyWidget() {
print("MyWidget的构造函数被调用");
}
@override
_MyWidgetState createState() {
print("MyWidget的createState函数被调用");
return _MyWidgetState();
}
}
class _MyWidgetState extends State<MyWidget> {
int counter = 0;
_MyWidgetState() {
print("_MyWidgetState的构造函数被调用");
}
void initState() {
super.initState();
print("_MyWidgetState的initState函数被调用");
}
@override
void didChangeDependencies() {
print("_MyWidgetState的didChangeDependencies函数被调用");
super.didChangeDependencies();
}
@override
void didUpdateWidget(MyWidget oldWidget) {
print("_MyWidgetState的didUpdateWidget函数被调用");
super.didUpdateWidget(oldWidget);
}
@override
Widget build(BuildContext context) {
print("_MyWidgetState的build函数被调用");
return Center(
child: Column(
children: <Widget>[
RaisedButton(
child: Text("+"),
onPressed: () {
setState(() {
counter++;
});
}),
Text("counter:$counter")
],
),
);
}
}
执行顺序如下:四. 网络请求DIO
1. 安装DIO
打开pubspec.yaml, 在dependencies里面添加
dio: ^3.0.3
保存的瞬间,vscode会自动运行命令:
flutter pub get
2. 封装网络请求
建立文件 lib/network/request.dart
import 'package:dio/dio.dart';
import './http_config.dart';
class HttpRequest {
static BaseOptions baseOptions = //基础选项
BaseOptions(baseUrl: BASE_URL, connectTimeout: TIMEOUT);
//1. 创建dio实例
static final dio = Dio(baseOptions); //创建实例
//类的request函数异步返回一个
static Future request(String url,
{String method = 'get', Map<String, dynamic> params}) async {
//用static 修饰,使之成为类方法,可以直接类调用
//2. 请求
Options options = Options(method: method); //本次请求的选项
try {
final result =
await dio.request(url, queryParameters: params, options: options);
return result;
} catch (e) {
throw e;
}
}
}
3. 如何调用?
调用网络请求一般是在statefulWidget 的 initSate中调用
五. 页面路由动画效果
如果想要各种酷炫的页面切换动画,需要自己重写一个PageRouteBuilder
,
整个过程如下:
新建一个文件myRouter.dart
import 'package:flutter/material.dart';
class MyRouter extends PageRouteBuilder {
final Widget widget;
MyRouter(this.widget)
: super(
transitionDuration: Duration(milliseconds: 500), //单位是可以换的
pageBuilder: (BuildContext context, Animation<double> animation1,
Animation<double> animation2) {
return widget;
},
transitionsBuilder: (BuildContext context,
Animation<double> animation1,
Animation<double> animation2,
Widget child) {
//--------------左右移动效果--------------------
return SlideTransition(
position: Tween<Offset>(
begin: Offset(-1.0, 0.0), end: Offset(0.0, 0.0))
.animate(CurvedAnimation(
parent: animation1, curve: Curves.fastOutSlowIn)),
child: child,
);
//--------------缩放效果--------------------
// return ScaleTransition(
// scale: Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation(
// parent: animation1, curve: Curves.fastOutSlowIn)),
// child: child,
// );
//--------------渐入渐出效果--------------------
// return FadeTransition(
// opacity: Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation(
// parent: animation1, curve: Curves.fastOutSlowIn)),
// child: child,
// );
//--------------旋转加缩放效果--------------------
// return RotationTransition(
// turns: Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation(
// parent: animation1, curve: Curves.fastOutSlowIn)),
// child: ScaleTransition(
// scale: Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation(
// parent: animation1, curve: Curves.fastOutSlowIn)),
// child: child,
// ),
// );
});
}
引用时:
onPressed: () {
Navigator.of(context).push(MyRouter(Page2nd()));
}),
六. TabBar //tab切换条
tabbar用法还挺特殊
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: KeepAliveDemo());
}
}
class KeepAliveDemo extends StatefulWidget {
_KeepAliveDemoState createState() => _KeepAliveDemoState();
}
/*
with是dart的关键字,意思是混入的意思,
就是说可以将一个或者多个类的功能添加到自己的类无需继承这些类,
避免多重继承导致的问题。
SingleTickerProviderStateMixin 主要是我们初始化TabController时,
需要用到vsync ,垂直属性,然后传递this
*/
class _KeepAliveDemoState extends State<KeepAliveDemo>
with SingleTickerProviderStateMixin {
TabController _controller;
List tabs = <Tab>[
Tab(
text: "tab1",
),
Tab(
text: "tab2",
),
Tab(
text: "tab3",
)
];
@override
void initState() {
//重写初始化阶段, 给_controller初始化
_controller =
TabController(length: tabs.length, vsync: this //动画效果的异步处理,默认格式,背下来即可
);
super.initState();
}
@override
void dispose() {
//重写销毁阶段, 给_controller销毁
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Keep Alive Demo'),
bottom: TabBar(
tabs: tabs,
controller: _controller,
),
),
body: TabBarView(controller: _controller, children: [
Text("111"),
Text("222"),
Text("333"),
]));
}
}
七. 保持状态
这里我们做这样一个效果:tabbar切换后, 被切换掉的页面还保持原先的状态
这个案例更综合一些,耐住性子结合上一节来看。关键处在于 with混合一个AutomaticKeepAliveClientMixin
类 重写一个get方法
import 'package:flutter/material.dart';
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>
with AutomaticKeepAliveClientMixin {
int _counter = 0;
@override
bool get wantKeepAlive => true;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("点一下加1"),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
)
],
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add), tooltip: "+++", onPressed: _incrementCounter),
);
}
}
八. 折叠瓦片
import 'package:flutter/material.dart';
class ExpandDemo extends StatefulWidget {
@override
_ExpandDemoState createState() => _ExpandDemoState();
}
class _ExpandDemoState extends State<ExpandDemo> {
bool _expandState;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("折叠瓦片")),
body: ExpansionTile(
leading: Icon(Icons.add_location),
title: Text("标题$_expandState"),
subtitle: Text("副标题"),
trailing: Icon(Icons.arrow_downward), //可以不写,有默认的
backgroundColor: Colors.white12,
initiallyExpanded: true, //默认打开
onExpansionChanged: (val) {
setState(() {
_expandState = val;
});
},
children: <Widget>[
Text("展开内容第一行"),
Image.network("https://s2.ax1x.com/2019/04/15/AXx8w8.jpg"),
],
),
);
}
}
九. 单子组件滚动视图 SingleChildScrollView
十. 可展开列表
主要使用的组件:ExpansionPanelList, ExpansionPanel
import 'package:flutter/material.dart';
class ExpandDemo extends StatefulWidget {
@override
_ExpandDemoState createState() => _ExpandDemoState();
}
class _ExpandDemoState extends State<ExpandDemo> {
List<int> mList;
List<ExpandStateBean> expandStateList;
// 初始化时清空一下列表,再生成几个栗子
_ExpandDemoState() {
mList = []; //为了生成栗子准备的列表
expandStateList = []; //存放每个项的展开状态的数组
for (var i = 0; i < 20; i++) {
mList.add(i);
expandStateList.add(ExpandStateBean(i, false));
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("可展开列表")),
body: SingleChildScrollView(
//ExpansionPanelList必须放在滚动组件里
child: ExpansionPanelList(
children: mList.map((value) {
//子元素必须是ExpansionPanel类,直接用map生成
return ExpansionPanel(
canTapOnHeader: true, //点击头部可以展开否
isExpanded: expandStateList[value].isOpen, //是否展开
headerBuilder: (context, bol) {
//头部的组件
return ListTile(
title: Text("this is No.$value"),
);
},
body: ListTile(
//身体的组件
title: Text("this is No.$value's content!"),
));
}).toList(),
expansionCallback: (num, val) {
//被点击时的回调
setState(() {
expandStateList[num].isOpen = !val;
});
}),
),
);
}
}
//创建一个类 里面存储索引和开关状态之间的关系.
class ExpandStateBean {
var isOpen;
var index;
ExpandStateBean(this.index, this.isOpen);
}
十一. 贝塞尔曲线绘制
通过绘制贝塞尔曲线,我们可以做一些异形的效果.使得我们的APP更美. 关键问题在于绘制曲线路径.
import 'package:flutter/material.dart';
class MyCliper extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("贝塞尔曲线"),
),
body: Column(
children: <Widget>[
ClipPath(
clipper: MyBottomCliper(),
child: Container(
color: Colors.amberAccent,
height: 200.0,
),
)
],
),
);
}
}
class MyBottomCliper extends CustomClipper<Path> {
//MyBottomCliper是对CustomClipper的继承,
@override
Path getClip(Size size) {
//必须重写方法getClip, 返回一个path类型的路径
var path = Path();
path.lineTo(0, 0); //就像画图一样,从第一个点开始绘制并填充
path.lineTo(0, size.height - 50); // 第二个点
var firstControlPoint =
Offset(size.width / 2, size.height); //为了绘制贝塞尔曲线,先声明两个关键点,此为控制点
var firstEndPoint = Offset(size.width, size.height - 50); //此为结束点
path.quadraticBezierTo(
firstControlPoint.dx,
firstControlPoint.dy, //绘制贝塞尔曲线
firstEndPoint.dx,
firstEndPoint.dy);
path.lineTo(size.width, 0); //回至顶部
return path;
}
@override
bool shouldReclip(CustomClipper<Path> oldClipper) {
//必须重写shouldReclip, 返回false,一般不改动
return false;
}
}
关于控制点 和 结束点 的意义, 有一点PS基础的同学看这张图就明白了
用两个贝塞尔曲线绘制波浪线, 理解了控制点和结束点的意义,就可以自由发挥了.
var firstControlPoint =
Offset(size.width / 4, size.height); //为了绘制贝塞尔曲线,先声明第一组两个关键点
var firstEndPoint = Offset(size.width / 2, size.height - 50);
path.quadraticBezierTo(
firstControlPoint.dx,
firstControlPoint.dy, //绘制第一组贝塞尔曲线
firstEndPoint.dx,
firstEndPoint.dy);
var secondControlPoint =
Offset(size.width / 4 * 3, size.height - 100); //为了绘制贝塞尔曲线,再声明第二组两个关键点
var secondEndPoint = Offset(size.width, size.height - 50);
path.quadraticBezierTo(
secondControlPoint.dx,
secondControlPoint.dy, //绘制第二组贝塞尔曲线
secondEndPoint.dx,
secondEndPoint.dy);