flutter的tips

使用

flutter create 项目名称

在一个文件夹的命令行可以创建一个项目,项目名称可以用下划线分割,约定是全部是小写字母

同样地,在命令行中可以通过

flutter run

来运行一个项目

extends 和 implements 区别

extends是继承,具有父类的特性,implements是实现接口,需要重写所有的方法

可选位置参数,可选命名参数

1
2
[这个括起来的] 是可选位置,传参数的时候直接传,和位置有关
{这个括起来的} 是可选命名参数,传参数的时候 用 参数 名称:参数

按钮里面的 onPressed

这个接受的参数是一个函数,如果没有可以写成

1
2
3
ElevatedButton(onPressed: null, child: Text("Answer1")),
或者
ElevatedButton(onPressed: () {}, child: Text("Answer1")),

重点来了

我们可以定义一个函数,并将它传给onPressed,但是有个点要注意

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
answer() => print("按了一下");

void outpout(){
print("this is a void function");
}

ElevatedButton(onPressed: answer, child: Text("Answer1")), 这个是正确的
ElevatedButton(onPressed: outpout(), child: Text("Answer2")), 这个是错误的
ElevatedButton(onPressed: output, child: Text("Answer3")), 这个也是对的,传的是一个指针

我们也可以写一个匿名函数
ElevatedButton(onPressed: () => print("这是一个箭头函数"), child:Text("Answer2")),
ElevatedButton(onPressed: () {
print("这是一个匿名函数");
}, child: Text("Answer3")),

展示一个错误的案例

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
import 'package:flutter/material.dart';

main() => runApp(MyApp());

class MyApp extends StatelessWidget {
MyApp({super.key});

var question = [
"first question",
"second question"
];

var questionIndex = 0;

void questionFunction(){
questionIndex +=1;
print("qustion${questionIndex}is chosen");
}

@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("My first app"),
),
body: Column(
children: [
Text(question[questionIndex]),
ElevatedButton(onPressed: questionFunction, child: Text("Answer1")),
],
),
),
);
}
}

无法改变状态

statefulWidegt

  • state 是一个泛型,这个是连接上下两部分的桥梁
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
import 'package:flutter/material.dart';

main() => runApp(MyApp());

class MyApp extends StatefulWidget{
@override
State<StatefulWidget> createState() {
return MyAppState();
}

}

class MyAppState extends State<MyApp> {
var question = [
"first question",
"second question"
];

var questionIndex = 0;

void questionFunction(){
questionIndex =(1+questionIndex)%1;
print("qustion${questionIndex}is chosen");
}

@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("My first app"),
),
body: Column(
children: [
Text(question[questionIndex]),
ElevatedButton(onPressed: questionFunction, child: Text("Answer1")),
],
),
),
);
}
}

代码写成上面这样点击button状态也不会改变,想想也对如果点击任意一个 button就要重新渲染的话,app的性能会爆炸

我们要修改的就是,使用setState函数可以修改状态

1
2
3
4
5
6
7
void questionFunction(){
setState(() {
questionIndex =(1+questionIndex);
});

print("qustion${questionIndex}is chosen");
}

setSate是一个强制Flutter重新渲染用户界面的函数,但不是整个应用程序的用户界面,是指在调用set的地方再次调用这个小部件的build

私有类和私有变量

在类名和变量名前面加下划线变成私有

设置Text中居中为什么不起作用

1
2
3
4
5
6
Text(question,
style: TextStyle(
fontSize: 50,
),
textAlign: TextAlign.center,
);

因为默认情况下文本小部件仅分配文本所需要的空间

1
2
3
4
5
6
7
8
9
10
Container(
width: double.infinity,
child: Text(
question,
style: TextStyle(
fontSize: 50,
),
textAlign: TextAlign.center,
),
);

我们要加一个容器,并设定一个宽度

Container

核心是child

再往外是 padding

再往外是Border

最外层才是Margin

如果设置Container的颜色,Margin是不会有颜色的

TextAign

这是一个枚举

EdgeInsets.all(10)

这是一个构造函数

Function

1
2
3
4
5
6
7
8
9
10
11
12
class Answer extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
child: ElevatedButton(
onPressed: (){},
child: Text("Answer1")
),
);
}
}

对于onPressed来说,需要一个函数指针,所以我们如果想自定义一个button,就需要定义一个函数指针

typedef VoidCallback = void Function();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Answer extends StatelessWidget {

final VoidCallback answerpoint;

Answer(this.answerpoint);

@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
child: ElevatedButton(
onPressed: answerpoint,
child: Text("Answer1")
),
);
}
}

将VoidCallback改成Funtion报错的话还是用VoidCallback吧

