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; }
|
这个很重要教程](https://docs.flutter.dev/tools/devtools/android-studio))
地址) 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
来设置边距和边框,可以在Text
的stytle
中来设置美化样式,对于Column
中可以设置对称轴的对齐方式
注意,为了美化日期的输出,我们要用到intl: ^0.19.0
1
| DateFormat().format(e.date)
|
创建一个临时对象,调用其formate
方法,接收一个DateTime类型,返回一个String