Flutter
Flutter是谷歌基于Dart语言开发的一款开源、免费且跨平台的App开发框架,所以建议先学习Dart语言的基本语法,很像JavaScript和Java,学起来很快。这篇文章打算直接总结如何使用Flutter框架,所以比较长,但是讲解的东西比较简短
环境配置
安装JDK
-
前往Java Downloads - Oracle下载所需要的jdk,JDK17版本够用了
-
配置系统环境
- 新建系统变量,第一行输入
JAVA_HOME
,第二行输入安装jdk的目录
-
在系统变量中找到Path双击点开,点击新建,并输入
%JAVA_HOME%\bin
-
一直点确定,然后再cmd中输入
java -version
,一般来说出现的就是版本号,若不行,重启下电脑,若还不行,重新安装吧!
- 新建系统变量,第一行输入
安装Android Studio
-
前往下载 Android Studio ,并安装
-
安装完毕之后,点击左边的第三项,插件,我这里汉化了,汉化教程
-
搜索并安装这两个插件,然后重启Android Studio
安装FlutterSDK
-
前往Flutter官网下载FlutterSDK,Flutter SDK archive - Flutter
-
将下载好的压缩包解压,然后配置环境,双击系统变量的Path,新建并输入刚刚解压到的文件夹到bin目录的路径
-
在cmd中输入
flutter doctor
,等待一下,出现这些前三项和倒数三项不是x一般就能用,如果第三项出现问题,先安装Android Studio
-
常见问题
若第三项出现问题,先安装Android Studio
Android-SDK的系统环境,未配置,在系统变量中新建并第一行输入
ANDROID_HOME
,第二行输入你安装Android Studio时,安装Android-SDK的路径,然后重启电脑,一般就行了如果
flutter doctor
时出现cmdline-tools component is missing
根据以下步骤:
-
打开Android Studio,关闭项目,到开始页面,点最右边的竖着的三个点,选择SDK Manager
-
点第二项SDK Tools,在下面找到Android SDK Command-line Tools并勾选,点击确定
-
点击确定[我这里因为已经最新了,所以下载的东西与你们出问题的不一样]
-
等待这个界面下载,下载完成就点完成
如果还不行,推荐重新安装Android SDK!
-
创建Flutter项目并运行
创建
-
一切准备好之后,会看到Android Studio右上角有个
New Flutter Projec
t的按钮,点这个按钮是为了创建Flutter项目 -
点进去之后,右边点击Flutter,然后下一步
-
这里就是项目的基本信息
- 项目名称:随便你想怎么起怎么起
- 项目路径:看你想放哪
- 项目描述:这一条无所谓
- 项目类型:想做app就选Application
- 所属组织:随你
- 安卓系统的编译语言:一般选第二项
- IOS系统的编译语言:一般选第二项
- 所在平台:一般全选
然后填好之后,点左下角创建
最后,我们需要编写的文件,就在lib里
运行
先点击右上角的手机图标,然后选择Edge,再点击运行,如果没有这种运行,左边的项目找到项目的目录,然后点开lib,再双击.dart文件,再编辑器右键点击小三角运行
flutter的基本要素
-
创建第一个flutter,需要导入一个包
package:flutter/material.dart
-
使用main函数,让他可以运行runApp()方法
-
runApp()需要填写容器,这里先使用Center
-
Center里面需要填写child
-
Text()即是创建一个文本到Center里
-
Text里可以有textDirection为让文字从左到右[ltr]还是从右到左[rtl]
-
Text里的style可以改变文字样式
1
2
3
4
5
6
7
8
9
10
11
12
13
14import 'package:flutter/material.dart'; //demo1-Center void main() { runApp(const Center( child: Text( "hello world!", textDirection: TextDirection.ltr, style: TextStyle( fontSize: 20.0, color: Colors.brown ), ) )); }
MaterialApp and Scaffold
MaterialApp常用的属性
属性名 | 用处 |
---|---|
home | 主页 |
title | 标题 |
color | 颜色 |
theme | 主题 |
routes | 路由 |
MaterialApp-home
填写容器,一般以Scaffold为主,Scaffold是与MaterialApp的另一种组件
1 |
|
MaterialApp-title|color
MaterialApp的标题和颜色,但是一般不用
MaterialApp-theme
整个软件的主题,填写的参数有要求
ThemeData.light()
:白色主题ThemeData.dark()
:黑色主题
1 |
|
MaterialApp-routes
none
Scaffold的常用属性
- appBar - 用于显示顶部部分
- body - 用于显示主要内容
- drawer - 左边抽屉菜单
Scaffold-appBar
如何使用appBar
1 |
|
其实appBar还有一些属性
- backgroundColor - 用于设置背景颜色
- centerTitle - 是否居中文字
1 |
|
Scaffold-body
主体部分,填写容器就行了,这里用了Center
1 |
|
Scaffold-drawer
-
编写一个简单的界面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19import 'package:flutter/material.dart'; //demo4-Drawer void main(){ runApp(MaterialApp( home: Scaffold( appBar: AppBar( title: const Text( "demo4-Drawer", textDirection: TextDirection.ltr ), backgroundColor: Colors.blue, titleTextStyle:const TextStyle( color: Colors.amber ), ), drawer: ), )); }
-
darwer需要填写与之相应的Darwer()
里面常用的两个属性
- backgroundColor - 抽屉菜单的背景颜色
- child - 抽屉菜单的内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29import 'package:flutter/material.dart'; //demo4-Drawer void main(){ runApp(MaterialApp( home: Scaffold( appBar: AppBar( title: const Text( "demo4-Drawer", textDirection: TextDirection.ltr ), backgroundColor: Colors.blue, titleTextStyle:const TextStyle( color: Colors.amber ), ), drawer:const Drawer( backgroundColor: Colors.lightBlue, child:Text( "DrawerTitle", textDirection: TextDirection.ltr, style: TextStyle( color: Colors.greenAccent, fontSize: 30.0, ), ), ), ), )); }
-
总体代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50import 'package:flutter/material.dart'; //demo4-Drawer void main(){ runApp(MaterialApp( home: Scaffold( appBar: AppBar( title: const Text( "demo4-Drawer", textDirection: TextDirection.ltr ), backgroundColor: Colors.blue, titleTextStyle:const TextStyle( color: Colors.amber ), ), body: HomeComp(), drawer:const Drawer( backgroundColor: Colors.lightBlue, child:Text( "DrawerTitle", textDirection: TextDirection.ltr, style: TextStyle( color: Colors.greenAccent, fontSize: 30.0, ), ), ), ), )); } class HomeComp extends StatelessWidget{ // const HomeComp ({Key ? key}) : super(key: key); @override Widget build(BuildContext context) { // TODO: implement build return const Center( child: Text( "Hello world", textDirection: TextDirection.ltr, style: TextStyle( color: Colors.green, ), ), ); } }
分离组件
我们在编写软件的时候,不会把所有的东西功能和界面全部写在一起,那样会让我们看起来很烦躁,因为非常的乱
-
先编写一个普通的界面,现在,代码是报错的,因为body还没有填写东西
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16import 'package:flutter/material.dart'; //demo3-分离组件 void main(){ runApp(MaterialApp( home: Scaffold( appBar: AppBar( title: const Text("demo3-分离组件"), backgroundColor: Colors.blue, titleTextStyle:const TextStyle( color: Colors.amber ), ), body: , ), )); }
-
在main方法下面,编写一个组件类,让这个类继承StatelessWidget,此时会报错,将鼠标放上去,让系统自动帮你把方法写出来
const HomeComp({super.key});
这一行一般都是这样的形式,照写就行1
2
3
4
5
6
7class HomeComp extends StatelessWidget{ const HomeComp({super.key}); @override Widget build(BuildContext context) { } }
-
在继承的方法返回容器即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16class HomeComp extends StatelessWidget{ const HomeComp({super.key}); @override Widget build(BuildContext context) { // TODO: implement build return const Center( child: Text( "Hello world", style: TextStyle( color: Colors.green, ), ), ); } }
-
总体代码就是这样
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34import 'package:flutter/material.dart'; //demo3-分离组件 void main(){ runApp(MaterialApp( home: Scaffold( appBar: AppBar( title: const Text("demo3-分离组件"), backgroundColor: Colors.blue, titleTextStyle:const TextStyle( color: Colors.amber ), ), body: const HomeComp(), ), )); } class HomeComp extends StatelessWidget{ const HomeComp({super.key}); @override Widget build(BuildContext context) { // TODO: implement build return const Center( child: Text( "Hello world", style: TextStyle( color: Colors.green, ), ), ); } }
基础组件
Container容器组件
alignment的参数
参数 | 说明 |
---|---|
Alignment.topCenter |
顶部居中对齐 |
Alignment.topLeft |
顶部左对齐 |
Alignment.topRight |
顶部右对齐 |
Alignment.topRight |
顶部右对齐 |
Alignment.center |
水平垂直居中对齐 |
Alignment.centerLeft |
垂直居中水平居左对齐 |
Alignment.centerRight |
垂直居中水平居右对齐 |
Alignment.bottomCenter |
底部居中对齐 |
Alignment.bottomLeft |
底部居左对齐 |
Alignment.bottomRight |
底部居右对齐 |
decoration的属性
先使用BoxDecoration(),里面的参数为
属性 | 说明 |
---|---|
gradient |
渐变 LinearGradient()为背景线性渐变 RadialGradient()为径向渐变,这两个方法里面需要color参数,可以书数组 |
boxShadow |
阴影 需要定义一个常量数组,数组里面需要BoxShadow(),里面有color[颜色]、offset[位移,参数为Offset(x, y)]、blurRadius[虚化程度] |
border |
边框 需要使用Border.all(),里面有color[颜色]、width[线条大小] |
borderRadius |
盒子圆角 需要使用BorderRadius.circular(),参数就是double类型的数字 |
其他属性
属性 | 说明 |
---|---|
margin |
容器与外部的距离,参数使用EdgeInsets.all(double) 或者EdgeInsets.fromLTRB(double,double,double,double) |
padding |
容器边框与child的距离,同上 |
transform |
让容器可以位移旋转 |
height | width |
容器宽高 |
child |
子元素 |
Text组件
textAlign参数
文字对齐方式
参数 | 说明 |
---|---|
TextAlign.center |
文字居中 |
TextAlign.left |
文字左对齐 |
TextAlign.right |
文字右对齐 |
TextAlign.justfy |
文字两端对齐 |
textDirection参数
文字方向
参数 | 说明 |
---|---|
TextDirection.ltr |
从左至右 |
TextDirection.rtl |
从右至左 |
overflow参数
文字超出屏幕之后的处理方式
参数 | 说明 |
---|---|
TextOverflow.visible |
强制显示 |
TextOverflow.clip |
裁剪隐藏 |
TextOverflow.fade |
渐隐 |
TextOverflow.ellipsis |
省略号 |
textScaler参数
textScaleFactor已弃用,用textScaler代替,用于字体显示倍率
参数 | 说明 |
---|---|
TextScaler.linear(double) |
字体显示倍率,1为正常 |
maxLines参数
显示最大行数
- 填写整数即可
style属性
需要搭配TextStyle()使用,以下是TextStyle()的属性
-
decoration:文字的装饰线
参数 说明 TextDecoration.none
无线条 TextDecoration.lineThrough
删除线 TextDecoration.overline
上划线 TextDecoration.underline
下划线 -
decorationColor:装饰线的颜色
-
decorationStyle:装饰线样式
参数 说明 TextDecorationStyle.dashed
横杆虚线 TextDecorationStyle.dotted
点虚线 TextDecorationStyle.double
双线 TextDecorationStyle.solid
实线 TextDecorationStyle.wavy
波浪线 -
wordSpacing:单词间隙,负值会让单词变得更紧凑
-
letterSpacing:字母间隙,负值,会让字母变得更紧凑
-
fontStyle:文字样式
参数 说明 FontStyle.normal
正常字体 FontStyle.italic
斜体 -
fontSize:文字大小
-
color:文字颜色
-
fontWeight:文字粗细
参数 说明 FontWeight.normal
正常粗细 FontWeight.normal
粗体 FontWeight.wx00
按照整百粗细(100-900)
图片组件
图片组件分为两种
- Image.asset
- Image.network
属性
两种的参数都是一样的,就是引用图片的方式不一样
alignment:图片对齐方式
参数 | 说明 |
---|---|
Alignment.topCenter |
顶部居中对齐 |
Alignment.topLeft |
顶部左对齐 |
Alignment.topRight |
顶部右对齐 |
Alignment.center |
水平垂直居中对齐 |
Alignment.centerLeft |
垂直居中水平居左对齐 |
Alignment.centerRight |
垂直居中水平居右对齐 |
Alignment.bottomCenter |
底部居中对齐 |
Alignment.bottomLeft |
底部居左对齐 |
Alignment.bottomRight |
底部居右对齐 |
fit:图片的填充
参数 | 说明 |
---|---|
BoxFit.fill |
拉伸填满 |
BoxFit.contain |
按原始比例,可能会有空隙 |
BoxFit.cover |
图片填满整个容器,不变形,但是会被剪裁 |
BoxFit.fitWidth |
宽度充满 |
BoxFit.fitHeight |
高度充满 |
BoxFit.scaleDown |
效果和contain差不多,但是不允许显示超过原图片的大小,可小不可大 |
color:图片背景颜色
colorBlendMode:图片混合颜色选项
-
参数比较多,都是比较基本的图片颜色混合
repeat:图片平铺
ImageRepeat.noRepeat
:不重复ImageRepeat.repeat
:横向和总线都重复ImageRepeat.repeatX
:横向重复,纵向不重复ImageRepeat.repeatY
:纵向重复,横向不重复
width 和 height:图片宽高
Image.asset
-
在目录新建储存图片的文件夹
-
在pubspec.yaml文件中声明图片文件[注意格式和空格],在这个地方添加⬇
1
2
3
4
5
6
7
8
9
10
11
12
13flutter: # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in # the material Icons class. uses-material-design: true assets: - "img/head.jpg" #在这个地方添加,认准这个地方,下面这里的这四行注释下面也行 # To add assets to your application, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg
-
在child中使用Image.asset()
1
2
3
4
5
6
7
8
9
10
11
12
13
14// 这只是个组件 class Demo6 extends StatelessWidget{ const Demo6({super.key}); @override Widget build(BuildContext context) { // TODO: implement build return Center( child: Image.asset( "img/head.jpg", ) ); } }
Image.network
这个也同理,就是不用配置什么东西
1 |
|
图标组件
使用Icon()来使用官方给的图标
1 |
|
属性
属性 | 说明 |
---|---|
Icons.图标名 | 使用官方的图标 |
color | 图标颜色 |
size | 图标大小,默认20 |
列表组件
通过使用组件ListView()
创建列表
属性
属性 | 说明 |
---|---|
scrollDirection |
设置列表类型,参数为 Axis.vertical 垂直列表[默认]、Axis.horizontal 水平列表 |
padding |
内边距,与其他组件一样 |
resolve |
是否反向排序,参数:true 和false |
children |
列表的元素,参数为数组 |
最简单的列表
通过使用ListTile()
创建列表项,利用ListTile里的参数title可以将其他元素放进去列表项
1 |
|
列表项
即ListTile()
,有必要说一下里面的参数
属性
属性 | 说明 |
---|---|
leading |
列表项的最左边[前导],一般放图标或者图片 |
trailing |
列表项的最右边[尾部],一般放图标或者图片 |
title |
列表项的标题 |
subtitle |
列表项的副标题 |
style |
列表项的文本样式 |
iconColor |
图标颜色 |
tileColor |
列表项背景颜色 |
titleTextStyle |
标题文本颜色 |
subtitleTextStyle |
副标题文本颜色 |
leadingAndTrailingTextStyle |
前导和尾部的文本样式 |
contentPadding |
内容的内边距 |
titleAlignment |
标题文本的对齐方式 |
isThreeLine |
额外文本行[三行列表项] |
布局
Grid布局
常用属性
属性 | 说明 |
---|---|
scrollDirection |
组件滚动方向,Axis.horizontal 水平,Axis.vertical 垂直 |
reverse |
组件元素是否反向排序 |
controller |
滚动监听 |
primary |
内容不足时是否可以滑动 |
shrinkWrap |
内容适配,默认false |
padding |
内边距 |
crossAxisCount |
一行或一列的元素数量 |
mainAxisSpacing |
主轴之间间距 |
crossAxisSpacing |
横轴之间间距 |
childAspectRatio |
元素的宽高比例 |
children |
组件元素 |
使用GridView.count创建网络布局
使用这个方法创建网络布局必须要填写一个值crossAxisCount,也就是一行限制多少个元素
1 |
|
使用GridView.count创建网络布局
使用此方法创建网络布局必须要填写一个值maxCrossAxisExtent,也就是固定横轴的最大长度
1 |
|
线性布局
属性
属性 | 说明 | 参数 |
---|---|---|
mainAxisAlignment |
主轴的排序方式 | MainAxisAlignment.center|start|end|spaceEvenly|spaceAround|spaceBetween |
crossAxisAlignment |
次轴的排序方式 | CrossAxisAlignment.center|start|end|stretch |
children |
组件元素 | 略 |
Row布局
1 |
|
Colum布局
1 |
|
Flex布局
使用flex布局一般需要搭配Expanded组件一起使用
Flex属性和Expanded属性
Flex属性 | 说明 | Expanded属性 | 说明 |
---|---|---|---|
direction |
flex布局方向,Axis.horizontal 水平,Axis.vertical 垂直 |
flex |
该组件占总flex的多少,总flex=处于该组件内的Expanded组件的flex值之和 |
children |
flex的元素 | child |
Expanded的元素 |
水平布局
1 |
|
垂直布局
1 |
|
水平与垂直布局的混合使用
1 |
|
Wrap布局
属性
属性 | 说明 |
---|---|
direction |
组件主轴的方向,参数为Axis.horizontal 水平,Axis.vertical 垂直 |
spacing |
主轴元素之间的间距 |
runSpacing |
新行元素之间的间距 |
alignment |
主轴元素的对齐方式 |
runAlignment |
新行元素的对齐方式 |
verticalDirection |
定义children 摆放顺序,默认是VerticalDirection.down ,参数还有VerticalDirection.up |
children |
组件元素 |
1 |
|
Stack布局
一般来说,会使用其他组件来与Stack布局一起使用
属性
属性 | 说明 |
---|---|
alignment |
对齐方式,参数Alignment.* |
textDirection |
文本方向,参数TextDirection.* |
children |
子组件 |
使用stack布局,会让它的子组件都堆叠在一起,如下
1 |
|
结合Align使用
Align组件一般来说,只用通过alignment属性来控制组件的位置
Align属性
属性 | 说明 |
---|---|
alignment |
对齐方式,参数Alignment.* |
child |
子组件 |
如果位置不够,Stack布局里的组件依然会堆叠
1 |
|
创建一个底部导航栏
通过使用Align的对齐方式定位,将导航栏固定在底部
1 |
|
结合Positioned使用
使用Positioned组件,可以让组件位于哪个地方,就位于哪个地方,相当于绝对定位
Positioned属性
属性 | 说明 |
---|---|
left | top | right | bottom |
控制组件的定位,相当于坐标,控制组件与窗口 左边|顶部|右边|底部 边缘的距离 |
width | height |
组件的 宽度 | 高度 ,参数不能使用double.infinity ,且一定为固定值 |
child |
子组件 |
1 |
|
创建一个悬浮按钮
可以使用Positioned组件的绝对定位的特点,让按钮随时固定在窗口的一个位置
1 |
|
AspectRatio布局
此布局的理解比较的复杂,布局的宽高由aspectRatio
参数去决定,即宽高比,它会在布局限制条件允许的范围内尽可能的扩展,按照固定比率去尽量占满区域
属性
属性 | 说明 |
---|---|
aspectRatio |
宽高比,可能会忽略这个数值 |
child |
子组件 |
1 |
|
按钮
flutter内置了一些比较好的按钮,虽然样式设置起来有些许麻烦
属性
属性 | 说明 |
---|---|
onPressed |
点击事件 [必填] |
style |
按钮样式,需要使用ButtonStyle来调整样式 |
child |
子组件 [必填] |
Flutter内置的按钮
按钮 | 说明 |
---|---|
ElevatedButton |
最普通的按钮,有背景颜色,有少许阴影,点击有颜色变化,无边框 |
TextButton |
文本按钮,无背景颜色,无阴影,点击有颜色变化,无边框 |
OutlinedButton |
边框按钮,无背景颜色,无阴影,点击有颜色变化,有边框 |
IconButton |
图标按钮,无背景颜色,无阴影,点击有颜色变化,无边框 |
ElevatedButton.icon |
带图标的普通按钮,样式如上,icon 必填 |
TextButton.icon |
带图标的文本按钮,样式如上,icon 必填 |
OutlinedButton.icon |
带图标的边框按钮,样式如上,icon 必填 |
1 |
|
修改按钮宽高
通过使用SizeBox来调整按钮,SizeBox中的width和height调整盒子的大小,进而调整按钮宽高
1 |
|
修改按钮样式
想要修改按钮的样式,需要通过ButtonStyle
去调整,而里面的参数比较的复杂
ButtonStyle的属性
属性 | 说明 | 参数 |
---|---|---|
backgroundColor |
背景颜色 | MaterialStateProperty.all(Colors.*) |
foregroundColor |
组件内容颜色 | MaterialStateProperty.all(Colors.*) |
shadowColor |
阴影颜色 | MaterialStateProperty.all(Colors.*) |
shape |
按钮形状 | MaterialStateProperty.all(*) 这个比较特殊 |
padding |
内边距 | MaterialStateProperty.all(const EdgeInsets.*) |
elevation |
阴影范围 | MaterialStateProperty.all(num) |
side |
边框 | MaterialStateProperty.all(const BorderSide(width: num,color: Colors.*)) |
alignment |
组件对齐方式 | Alignment.* |
设置按钮圆角
shape
这个属性的参数比较多变
若要设置圆角,参数为:MaterialStateProperty.all(RoundedRectangleBorder(borderRadius: BorderRadius.circular(num)))
1 |
|
若要设置圆形,参数为:MaterialStateProperty.*all*(const CircleBorder(side: BorderSide(color: Color.fromARGB(0, 0, 0, 0))))
1 |
|
设置按钮边框
1 |
|
事件
PointerEvents
最简单的事件监听,通过Listener()去监听子组件的行为 tips:以下的e
和event
都是event
,只是个变量而已
属性
属性 | 说明 |
---|---|
onPointerDown |
按下组件触发,参数:(e)=>{} |
onPointerMove |
按下并在组件内移动触发,参数:(e)=>{} |
onPointerHover |
移动到组件触发,参数:(e)=>{} |
onPointerUp |
按下后松开才触发,参数:(e)=>{} |
1 |
|
GestureDetector
GestureDetector封装了非常多的触发类型,所以直接使用GestureDetector()组件可以直接使用这些监听类型
Tap单击 属性
属性 | 说明 |
---|---|
onTapDown |
按下触发,参数(e)=>{} |
onTapUp |
抬起触发,参数(e)=>{} |
onTap |
按下后抬起[与抬起触发不同,这个两个条件都得有],参数()=>{} |
onTapCancel |
按下后取消[可移出组件触发],参数()=>{} |
1 |
|
DoubleTap双击 属性
属性 | 说明 |
---|---|
onDoubleTap |
双击触发[必须按下抬起各两次],参数()=>{} |
onDoubleTapCancel |
双击时取消[可移出组件触发],参数()=>{} |
onDoubleTapDown |
双击时第二次点击触发,参数(e)=>{} |
1 |
|
LongPress长按 属性
属性 | 说明 |
---|---|
onLongPress |
长按触发,参数()=>{} |
onLongPressStart |
长按开始时[长按触发时]触发,参数(e)=>{} |
onLongPressEnd |
长按结束时[长按抬起]触发,参数(e)=>{} |
onLongPressDown |
长按按下时[单点也可]触发,参数(e)=>{} |
onLongPressUp |
长按抬起时触发,参数()=>{} |
onLongPressMoveUpdate |
长按后移动更新时触发,参数(e)=>{} |
onLongPressCancel |
长按取消时[也可移出组件]触发,参数()=>{} |
1 |
|
Drag拖动 属性
属性 | 说明 |
---|---|
onVerticalDragStart |
开始垂直移动,参数(e)=>{} |
onVerticalDragUpdate |
持续垂直移动,参数(e)=>{} |
onVerticalDragEnd |
结束垂直移动,参数(e)=>{} |
onHorizontalDragStart |
开始水平移动,参数(e)=>{} |
onHorizontalDragUpdate |
持续水平移动,参数(e)=>{} |
onHorizontalDragEnd |
结束水平移动,参数(e)=>{} |
1 |
|
事件隔离
因为通过Listener()
可以拿到子组件Widget的信息,使用AbsorbPointer()
组件和IgnorePointer()
组件可以使得这些信息不允许打印出来,而两者的用法一样,只是属性不一样
组件 | 属性 | 说明 |
---|---|---|
AbsorbPointer() |
absorbing |
true :不会打印,false :会打印 |
IgnorePointer() |
ignoring |
true :不会打印,false :会打印 |
1 |
|
输入框
使用TextField()可以创建出一个输入框,Flutter为输入框提供了非常多的属性,供开发人员自定义输入框
属性
属性 | 说明 | 参数 |
---|---|---|
controller |
控制器,文本的交互需要它 | TextEditingController() |
decoration |
输入框的装饰选项 | InputDecoration() |
textInputAction |
键盘左下角的按钮类型 | TextInputAction.* |
textCapitalization |
键盘默认大小写 | TextCapitalization.* |
autofocus |
是否自动聚焦到输入框 | bool ,默认false |
obscureText |
是否将文本变成点,即隐藏文本 | bool ,默认false |
autocorrect |
是否自动更正 | bool ,默认true |
maxLength |
最大输入字符数 | int |
cursorWidth |
光标宽度 | int ,默认2.0 |
cursorColor |
光标颜色 | color |
cursorRadius |
光标圆角 | Radius.circular(*) |
cursorHeight |
光标高度 | int |
cursorOpacityAnimates |
光标是否开启闪烁动画 | bool |
inputFormatters |
限制输入文字/数字 | [] |
onTap |
点击输入框事件 | (){事件} |
onTapOutside |
点击输入框外事件 | (){事件} |
onChanged |
输入框变更事件 | (e){事件} |
onEditingComplete |
输入框完成输入事件 | (){事件} |
onSubmitted |
输入框提交事件 | (){事件} |
enabled |
是否启用输入框 | bool |
Controller
一般来说,使用控制器直接使用该对象就好了
1 |
|
方法 | 用途 |
---|---|
clear() |
清除使用该控制器的输入框内容 |
text() |
设置使用该控制器的输入框内容 |
但是,使用TextEditingController.fromValue(*)
可以自定义输入框的一些属性,*为TextPosition()
而TextPosition()
有以下两个属性
属性 | 说明 |
---|---|
text |
设置使用该控制器的输入框内容,参数为String |
selection |
设置输入框内边距,参数请继续看↓ |
selection的参数为TextSelection.fromPosition(*),而里面又需要TextPosition(),里面只有两个属性
属性 | 说明 |
---|---|
affinity |
一般填TextAffinity.downstream |
offset |
输入框的光标距离最左边的距离,参数为int |
decoration
可以让输入框自定义成想要的样式,参数为InputDecoration()
,以下为InputDecoration()
的属性
属性 | 说明 | 参数 |
---|---|---|
icon |
最左侧的图标,图标下无横线 | Icon() |
labelText |
悬浮文字 | String |
labelStyle |
悬浮文字样式 | TextStyle() |
helperText |
帮助文字 | String |
helperStyle |
帮助文字样式 | TextStyle() |
hintText |
提示文字 | String |
hintStyle |
提示文字样式 | TextStyle() |
errorText |
错误文字 | String |
errorStyle |
错误文字样式 | TextStyle() |
contentPadding |
内容内边距 | EdgeInsets.all(*) |
prefixIcon |
最左边的图标,包含在横线内 | Icon() |
prefix |
最左边自定义Widget | Widget |
prefixText |
最左边文字,只有当输入框聚焦才显示 | String |
prefixStyle |
最左边文字样式 | TextStyle() |
suffixIcon |
最右边的图标,包含在横线内 | Icon() |
suffix |
最右边自定义Widget | Widget |
suffixText |
最右边文字,只有当输入框聚焦才显示 | String |
suffixStyle |
最右边文字样式 | TextStyle() |
counter |
自定义计数器Widget | Widget |
counterText |
计数器文本 | String |
counterStyle |
计数器文本样式 | TextStyle() |
filled |
是否填充颜色 | bool ,默认false |
fillColor |
填充颜色 | Color |
border |
输入框整体边框,看下面教程 | InputBorder.none 或OutlineInputBorder() |
enabledBorder |
输入框可用时边框,看下面教程 | InputBorder.none 或不填OutlineInputBorder() |
disabledBorder |
输入框禁用时边框,看下面教程 | InputBorder.none 或不填OutlineInputBorder() |
errorBorder |
输入框错误时边框,看下面教程 | InputBorder.none 或不填OutlineInputBorder() |
focusedBorder |
输入框聚焦时边框,看下面教程 | InputBorder.none 或不填OutlineInputBorder() |
border参数详解
参数为InputBorder.none
时,输入框不会显示下横线
参数为OutlineInputBorder()
,需要通过属性borderRadius
设置圆角,borderRadius
的参数为BorderRadius.all(Radius.circular(*))
参数为UnderlineInputBorder()
,输入框只会在下面显示一条线
最后两个参数的属性borderSide
可以设置线条颜色,参数为BorderSide(color: Colors.*)
放demo
1 |
|
Dialog
简单的Dialog
通过AlertDialog()
,我们可以很快的创建出一个确认窗口
但是在使用AlertDialog()
时,需要使用showDialog()
,才能显示
-
新建一个方法,并携带
context
变量,定义一个result
变量和打印result1
2
3
4void alertDialog(context){ var result; print("返回值:$result"); }
-
使用
showDialog()
,以及将方法变为异步方法,这里来看一下showDialog的属性属性 说明 context
不理,就填 context
builder
返回的Dialog类型 barrierDismissible
是否点击蒙版关闭Dialog useSafeArea
是否预留安全区域 1
2
3
4
5
6
7
8
9void alertDialog(context)async{ var result = await showDialog( barrierDismissible: true, useSafeArea: true, context: context, builder: builder ); print("返回值:$result"); }
-
让
builder
返回AlertDialog()
1
2
3
4
5
6
7
8
9void alertDialog(context)async{ var result = await showDialog( barrierDismissible: true, useSafeArea: true, context: context, builder: (context)=>AlertDialog() ); print("返回值:$result"); }
-
编辑
AlertDialog()
的title,content,action
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28void alertDialog(context)async{ var result = await showDialog( barrierDismissible: true, useSafeArea: true, context: context, builder: (context)=>AlertDialog( title: const Text("标题"), content: const Text("内容"), actions: [ TextButton( onPressed: (){ print("确定"); Navigator.pop(context,"confirm"); }, child: const Text("确定") ), TextButton( onPressed: (){ print("取消"); Navigator.pop(context,"cancel"); }, child: const Text("取消") ), ], ) ); print("返回值:$result"); }
-
最后创建一个按钮使用这个方法
1
2
3onPressed: (){ selectDialog(context); },
-
效果
多选项Dialog
使用showDialog()
可以创建出多选项Dialog
与普通的Dialog创建方法相同,只是里面的children
用了SimpleDialogOption()
,相当于按钮
1 |
|
自定义Dialog
通过继承Dialog这个类,可以创建出高自定义的Dialog
1 |
|
路由
在使用MaterialApp()
时,可以使用路由,来实现页面之间的切换,而这里面的路由实现方式是通过将页面加入栈与移出栈的思想
最简单的页面切换
通过使用Navigator.push
来将新页面加入栈
1 |
|
传参数入页面
-
新建一个页面,和一个实体类,并在页面入口方法中添加属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36class Person{ String name; int age; Person(this.name,this.age); @override String toString() { return "{name:$name,age:$age}"; } } class AttrPage extends StatelessWidget{ final String text; final Person person; const AttrPage({ super.key, required this.text, required this.person }); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("AttrPage"), backgroundColor: Colors.cyanAccent, ), body: Center( child: Column( children: [ Text("text:$text"), Text("person:${person.toString()}") ], ), ), ); } }
-
在主页面的按钮添加点击事件,用
Navigator.push
将页面加入栈,并在返回页面的对象里写入属性的参数1
2
3
4
5
6
7onPressed: (){ Navigator.push(context, MaterialPageRoute( builder: (context){ return AttrPage(text: "TestText", person: Person("jack",10)); } )); }
-
效果
退出页面返回值
通过Navigator.push
加入的页面,左上角都会有一个返回的按钮,但是这个按钮返回值为null
,所以需要Navigator.pop
在退出页面时,返回一个值
-
新建一个页面,并添加一个按钮,添加点击事件,使用
Navigator.pop
返回页面1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20class BackAttrPage extends StatelessWidget{ const BackAttrPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("BackAttrPage"), backgroundColor: Colors.green, ), body: Center( child: ElevatedButton( onPressed: (){ Navigator.pop(context,"hello world"); }, child: const Text("返回"), ), ), ); } }
-
在主页添加按钮进入这个界面,但是这里的方法需要在
()
后加async
表示这个方法为异步方法,并在Navigator.push
前加await
表示等待这个方法运行完,在进行下一步,然后用一个变量给Navigator.push
赋值,为了接收页面的返回值1
2
3
4
5
6
7
8onPressed: ()async{ var result = await Navigator.push(context, MaterialPageRoute( builder: (context){ return const BackAttrPage(); } )); print("返回值:$result"); }
-
点击默认的返回按钮和点击自己的返回按钮的效果
使用MaterialApp()的routes
MaterialApp()
有个routes属性,这个属性是为了方便添加页面,形式为"路由名":(context) => const 页面()
1 |
|
然后我们就可以通过Navigator.pushNamed(context,"路由名")
去进入页面
传参问题
因为通过routes命名路由进入页面的方式无法直接传输参数,但是Navigator.pushNamed里的arguments可以将参数传输到页面里
-
在按钮处添加事件,并在Navigator.pushNamed里添加arguments属性,将要传的参数写在
{}
里,以,
隔开1
2
3
4
5
6
7
8
9
10onPressed: (){ Navigator.pushNamed( context, "route1", arguments: [ "TestText", 123 ] ); }
-
在返回页面的方法里,通过
ModalRoute.of(context)?.settings.arguments
获取传入的数据,如果只传一个参数,那就直接将变量转成String类型即可,但是如果是其他的形式,例如:我传的是一个List,我需要将变量转成字符串,然后再将它转换成List才能拿出来使用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23class Router1Page extends StatelessWidget{ const Router1Page({super.key}); @override Widget build(BuildContext context) { var args = ModalRoute.of(context)?.settings.arguments; List<String> argList = args.toString().substring(1,args.toString().length-1).split(","); return Scaffold( appBar: AppBar( title: const Text("Page1111"), backgroundColor: Colors.cyan, ), body: Center( child: Column( children: [ Text("text:${argList[0]}"), Text("num:${argList[1]}"), ], ), ), ); } }
-
效果
返回值[与普通的一样]
返回值的方法与普通进入页面的方法一样,没有问题
效果:
route的钩子函数
顾名思义就是访问routes时,执行的函数
函数 | 说明 |
---|---|
onGenerateRoute |
当页面没有Route命名但依旧使用Navigator.pushNamed 会执行[需要单独出来讲] |
onUnknownRoute |
当页面没有Route命名但依旧使用Navigator.pushNamed 会执行,防止访问未知页面用 |
navigatorObservers |
监控页面的入栈和出栈 |
onGenerateRoute与onUnknownRoute
onGenerateRoute
这个函数的使用方法是为了:
- 使用路由命名方式传参的问题
- 防止没有权限的用户访问页面
- 访问空页面[不适用]
在使用这个函数的时候,需要一个参数RouteSettings settings
,通过settings.name
可以获取当前使用Navigator.pushNamed
时,访问的路由名
1 |
|
可以通过给访问特定的路由名加个权限,或者传参
1 |
|
1 |
|
onUnknownRoute
这个函数的执行时机在于当页面没有Route命名但依旧使用Navigator.pushNamed
且onGenerateRoute
没有返回页面
也就是当页面不存在的时候,这个函数会执行,且可以自定义不存在页面
1 |
|
1 |
|
navigatorObservers
这个钩子函数,会全程监控页面的入栈和出栈,只要栈堆有变化,就会执行
1 |
|
demo
Turing158/flutter-demo (github.com)
Turing_ICE/flutter-demo (gitee.com)
待更新 …