因为

1
final VoidCallback? onPressed;

这个定义就是这个类型

拓展

如果我们要把一个不是void Funtion()的函数传给 onPressed 该如何处理

1
2
3
4
5
6
void _questionFunction(int score)
传给
final Function questionFunction;
由于我们需要的是 void Funtion()
转换
Answer(() => questionFunction(answer['score']), answer["text"]);

一个细节,这里的questionFunction 不再是voidcallback 类型

设置一个map

1
2
3
4
5
6
7
8
9
10
11
12
var question2 = [
{"questionText":"what\'s your favourate color",
"answer":['black','Red',"Green"]
},
{"questionText":"what\'s your favourate animal",
"answer":['black','Red',"Green"]
},
{
"questionText":"what\'s your favourate animal",
"answer":["max","small","middle"],
}
];

这个显示的是List<Map<String, Object>>类型

1
2
question2[questionIndex]["questionText"]  这么用可能会报错
改成 question2[questionIndex]["questionText"] as String

展开运算符

展开运算符 ... 被用于展开一个可迭代对象(例如数组)的元素,将它们作为独立的参数传递给方法或构造函数。

1
2
3
4
5
6
7
8
9
10
11
12
var question2 = [
{"questionText":"what\'s your favourate color",
"answer":['black','Red',"Green"]
},
{"questionText":"what\'s your favourate animal",
"answer":['black','Red',"Green"]
},
{
"questionText":"what\'s your favourate animal",
"answer":["max","small","middle"],
}
];
1
2
3
4
5
6
children: [
Question(question2[questionIndex]["questionText"] as String),
...(question2[questionIndex]['answer'] as List<String>).map((answer){
return Answer(_questionFunction,answer);
}).toList()
],

分析一下上面这个代码,首先我们需要得到answer一个列表(由于可能识别不到,我们需要加上as List<String> 然后用 map 函数来返回一个迭代器,但是children 中需要的是单个的widget,我们需要把迭代器对象转换成一个List ,然后用... 展开List ,实际作用就是把列表中的值取出来并把他们作为单独的值添加到

类中静态属性

类似地,如果想不创建一个对象来使用这个类的属性,就要设置为static const

三目运算符防止越界

1
2
3
4
5
6
7
8
body:questionIndex<question2.length? Column(
children: [
Question(question2[questionIndex]["questionText"] as String),
...(question2[questionIndex]['answer'] as List<String>).map((answer){
return Answer(_questionFunction,answer);
}).toList()
],
):Center(child: Text("you did it"),),

私有成员变量初始化

1
Quize({required this.question2,required this.questionIndex,required this._questionFunction});

由于_questionFunction 是私有成员,不可以这样初始化

要么去掉下划线

或者参考

1
2
3
4
class LoadingDialog{
final _valueColor;
LoadingDialog(this._valueColor):this._valueColor = valueColor;
}

要学会怎么用Dart Devtools

这个很重要教程](https://docs.flutter.dev/tools/devtools/android-studio))

Widget catalog

地址) widge catalog

查看各个组件的用法

右上角查看组件) api

card 组件

1
2
3
Card(
child: Text("List of tx"),
)

默认情况下Card 的大小就是里面Text 的大小

不需要背诵这些组件的组合,如果达不到想要的效果,再去试着包装一个Container

DateTime

这是一个内置的类型,日期类

定义一个普通类来实现数据类型

1
2
3
4
5
6
7
8
9
10
11
12
class Transaction {
final String id;
final String title;
final double amout;
final DateTime date;

Transaction(
{required this.id,
required this.title,
required this.amout,
required this.date});
}
1
final List<Transaction> transaction = [];

注意的是,这个类没有继承自statelesswidget或者statefulwidget

初始化

1
2
3
4
final List<Transaction> transaction = [
Transaction(id: 't12', title: 'new shose', amout: 67.55, date: DateTime.now()),
Transaction(id: 't15', title: "new clothes", amout: 88.55, date: DateTime.now())
];

如何转化成组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Column(
children: [
...transaction.map((e) => Card(
child:Row(
children: [
Container(child: Text(e.amout.toString()),),
Column(
children: [
Text(e.title),
Text(e.date.toString()),
],
)
],
),
)).toList()
],
)

可以采用map转换成一个迭代器,然后再转换成一个List

我们要怎么去美化这个界面

首先我们要了解可以通过container来设置边距和边框,可以在Textstytle中来设置美化样式,对于Column 中可以设置对称轴的对齐方式

注意,为了美化日期的输出,我们要用到intl: ^0.19.0

1
DateFormat().format(e.date)

创建一个临时对象,调用其formate方法,接收一个DateTime类型,返回一个String