前言
在Flutter中,几乎一切的对象都是widget,不仅是UI元素,还有手势事件的检测,用于APP主题数据传递的Theme等等,可能你会认为widget是绘制到屏幕上的元素,其实不是的,widget只是一种配置信息,是生成element的指令集,真正绘制到屏幕上的是element,在widget第一次build的时候,其实这个时候会给widget树中的每一个widget生成相对应的element,然后这些element会组成一个element树,而且每一个element中还会拥有对应的widget的对象的引用,总之,element才是是绘制在屏幕上的元素
我们在实际的开发中,面对的最多的是 StatefulWidget和StatelessWidget,他们是Widget的直接子类,我们看到的每一个页面,几乎都是StatefulWidget
1.StatelessWidget
StatelessWidget是无状态的组件,它不支持调用setState方法实现页面刷新,有自己的生命周期,但是很简单,如下代码
class StatelessTest extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Material(
child: Container(
alignment: Alignment.center,
child: Text("StatelessWidget"),
),
);
}
}
这段代码其实就是一个实现了文字居中的页面,效果图如下
2.StatefulWidget
StatefulWidget是有状态的组件,在flutter看到的每一个页面几乎都是StatefulWidget,支持调用setState方法实现页面刷新,但在实际开发中不建议使用setState实现页面刷新,因为setState会导致整个页面刷新,可以使用provider替代,实现局部刷新,代码如下
class StatefulTest extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return StatefulState();
}
}
class StatefulState extends State<StatefulTest> {
int num = 0;
@override
Widget build(BuildContext context) {
return Material(
child: Container(
alignment: Alignment.center,
child: Column(
mainAxisSize: MainAxisSize.min,//表示高度自适应
children: [
Text("$num"),
Container(
width: 100,
height: 40,
margin: EdgeInsets.only(top: 50),
child: RaisedButton(
onPressed: () {
num++;
setState(() {
});
},
child: Text("add"),
),
)
],
),
),
);
}
}
这其实就是实现一个按钮点击然后增加数字的功能,效果图如下
点击add按钮数字会增加,每次点击add按钮后将num加1,然后调用setState方法,导致build方法被重新调用,从而刷新页面,实现数字的增加,其实build方法是StatefulWidget生命周期的一个方法,还有其他的生命周期方法
3.StatefulWidget生命周期
以前学习android的时候,就学习过activity的生命周期,而StatefulWidget作为flutter的页面承载类,也有自己的生命周期
1. 流程图
- 启动一个页面的时候会调用initState,didChangeDependencies,build三个生命周期方法
- 关闭页面的时候会调用deactivate和dispose 方法
- 调用setState的时候build方法会被重新调用
2.生命周期的各个方法含义:
1. initState
这是创建widget时调用的除构造方法外的第一个方法,类似于Android的onCreate() ,经常做 一些初始化的操作
2. didChangeDependencies
当依赖的State对象改变时会调用:
- 在第一次构建widget时,在initState()之后立即调用此方法;
- 如果的StatefulWidgets依赖于InheritedWidget,那么当前State所依赖InheritedWidget 中的变量改变时会再次调用它,InheritedWidget可以高效的将数据在Widget树中向下传递、共享,可参考:https://book.flutterchina.club/chapter7/inherited_widget.html
3. build
这里返回页面展示的UI组件,是必须实现的方法,调用setState会重新调用
4. deactivate
很少使用,在组件被移除时调用在dispose之前调用
5. dispose
常用,组件被销毁时被调用,通常在该方法中执行一些资源的释放工作比如,监听器的卸载
3. 实例代码
class WidgetLifecycle extends StatefulWidget {
///当我们构建一个新的StatefulWidget时,这个会立即调用
///并且这个方法必须被覆盖
@override
_WidgetLifecycleState createState() => _WidgetLifecycleState();
}
class _WidgetLifecycleState extends State<WidgetLifecycle> {
int _count = 0;
///这是创建widget时调用的除构造方法外的第一个方法:
///类似于Android的:onCreate() 与iOS的 viewDidLoad()
///在这个方法中通常会做一些初始化工作,比如channel的初始化,监听器的初始化等
@override
void initState() {
print('----initState----');
super.initState();
}
///当依赖的State对象改变时会调用:
///a.在第一次构建widget时,在initState()之后立即调用此方法;
///b.如果的StatefulWidgets依赖于InheritedWidget,那么当当前State所依赖InheritedWidget中的变量改变时会再次调用它
///拓展:InheritedWidget可以高效的将数据在Widget树中向下传递、共享,可参考:https://book.flutterchina.club/chapter7/inherited_widget.html
@override
void didChangeDependencies() {
print('---didChangeDependencies----');
super.didChangeDependencies();
}
///这是一个必须实现的方法,在这里实现你要呈现的页面内容:
///它会在在didChangeDependencies()之后立即调用;
///另外当调用setState后也会再次调用该方法;
@override
Widget build(BuildContext context) {
print('---build-----');
return Scaffold(
appBar: AppBar(
title: Text('Flutter页面生命周期'),
leading: BackButton(),
),
body: Center(
child: Column(
children: <Widget>[
RaisedButton(
onPressed: () {
setState(() {
_count += 1;
});
},
child: Text(
'点我',
style: TextStyle(fontSize: 26),
),
),
Text(_count.toString())
],
),
),
);
}
///很少使用,在组件被移除时调用在dispose之前调用
@override
void deactivate() {
print('-----deactivate------');
super.deactivate();
}
///常用,组件被销毁时调用:
///通常在该方法中执行一些资源的释放工作比如,监听器的卸载,channel的销毁等
@override
void dispose() {
print('-----dispose-----');
super.dispose();
}
}
4.常用的widget
我们看到的flutter页面其实就是一个个widget互相嵌套组装而成的,在实际项目开发中,难免会出现大量的widget 互相嵌套的情况
1. MaterialApp
在学习Flutter的过程中我们第一个看见的控件应该就是MaterialApp,毕竟创建一个新的Flutter项目的时候,项目第一个组件就是MaterialApp,这是一个Material风格的根控件,不仅可以设置路由,还可以设置主题颜色,展示Material风格的页面等等,基本使用如下:
class MaterialAppTest extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return new RouterTestState();
}
}
class RouterTestState extends State {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(primarySwatch: Colors.blue), 主题颜色为蓝色
home: Scaffold(
appBar: AppBar(title: Text('MaterialAppTest1')), //标题栏
body: Container(
alignment: Alignment.center,
child: Column(
children: <Widget>[
Container(
child: RaisedButton(
onPressed: () => {Navigator.pushNamed(context, "newPage")},
child: Text("跳转新页面")))
],
)),
),
routes: <String, WidgetBuilder>{
//设置路由
"newPage": (BuildContext context) => FuJuStudy(),
},
);
}
}
效果如下,主题颜色为蓝色,标题栏标题为MaterialAppTest,下面有一个按钮,跳转可以到一个新的页面
2. Scaffold
Scaffold提供了Material风格的基本控件,例如从左边和右边出现的抽屉式控件,还有底部导航等,一般和MaterialApp结合使用,使用如下:
Scaffold(
drawer: Drawer(),
)
效果如下:左边出现的抽屉式控件
3. Text
文本控件,相当于android中的TextView,使用简单,控制文本样式使用TextStyle,代码如下
Text(
"文字使用",
style: TextStyle(
fontSize: 15,
color: Colors.white,
backgroundColor: Colors.black),
)
效果如下
4. Image
mage用于加载图片,相当于安卓中的ImageView,可以加载网络图片,assest图片,本地图片,使用方法如下,加载一张网络图片,并且设置宽高为100*100
Image.network(
"http://www.devio.org/img/avatar.png",
width: 100,
height: 100,
)
效果如下:
5. Container
容器类widget,用于设置widget的背景,外边距,内边距,对齐方式,控件的间隔,分割线等等,很常用的一个widget,使用如下
Container(
margin: EdgeInsets.all(15),
padding: EdgeInsets.all(15),
color: Colors.blue,
child: Container(
color: Colors.yellow,
width: 200,
height: 200,
alignment: Alignment.center,
child: Text(
'Container',
style: TextStyle(fontSize: 20, color: Colors.white),
),
),
alignment: Alignment.center,
height: 100,
)
效果图如下,一个蓝色背景的Container包含了一个黄色背景的Container,黄色背景的Container又包含了一个白色的Text
6. Column
顾名思义,列,是一个类似于竖直方向的LinearLayout,实现子控件的竖直排列,可以设置子控件在水平和竖直方向上的对齐方式 ,代码如下
Column(
mainAxisAlignment: MainAxisAlignment.center,//(主轴)竖直方向居中
crossAxisAlignment: CrossAxisAlignment.start,//(交叉轴)水平方向左对齐
mainAxisSize: MainAxisSize.max,//竖直方向填充父类,如果改成MainAxisSize.min,那么Column高度变成自适应
children: [
Container(
height: 50,
color: Colors.green,
),
Container(
color: Colors.blue,
width: 100,
height: 100,
margin: EdgeInsets.only(top: 15),
)
],
)
效果如下,其实就是一个Column,设置水平方向左对齐,竖直方向居中对齐,所以就会呈现出下面的效果
7. SingleChildScrollView
只有一个child的 ScrollView,类似于原生安卓的ScrollView,使用如下
SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
color: Colors.blue,
margin: EdgeInsets.only(top: 10),
height: 100,
width: 100,
),
Container(
color: Colors.green,
margin: EdgeInsets.only(top: 10),
height: 100,
),
Container(
color: Colors.blue,
margin: EdgeInsets.only(top: 10),
height: 100,
width: 100,
),
Container(
color: Colors.green,
margin: EdgeInsets.only(top: 10),
height: 100,
),
Container(
color: Colors.blue,
margin: EdgeInsets.only(top: 10),
height: 100,
width: 100,
),
Container(
color: Colors.green,
margin: EdgeInsets.only(top: 10),
height: 100,
),
Container(
color: Colors.blue,
margin: EdgeInsets.only(top: 10),
height: 100,
width: 100,
),
Container(
color: Colors.green,
margin: EdgeInsets.only(top: 10),
height: 100,
),
],
),
)
效果如下,可以看到蓝色和绿色的方块一直向下排列,并且可以滚动
8. Stack
作用类似于android的FrameLayout,子组件可以根据父组件四个角的位置来确定组件的位置,但是需要结合Position组件来确定子组件的位置,Positioned内部提供了left,right,bottom,top四个参数来确定子组件距离 Stack上下左右的距离,使用如下
Stack(
children: [
Positioned(
left: 50,
top: 50,
child: Container(
color: Colors.blue,
width: 50,
height: 50,
),
),
Positioned(
left: 100,
top: 100,
child: Container(
color: Colors.red,
width: 50,
height: 50,
),
)
],
)
效果如下,蓝色方块宽高各50,距离左边和上边都是50,红色方块宽高各50,距离左边和上边都是100
9. Row
顾名思义,行,类似于android的水平的LinearLayout,实现控件的水平排列,使用如下
Container(
color: Colors.green,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,// 水平方向为左对齐
crossAxisAlignment: CrossAxisAlignment.center,//竖直方向居中对齐
mainAxisSize: MainAxisSize.min,//水平方向宽度自适应,改成MainAxisSize.max宽度变成填充屏幕
children: [
Container(
width: 100,
height: 500,
color: Colors.red,
),
Container(
width: 100,
height: 100,
color: Colors.blue,
),
Container(
width: 100,
height: 100,
color: Colors.yellow,
)
],
),
)
效果如下,绿色的Container包着Row,Row的宽度自适应,水平方向左对齐,竖直方向居中对齐,内部包着红色的Container,蓝色的Container,黄色的Container,从左往右排列
10. ElevatedButton
类似于android的Button,可以设置点击事件和长按事件,使用如下
ElevatedButton(
child: Text("ElevatedButton"),
style: ButtonStyle(
backgroundColor: MaterialStateProperty.resolveWith((states) {
//设置按下时的背景颜色
if (states.contains(MaterialState.pressed)) {
return Colors.blue[200];
}
//默认不使用背景颜色
return Colors.green;
}),
//设置水波纹颜色
overlayColor: MaterialStateProperty.all(Colors.yellow),
//设置阴影 不适用于这里的TextButton
elevation: MaterialStateProperty.all(0),
//设置按钮内边距
padding: MaterialStateProperty.all(EdgeInsets.all(10)),
//设置按钮的大小
minimumSize: MaterialStateProperty.all(Size(200, 50)),
//设置圆角
shape: MaterialStateProperty.resolveWith((states) => RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(30)))),
),
onPressed: () {
print("点击");
},
onLongPress: () {
print("长按");
},
)
效果如下:
11. ListView
用于展示列表数据,在数据较少的时候可以使用以下方式生成一个列表
ListView(
children: <Widget>[
widget1, widget2, widget3,
],
)
以上的方式是一次性加载所有数据的,没有懒加载,不适合加载大量数据,加载大量数据的时候使用下面的方式,用ListView.builder()方法生成一个列表
ListView.builder(
itemExtent: 50,//确定每一个item的高度,列表在滑动的时候才不会去计算item高度,可以让列表滑动更加高效
itemBuilder: (BuildContext context, int index) {
return Container(
alignment: Alignment.center,
height: 50,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
alignment: Alignment.center,
height: 49,
child: Text('第$index个child'),
),
Container(
height: 1,
color: Colors.grey,
)
],
),
);
},
)
效果如下
12. GridView
用于展示网格布局,少量的数据可以通过以下方式展示,但是大量数据就不合适,原因是这种方式没有懒加载,
gridDelegate设置子控件排列方式,crossAxisCount设置主轴方向(水平方向)子控件数量,
crossAxisSpacing控制交叉轴(竖直方向)子控件间距,mainAxisSpacing设置主轴方向子控件间距
GridView(
scrollDirection: Axis.vertical, //竖直方向滑动
controller: scrollController, //监听GridView滚动
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, crossAxisSpacing: 3, mainAxisSpacing: 2),
children: [
_createGridViewItem(Colors.primaries[0]),
_createGridViewItem(Colors.primaries[1]),
_createGridViewItem(Colors.primaries[2]),
_createGridViewItem(Colors.primaries[3]),
_createGridViewItem(Colors.primaries[4]),
_createGridViewItem(Colors.primaries[5]),
_createGridViewItem(Colors.primaries[6]),
_createGridViewItem(Colors.primaries[7]),
_createGridViewItem(Colors.primaries[0]),
_createGridViewItem(Colors.primaries[1]),
_createGridViewItem(Colors.primaries[2]),
_createGridViewItem(Colors.primaries[3]),
_createGridViewItem(Colors.primaries[4]),
_createGridViewItem(Colors.primaries[5]),
_createGridViewItem(Colors.primaries[6]),
_createGridViewItem(Colors.primaries[7]),
],
)
_createGridViewItem(Color color) {
return Container(
height: 200,
color: color,
);
}
效果如下
展示大量数据的时候,需要通过下面这种方式来展示数据,使用builder方法获取GridView对象
GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
),
itemBuilder: (context, index) {
return Container(
height: 80,
color: Colors.primaries[index % Colors.primaries.length],
);
},
itemCount: 50,
)
效果如下
13. TextField
文本输入框,使用如下
TextField(
obscureText: true,//设置为密文
style: TextStyle(color: Colors.white, fontSize: 15),
decoration: InputDecoration(
hintText: "请输入登录密码",
hintStyle: TextStyle(color: Colors.white, fontSize: 15),
focusedBorder: OutlineInputBorder(//获取到焦点的时候的背景
borderSide: BorderSide(color: Colors.white),
borderRadius:
BorderRadius.all(Radius.circular(100))),
enabledBorder: OutlineInputBorder(//正常情况下的背景
borderSide: BorderSide(color: Colors.white),
borderRadius:
BorderRadius.all(Radius.circular(100)))),
onChanged: (text) {
//监听输入
},
)
效果如下
14. AppBar
页面标题栏,一般和Scaffold结合使用,可以设置标题,后退按钮等,使用如下
Scaffold(
appBar: AppBar(
backgroundColor: Colors.blue,//背景色
toolbarHeight: 45,//标题栏高度
leading: GestureDetector(//后退按钮
child: Icon(Icons.arrow_back),
onTap: () => {Navigator.pop(context)},
),
centerTitle: true,//设置标题居中
title: Text(//标题
'TweenStudy',
style: TextStyle(color: Colors.black87, fontSize: 18),
))
)
效果如下
5.QQ交流群
770892444