开篇
-
什么是Flutter
Flutter 是由 Google 开源的 UI(用户界面)软件开发工具包(SDK)。它的核心使命是:让开发者能够使用单一的代码库,快速构建出运行在移动端(iOS、Android)、Web、桌面端(Windows、macOS、Linux)甚至嵌入式设备上的高质量应用程序。
Flutter 使用 Dart 作为其编程语言。
Dart
变量
在 Dart 语言中,变量声明有着非常灵活且严谨的语法。Dart 支持静态类型检查,但同时也通过类型推导让代码编写变得更加简洁。
以下是 Dart 中声明变量的几种主要方式和核心关键字:
1. 明确指定类型
在变量名前直接指定数据类型,这在编译时会进行严格的类型检查:
String name = "张三";
int age = 25;
double height = 1.75;
bool isStudent = false;
List<String> hobbies = ["编程", "阅读"];2. 使用 var(类型推导)
如果你不希望显式写出类型,可以使用 var。Dart 编译器会根据你第一次赋值的值自动推导出变量的类型。
- 特点:一旦类型被推导确定,就不能再赋予其他类型的值。
var name = "李四"; // 被推导为 String
// name = 123; // 错误!无法将 int 赋值给 String 类型的变量3. 使用 dynamic(动态类型)
dynamic 关键字表示该变量可以持有任何类型的值,并且在运行期会进行类型检查。
- 特点:它的类型可以在运行过程中改变,常用于处理类型不确定的数据(如 JSON 解析)。
dynamic flexibleVar = "Hello";
flexibleVar = 42; // 合法,类型变为了 int
flexibleVar = true; // 合法,类型变为了 bool⚠️
var与dynamic的区别:
var是编译时类型推导(确定后不可变)。dynamic是运行时动态类型(随时可变,且会关闭编译时的类型安全检查)。
常量
如果声明的变量在初始化后不允许被修改,应使用 final 或 const。
final(运行时常量):只能被赋值一次。它的值可以是在运行时才能确定的动态结果(例如当前时间)。
// 运行时常量
final time = DateTime.now(); // 运行时的动态赋值
print(time);const(编译时常量):它是一个绝对的编译时常量。这意味着它的值必须在编译时就能计算出来。
const pie = 3.1415926;
print(pie); // π
const length = 2 * pie * 10; // 圆的周长
print(length);数据类型
String
在 Dart 中,字符串类型使用 String 定义,也可以使用类型推断。
-
String定义String title = 'hello world'; print(title); -
类型推断
var message = 'hello nancy'; print(message); -
字符串模版“
DateTime time = DateTime.now(); String str = '当前时间是:${time}。'; print(str);
数值类型
在 Dart 中,有三种数值类型:int、double、num。
区别:
int整型数字、double小数、double可整型可小数。
// int、double、num
int intVal = 10;
double dVal = 10.0;
num nVal = 1.0;
print(intVal);
print(dVal);
print(nVal);注意:
-
double和int不能直接赋值。 -
num不能直接给double赋值。 -
double可直接给num赋值。// intVal = doubleVal.toInt(); // print(intVal); // 2 // doubleVal = intVal.toDouble(); // print(doubleVal); // 2.0 numVal = intVal; print(numVal); // 1 numVal = doubleVal; print(numVal); // 2.0
bool
在 Dart 中,布尔类型为bool。
// bool
bool isFinished = true;
print("今天任务为${isFinished}");
isFinished = false;
print("今天任务为${isFinished}");数组
-
在尾部添加-
add(内容)。 -
在尾部添加一个列表-
addAll(列表) -
删除满足内容的第一个-
remove(内容)。 -
删除最后一个-
removeLast()。 -
删除索引范围内数据-
removeRange(start, end)。List arr = ['zs']; print(arr); // [zs] arr.add('ls'); print(arr); // [zs, ls] arr.addAll(['ww', 'zl']); print(arr); // [zs, ls, ww, zl] arr.remove('ls'); print(arr); // [zs, ww, zl] arr.removeLast(); print(arr); // [zs, ww] arr.removeRange(0, 1); print(arr); // [ww] -
循环-
forEach((item) {})。 -
是否都满足条件-
every((item) {return 布尔值})。 -
筛选出满足条件的数据-
where((item) {return 布尔值})。 -
列表的长度(属性)-
length。 -
最后一个元素(属性)-
last。 -
第一个元素(属性)-
first。 -
是否为空(属性)-
isEmpty。List arr = ['zs', 'zls', 'ww', 'zl']; print(arr); // [zs, ls, ww, zl] // forEach 方法 arr.forEach((item) { print(item); }); // zs ls ww zl // every 方法 bool isRes = false; isRes = arr.every((item) { return item.toString().startsWith('z'); }); print(isRes); // false // where 方法 print(arr.where((item) { return item.toString().startsWith('z'); }).toList()); // [zs, zls, zl] print(arr.length); // 4 print(arr.last); // zl print(arr.last); // zl print(arr.first); // zs print(arr.isEmpty); // false
对象
在 Dart 中,Map(映射) 是一种无序的键值对(Key-Value pairs)集合。它类似于其他编程语言中的字典(Dictionary)、哈希表(Hash Table)或关联数组。
- 循环-
forEach。 - 在添加一个字典-
addAll。 - 是否包含某个key-
containsKey。 - 删除某个key-
remove。 - 清空-
clear。
// Map 定义
Map myMap = {"name": "zs", "age": 18, "sex": "男"};
print(myMap); // {name: zs, age: 18, sex: 男}
print(myMap["name"]); // zs
// Map操作
myMap["age"] = 20;
print(myMap); // {name: zs, age: 20, sex: 男}
// ForEach
myMap.forEach((key, value) {
print("${key}: ${value}"); // name: zs, age: 20, sex: 男
});
// addAll
myMap.addAll({"height": "180cm", "weight": "70kg"});
print(myMap); // {name: zs, age: 20, sex: 男, height: 180cm, weight: 70kg}
// containsKey
print(myMap.containsKey("name")); // true
// remove
myMap.remove("weight");
print(myMap); // {name: zs, age: 20, sex: 男, height: 180cm}
// clear
myMap.clear();
print(myMap); // {}dynamic
Dart 语言中,dynamic用来声明动态类型。
允许变量运行时自由改变类型,同时绕过编译时的静态检查。
dynamic msg = 10;
msg = "hello world";
msg = "pink";
print(msg);空安全机制
在 Dart 语言中,通过编译静态检查将运行时空指针提前暴露。
常用安全操作符
| 操作符 | 符号 | 作用 |
|---|---|---|
| 可空类型 | ? | 声明可空变量 |
| 安全访问 | ?. | 对象为null时跳过操作,返回null |
| 非空断言 | !. | 开发者保证变量非空 |
| 空合并 | ?? | 左侧为null时返回右侧内容 |
// ?
String? name = null;
print(name); // null
// ?.
String? name2 = null;
name2?.startsWith("z");
// !.
String? name3 = null;
name3!.startsWith("z");
// ?
String? name4 = null;
String res = name4 ?? "默认值";
print(res); // 默认值运算符
算数运算符
在 Dart 语言中,对数字进行加减乘除运算采用算数运算符。
| 运算符 | 作用 |
|---|---|
| + | 加 |
| - | 减 |
| * | 乘 |
| / | 除 |
| ~/ | 整除 |
| % | 去余数 |
int total = 100;
int plus = total + 10;
int sub = total - 10;
print(plus); // 110
print(sub); // 90
int ride = total * 2;
print(ride); // 200
double div = total / 3;
print(div); // 33.333333333333336
int iDiv = total ~/ 3;
print(iDiv); // 33
int rem = total % 3;
print(rem); // 1赋值运算符
| 运算符 | 作用 |
|---|---|
| = | 赋值 |
| += | 加等 |
| -= | 减等 |
| *= | 乘等 |
| /= | 除等 |
int count = 100;
count += 10;
print(count); // 110
count -= 10;
print(count); // 100
count *= 2;
print(count); // 200
double count2 = 100.0;
count2 /= 2;
print(count2); // 50.0比较运算符
| 运算符 | 作用 |
|---|---|
| == | 判断两个值是否相等 |
| != | 判断两个值是否不等 |
| > | 判断左侧值是否大于右侧值 |
| >= | 判断左侧值是否大于等于右侧值 |
< | 判断左侧值是否小于右侧值 |
<= | 判断左侧值是否小于等于右侧值 |
int a = 100;
int b = 200;
// ==
bool isTrue = a == b;
print(isTrue); // false
// !=
bool isTrue2 = a != b;
print(isTrue2); // true
// >
bool isTrue3 = a > b;
print(isTrue3); // false
// >=
bool isTrue4 = a >= b;
print(isTrue4); // false
// <
bool isTrue5 = a < b;
print(isTrue5); // true
// <=
bool isTrue6 = a <= b;
print(isTrue6); // true逻辑运算符
| 运算符 | 作用 |
|---|---|
| && | 逻辑与,a&&b, a和b同时为true,得true |
| || | 逻辑或,`a |
| ! | 逻辑非,!a,对 a 变量进行取反 |
bool a = true;
bool b = false;
// &&
bool isTrue = a && b;
print(isTrue); // false
// ||
bool isTrue2 = a || b;
print(isTrue2); // true
// !
bool isTrue3 = !a;
print(isTrue3); // false流程控制
if分支
在 Dart语言中,if分支语句可以进行不同的判断和处理。
num score = 80;
if(score >= 60){
print("恭喜你,及格了。");
}
bool isFamily = true;
if(isFamily){
print("你是家庭成员。");
}else{
print("你不是家庭成员。");
}
if(score >= 80){
print("恭喜你,成绩优秀。");
}else if(score >= 60){
print("恭喜你,成绩及格。");
}else{
print("很遗憾,成绩不及格。");
}三元运算符
在 Dart 语言中,三元运算符是一种简化版本的if语句。
num score = 80;
String result = score >= 60 ? "及格" : "不及格";
print(result); // 及格switch/case
在 Dart 语言中,如果分支条件有很多,切条件判断相等,可以使用 switch case语句。
num state = 2; // 1. 正常状态 2. 异常状态 3. 未知状态
switch(state){
case 1:
print("正常状态");
break;
case 2:
print("异常状态");
break;
default:
print("未知状态");
break;
}while
在 Dart 语言中,循环主要使用while和for循环。
break是跳出整个while循环,continue是跳出当前迭代,进入下次迭代。
List names = ["张三", "李四", "王五", "赵六"];
int index = 0;
while(index < names.length){
if(index == 1){
index++;
// break; // 张三
continue; // 张三、王五、赵六
}
print(names[index]);
index++;
}for
在 Dart 语言中,for循环可以对列表类型进行遍历。
break是跳出整个while循环,continue是跳出当前迭代,进入下次迭代。
List names = ["张三", "李四", "王五", "赵六"];
for(int i = 0; i < names.length; i++){
if(i == 1){
// break; // 张三
continue; // 张三、王五、赵六
}
print(names[i]);
}函数
函数定义
在 Dart 语言中,函数是代码组合和复用的核心单元。
函数返回值分为有返回值和无返回值:
-
有返回值:具体类型 函数名称()
-
无返回值:void 函数名称()
注意:返回值类型可以省略,Dart 会自动推断类型为 dynamic。
int num = 10;
int num2 = 20;
int sum(int a, int b){
return a + b;
}
print(sum(num, num2));可选位置参数
可选位置参数必须位于必传参数后面,采用中括号包裹。
int sum(int a, [int b = 0, int c = 0]){
return a + b + c;
}
print(sum(10));
print(sum(10, 20));可选命名参数
可选命名参数必须位于必传参数后面,采用大括号包裹。
void showPerson(String name, {int? age, String? sex}){
print("姓名: $name, 年龄: $age, 性别: $sex");
}
showPerson('张三'); // 姓名: 张三, 年龄: null, 性别: null
showPerson('李四', age: 20, sex: '男'); // 姓名: 李四, 年龄: 20, 性别: 男匿名函数
可以声明一个没有名称的函数赋值给变量,进行调用。
函数的类型使用Function来声明。
void main() {
test();
onTest(test);
}
Function test = () {
print("测试数据");
};
void onTest(Function callback){
callback();
}箭头函数
当函数体只有一行代码时,可以使用箭头函数编写。
使用箭头函数可以省略return关键字。
void main(){
print(sum(10, 20));
}
int sum(int a, int b) => a + b;类
Dart 语言中,类(class)是面向对象编程的核心,类包含属性和方法来定义对象的行为和状态。
void main(){
Person p = new Person();
p.name = "张三";
p.study();
}
class Person{
String? name;
int? age;
String? sex;
void study(){
print('$name正在学习');
}
}默认构造函数
实例化对象的时候,使用构造函数直接给对象中的属性赋值。
void main() {
Person p = new Person(name: "张三", age: 18, sex: "男");
p.study();
Person p2 = new Person(name: "李四", sex: "女");
p2.study();
}
class Person{
String? name;
int? age;
String? sex;
// 构造函数
Person({String? name, int? age, String? sex}){
this.name = name;
this.age = age;
this.sex = sex;
}
void study(){
print('$name正在学习, 年龄$age, 性别$sex');
}
}命名构造函数
构造函数可以采用命名的方式,返回一个实例对象。
默认构造函数和命名构造函数可同时存在。
void main() {
Person p = Person.createPerson(name: "张三", sex: "男");
p.study();
}
class Person{
String? name;
int? age;
String? sex;
// 命名构造函数
Person.createPerson({String? name, int? age, String? sex}){
this.name = name;
this.age = age;
this.sex = sex;
}
void study(){
print('$name正在学习, 年龄$age, 性别$sex');
}
}构造函数语法糖
同名构造函数和命名构造函数都支持简写写法。
void main(){
Person p = Person(name: "张三", age: 18);
p.study();
Person p2 = Person.createPerson(name: "李四", sex: "女");
p2.study();
}
class Person{
String? name;
int? age;
String? sex;
Person({this.name,this.age,this.sex});
Person.createPerson({this.name,this.age,this.sex});
void study(){
print('$name正在学习, 年龄$age, 性别$sex');
}
}公有属性和私有属性
公有属性,提供自身或者其他外部文件和类使用的属性和方法。
私有属性,仅提供自身使用的属性和方法,其他外部文件和类无法访问。
语法:私有属性以下划线开头,如_name,其余均为共有属性。
// 28-公有私有属性.dart
void main() {
Person p = new Person(name: '张三', age: 20, sex: '男');
p.readName();
p._readAge();
}
class Person{
String? name;
int? age;
String? _sex;
Person({this.name,this.age,this._sex});
void readName(){
print(name);
}
void _readAge(){
print('$_sex');
}
}// test.dart
import '28-公有私有属性.dart';
void main() {
Person p = new Person(name: '张三', age: 20, sex: '男');
print(p.name);
p.readName();
// print(p._sex); // 错误: '_sex' is not a member of 'Person'.
// p._readAge(); // 错误: '_readAge' is not a member of 'Person'.
}继承
定义:继承是拥有父类的属性和方法。
特点:dart 属于单继承,一个类只能拥有一个直接父类,子类拥有父类所有的属性和方法。
语法:class类名extends父类。
重写:子类可通过@override注解重写父类的方法,扩展其行为。
注意:子类不会继承父类的构造函数,子类必须通过super关键字调用父类构造函数确保父类正确初始化。
void main(){
Child c = new Child(name:'张三',age:20);
print(c.name); // 张三
c.study(); // 父类-张三正在学习
}
class Parent{
String? name;
int? age;
Parent({this.name,this.age});
void study(){
print('父类-$name正在学习');
}
}
class Child extends Parent{
Child({String? name,int? age}):super(name:name,age:age);
@override
void study(){
// 调用了父类原方法
// super.study();
// 子类自己的方法
print('子类-$name正在学习');
}
}多态
定义:Dart 中的类是指同一个操作作用于不同的对象,可以产生不同的执行效果。
场景:微信和支付宝都遵循同样的支付接口,但实现逻辑不同,即同一个支付操作拥有不同的支付效果。
实现方式:1.继承和方法重写、2.抽象类和接口
需求:定义一个父类,分别实现微信和支付宝支付类,重写得到不同的支付渠道。
继承和方法重写
void main() {
Payment p = new WxPay();
p.pay(); // 微信支付
p = new Alipay();
p.pay(); // 支付宝支付
}
// 基础支付类
class Payment{
void pay(){
print('支付');
}
}
// 微信支付
class WxPay extends Payment{
@override
void pay(){
print('微信支付');
}
}
// 阿里支付
class Alipay extends Payment{
@override
void pay(){
print('支付宝支付');
}
}抽象类
方式:使用abstract关键字声明一个抽象类(没有实现体)。
方式:使用implements关键字继承并实现抽象类。
void main() {
Payment p = new WxPay();
p.pay(); // 微信支付
p = new Alipay();
p.pay(); // 支付宝支付
}
abstract class Payment{
void pay();
}
class WxPay extends Payment{
@override
void pay(){
print('微信支付');
}
}
class Alipay extends Payment{
@override
void pay(){
print('支付宝支付');
}
}混入
定义:Dart 允许在不使用传统继承的情况下,向类中添加新的功能。
方式:使用mixin关键字定义一个对象。
方式:使用with关键字将定义的对象混入到当前对象中。
特点:一个类支持with多个mixin,调用优先级遵循“后来居上”原则,即后混入的会覆盖先混入的同名和方法。
需求:让一个学生和一个老师类都拥有唱歌的方法。
void main() {
Student s = new Student(name:'张三',age:20);
s.song(s.name!);
Teacher t = new Teacher(name:'李四',age:30);
t.song(t.name!);
}
mixin Base{
void song(String name){
print('$name正在唱歌');
}
}
class Teacher with Base{
String? name;
int? age;
Teacher({this.name,this.age});
}
class Student with Base{
String? name;
int? age;
Student({this.name,this.age});
}泛型
定义:Dart 允许使用类型参数,限定类型的同时又让类型更加灵活,让代码更加健壮和维护性更强。
场景:list类型中只想存储string类型怎么办?函数中返回值希望和参数一个类型怎么办?
常见分类:泛型集合、泛型方法、泛型类。
void main() {
// 列表泛型
List<int> list = [];
list.add(1);
print(list);
// Map泛型
Map<String, int> map = {};
map["name"] = 123;
print(map);
// 函数
print(getValue<String>('hello'));
printList<String>(['zs', 'ls']);
// 类
Student<String> s = new Student();
s.name = '张三';
print(s.name);
}
T getValue<T> (T value){
return value;
}
void printList<T>(List<T> list){
for(var i = 0; i < list.length; i++){
print(list[i]);
}
}
class Student<T>{
T? name;
}异步编程
Dart 是单线程语言,即同时只做一件事,遇到耗时任务就会造成程序阻塞,此时需要异步编程。
Dart 采用单线程 + 事件循环的机制完成耗时任务的处理。
事件循环: 执行同步代码 => 执行微任务队列 => 执行事件队列 => 结束
微任务队列:Future.microtask()。
事件队列:Future、Future.delayed()、I/O操作(文件、网络)等。
Future
Future 代表一个异步操作的最终结果。
状态:Uncompleted(等待)、Completed with a value(成功)、Completed with a error(失败)。
创建:Future(() {})。
执行成功:不抛出异常-成功状态-then(() )。
执行失败:throw Exception()-失败状态-catchError(() )。
Future<String> f = Future(() {
// return 'hello world';
throw Exception('报错了!');
});
f.then(((value) {
print(value); // hello world
}));
f.catchError((error) {
print(error);
});Future链式调用
Future 可以通过链式的方式连续得到异步的结果。
通过 Future().then()拿到执行的结果。
通过Future().catchError()拿到执行失败的结果。
注意:在上一个 then 返回对象会在下一个 then 中接收。
需求:执行三个异步任务,按照顺序执行,最后一次抛出异常。
void main() {
Future(() {
return 'hello world';
}).then(((value) {
print(value);
return Future(() => 'test1');
})).then(((value) {
print(value);
return Future(() => 'test2');
})).then(((value) {
print(value);
return Future(() => 'test3');
})).then((value) {
print(value);
throw Exception('报错了!');
}).catchError((error) {
print(error);
});
}async/await
除了通过 then/catchError的方式,还可以通过async/await来实现异步编程。
void main() {
test();
}
test() async{
try{
// 延迟5s执行
await Future.delayed(Duration(seconds: 5));
String msg = await Future(() => 'hello world');
print(msg); // hello world
throw Exception('报错了!');
}catch(error){
print(error);
}
}工程化
配置环境
-
克隆Flutter地址(目录不要带中文)。
-
GitHub
https://github.com/flutter/flutter.git -
码云
git clone -b stable https://gitee.com/mirrors/Flutter.git ~/GitHub/flutter-master
-
-
检查mac环境变量。
echo $SHELL // /bin/zsh如果显示
/bin/zsh说明.zshrc是配置文件,如果是/bin/bash说明.bash_profile是配置文件。 -
终端打开配置文件
nano ~/.zshrc -
添加环境变量
export PUB_HOSTED_URL="https://pub.flutter-io.cn" export FLUTTER_STORAGE_BASE_URL="https://storage.flutter-io.cn" export PATH="/Users/zhouxingyuan/GitHub/flutter-master/bin:$PATH"PATH根据Flutter的地址来。 -
按下键盘上的
Control + O(字母 O,代表 WriteOut 写入)再按下**Enter**。按下键盘上的
Control + X(代表 Exit 退出)。重启配置
source ~/.zshrc -
验证是否成功
flutter --version注意:第一次运行这个指令,会安装依赖,耐心等待安装。
-
诊断
Flutter环境flutter doctor -v
创建项目
使用命令创建Flutter工程(web)。
flutter create --platforms web <项目名称>运行项目
flutter run目录
.dart_tool:Dart 工具生成的文件和缓存。
.idea:IntelliJ IDEA 配置文件。
build:构建产物目录,包含编译生成的文件。
lib:项目的主要源代码目录。
main.dart:应用程序入口。
test:测试文件目录。
web:web平台特定的配置和资源文件。
.gitignore:Git版本控制忽略的文件配置。
metadata:Flutter项目标识文件(自动生成)。
analysis_options.yaml:配置静态代码分析工具。
flutter_core.iml:用于存储模块的特定设置。
pubspec.lock:依赖锁定文件。
pubspec.yaml:项目依赖和配置文件。
README.md:项目说明文档。
Widget
runApp函数是Flutter内部提供的一个函数,启动一个Flutter应用就是从调用这个函数开始的。
Widget表示控件、组件、部件的含义,Flutter中万物皆Widget。
Material库
Material是Google公司推行的一套设计风格,有很多的设计规范,如颜色、文字排版、动画等。
目的:Material为Android、Web、IOS、HarmonyOS多个平台提供统一的交互和视觉体验。
MaterialApp
整个应用被MaterialApp包裹,方便对整个应用进行整体设计。
常见属性:
title:用来展示窗口的标题内容。
theme:用来设置整个应用的主题。
home:用来展示窗口的主体内容。
// main.dart
import 'package:flutter/material.dart';
void main(){
runApp(MaterialApp(
title: 'Flutter初体验',
// ThemeData(...) 配置应用的主题
// scaffoldBackgroundColor: 背景颜色
theme: ThemeData(scaffoldBackgroundColor: Colors.blue),
// home: 首页
// Scaffold(...) 配置应用的布局
home: Scaffold()
));
}Scaffold
用于构建Material Design风格页面的核心布局组件,提供标准、灵活配置的页面框架。
appBar:页面顶部的应用栏,通常用于显示标题、导航按钮和操作菜单。
body:页面的主要内容区域,可以放置任何其它组件,是页面的核心。
bottomNavigationBar:底部导航栏,方便用户在不同核心功能页面间切换。
backgroundColor:设置整个Scaffold的背景颜色。
floatingActionButton:悬浮操作按钮,常用于触发页面的主要动作。
import 'package:flutter/material.dart';
void main(){
runApp(MaterialApp(
title: 'Flutter初体验',
/**
* ThemeData(...) 配置应用的主题
* scaffoldBackgroundColor: 背景颜色
*/
// theme: ThemeData(scaffoldBackgroundColor: Colors.white),
/**
* home: 首页
* Scaffold(...) 配置应用的布局
*/
home: Scaffold(
appBar: AppBar(
title: Text('顶部标题'),
),
body: Container(
child: Center(
child: Text('中间部分'),
),
),
bottomNavigationBar: Container(
height: 80,
child: Center(
child: Text('底部区域'),
),
),
)
));
}自定义组件
根据自己特定的需求创建自己的Widget。
Flutter分为无状态组件和有状态组件。
无状态组件
定义:创建一个新的类,继承StatelessWidget类并实现build方法。
要点:build返回一个Widget。
场景:纯展示型组件,没有用户交互操作。
需求:把之前案例的骨架换成无状态组件。
import 'package:flutter/material.dart';
void main(){
runApp(MainPage());
}
class MainPage extends StatelessWidget{
@override
Widget build(BuildContext context){
return MaterialApp(
title: 'Test Page',
home: Scaffold(
appBar: AppBar(
title: Text('Top Content'),
),
body: Container(
child: Center(
child: Text('Middle Content'),
),
),
bottomNavigationBar: Container(
height: 80,
child: Center(
child: Text('Bottom Content'),
),
),
)
);
}
}有状态组件
定义:有状态组件是构建动态交互界面的核心,能够管理变化的内部状态,当状态改变时,组件就会更新显示内容。
实现步骤1:创建两个类,第一个继承StatefulWidget类,主要接收和定义最终参数,核心作用是创建State对象。
实现步骤2:第二个类继承State<第一个类名>,负责管理所有可变的数据和业务逻辑,并实现build构建方法。
要点:build方法返回一个Widget。
需求:将之前骨架屏换成有状态组件。
import 'package:flutter/material.dart';
void main(){
runApp(MainPage());
}
class MainPage extends StatefulWidget{
@override
State<StatefulWidget> createState() => _MainPageState();
}
class _MainPageState extends State<MainPage>{
@override
Widget build(BuildContext context){
return MaterialApp(
title: 'Test Page1',
home: Scaffold(
appBar: AppBar(
title: Text('Top Content'),
),
body: Container(
child: Center(
child: Text('Middle Content'),
),
),
bottomNavigationBar: Container(
height: 80,
child: Center(
child: Text('Bottom Content'),
),
),
)
);
}
}组件生命周期
-
无状态组件
唯一阶段:
build方法。当组件被创建或父组件状态变化导致其需要重新构建时,
build方法会被调用。import 'package:flutter/material.dart'; void main() { runApp(const MainPage()); } class MainPage extends StatelessWidget { const MainPage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { print('组件被创建了'); return MaterialApp( home: Scaffold( body: Container( child: Center( child: Text('Hello World'), ), ), ), ); } } -
有状态组件
创建阶段:
createState:Widget初始化调用,创建State对象。
initState:State对象插入Widget树立刻执行,仅执行一次。
didChangeDependencies:initState后立刻执行,当所依赖的InheritedWidget更新时调用,可能多次。构建于更新阶段:
build:构建 UI 方法,初始化或更新后多次调用。
didUpdateWidget:父组件传入新配置时调用,用于比较新旧配置。销毁阶段:
deactiveate:当State对象从树中暂时移除时调用。
dispose:当State对象被永久移除时调用,释放资源,仅执行一次。InheritedWidget:专门用于在Widget树中自顶向下高效地共享数据,顶层组件提供数据,子孙节点直接获取。import 'package:flutter/material.dart'; void main() { runApp(MainPage()); } class MainPage extends StatefulWidget { MainPage({Key? key}) : super(key: key); @override _MainPageState createState() { // print('createState执行了'); return _MainPageState(); } } class _MainPageState extends State<MainPage> { @override initState() { print('initState执行了'); super.initState(); } @override didChangeDependencies() { print('didChangeDependencies执行了'); super.didChangeDependencies(); } @override Widget build(BuildContext context) { print('build执行了'); return Container( child: null, ); } @override didUpdateWidget(MainPage oldWidget) { print('didUpdateWidget执行了'); super.didUpdateWidget(oldWidget); } @override deactiveate() { print('deactiveate执行了'); super.deactiveate(); } @override dispose() { print('dispose执行了'); super.dispose(); } }
快速创建组件
安装插件:Awesome Flutter Snippets
无状态组件快捷键:statelessW
有状态组件快捷键:statefulW
组件
Flutter提供了丰富强大的布局组件来构建用户界面。
-
基础容器
Container、Center、Align、Padding提供装饰、对齐、边距等基础样式和布局控制,是使用频率极高的组件。
-
线性布局
Row、Column在水平或垂直方向线性排列子组件,是构建界面的基础。
-
弹性布局
Flex、Expanded、Flexible按照比例分配剩余空间,实现自适应布局,常与
Row和Column配合使用。 -
层叠布局
Stack、Positioned让子组件重叠堆叠,用于实现图片上叠加的文字、悬浮按钮等效果。
-
流式布局
Wrap、Flow当主轴空间不足时自动换行或换列,常用于标签、滤镜等动态宽高内容的排列。
-
滚动布局
ListView、GaidView提供可滚动的列表或网格视图,高效展示大量数据。
Container
Container是功能丰富的布局组件,是一个多功能组合容器。
可通过多种方式约束定义大小,有明确优先级规则。
优先级:明确宽高 > Container约束 > 父组件约束 > 自适应组件大小
-
布局
alignment控制其
child(子组件)在容器内部的对齐方式。 -
尺寸
width、height、constraints设置容器的宽度和高度/为容器设置更复杂的尺寸约束(如最小/最大宽度)。
-
间距
padding、margin按照比例分配剩余空间,实现紫石英布局,常与
Row和Column配合使用。 -
装饰
color、decoration为容器设置一个简单的背景颜色/为容器设置复杂的背景装饰。
-
变换
transform对容器及其内容进行矩阵变换。
-
子组件
child容器内包含的唯一直接子组件。
import 'package:flutter/material.dart';
void main() {
runApp(MainPage());
}
class MainPage extends StatelessWidget {
const MainPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Container(
width: 200,
height: 200,
alignment: Alignment.center,
margin: EdgeInsets.all(20),
child: Text('Hello Container', style: TextStyle(color: Colors.white, fontSize: 20)),
// color: Colors.blue,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.amber, width: 3),
),
transform: Matrix4.rotationZ(0.05),
)
)
);
}
}Center
将其子组件在父容器的空间内进行水平和垂直方向上的居中排列。
import 'package:flutter/material.dart';
void main() {
runApp(MainPage());
}
class MainPage extends StatelessWidget {
const MainPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Center 示例'),
),
body: Center(
child: Container(
width: 100,
height: 100,
alignment: Alignment.center,
child: Text('居中内容', style: TextStyle(color: Colors.white),),
color: Colors.blue,
),
)
)
);
}
}Align
作用:精确控制其子组件在父容器空间内的对齐位置。
alignment(对齐方式):子组件在父容器内的对齐方式。
widthFactor(宽度因子):Align的宽度将是子组件宽度乘以该因子。
heightFactor(高度因子):Align的高度将是子组件高度乘以该因子。
import 'package:flutter/material.dart';
void main() {
runApp(MainPage());
}
class MainPage extends StatelessWidget {
const MainPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Container(
color: Colors.red,
child: Align(
widthFactor: 3,
heightFactor: 3,
alignment: Alignment.bottomLeft,
child: Icon(Icons.star, color: Colors.amber, size: 50,),
)
)
)
);
}
}Padding
为其子组件添加内边距
-
paddingEdgeInsetsGeometry必须。定义内边距的大小和方向,通常使用
EdgeInsets类来设置。 -
childWidget需要被添加内边距的子组件。
补充:
EdgeInsets.all(20):配置四个方向
EdgeInsets.symmetric(horizontal: 20, vertical: 10):配置水平和垂直
EdgeInsets.only(left: 20, top: 10):单独配置某一个方向
import 'package:flutter/material.dart';
void main() {
runApp(MainPage());
}
class MainPage extends StatelessWidget {
const MainPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Container(
// padding: EdgeInsets.all(20), // 4个方向都相同
// padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10), // 水平方向相同,垂直方向不同
// padding: EdgeInsets.only(left: 20, top: 10), // 左边和上边有内边距
color: Colors.red,
child: Padding(
// padding: EdgeInsets.all(20),
// padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
padding: EdgeInsets.only(left: 20, top: 10), // 左边和上边有内边距
child: Container(color: Colors.blue,)
),
)
),
);
}
}Column
用于垂直排列其子组件的核心布局容器。
-
mainAxisAlignmentMainAxisAlignment控制子组件在主轴(垂直方向)上的排列方式,如顶部对齐、居中或均匀分布。
-
crossAxisAlignmentCrossAxisAlignment控制子组件在交叉轴(水平方向)上的对齐方式,如左对齐、右对齐或拉伸填满。
-
mainAxisSizeMainAxisSize决定
Column本身在垂直方向上的策略:是占满所有可用空间(max),还是仅包裹子组件的内容(min)。 -
childrenList<Widget>需要被垂直排列的子组件列表。
import 'package:flutter/material.dart';
void main() {
runApp(MainPage());
}
class MainPage extends StatelessWidget {
const MainPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Container(
width: double.infinity,
height: double.infinity,
color: Colors.red,
child: Column(
// mainAxisAlignment: MainAxisAlignment.spaceBetween, // 两头对齐
// mainAxisAlignment: MainAxisAlignment.spaceAround, // 环绕模式
// mainAxisAlignment: MainAxisAlignment.spaceEvenly, // 均分模式
// mainAxisAlignment: MainAxisAlignment.start, // 顶部对齐
// mainAxisAlignment: MainAxisAlignment.center, // 居中对齐
// mainAxisAlignment: MainAxisAlignment.end, // 底部对齐
// crossAxisAlignment: CrossAxisAlignment.start, // 交叉轴start
// crossAxisAlignment: CrossAxisAlignment.center, // 交叉轴center
crossAxisAlignment: CrossAxisAlignment.end, // 交叉轴end
children: [
Container(
width: 100,
height: 100,
color: Colors.blue,
),
Container(
width: 100,
height: 100,
color: Colors.blue,
),
Container(
width: 100,
height: 100,
color: Colors.blue,
)
],
),
)
)
);
}
}Row
用于水平排列其子组件的核心布局容器。
-
mainAxisAlignmentMainAxisAlignment控制子组件在主轴(垂直方向)上的排列方式,如顶部对齐、居中或均匀分布。
-
crossAxisAlignmentCrossAxisAlignment控制子组件在交叉轴(水平方向)上的对齐方式,如左对齐、右对齐或拉伸填满。
-
mainAxisSizeMainAxisSize决定
Column本身在垂直方向上的策略:是占满所有可用空间(max),还是仅包裹子组件的内容(min)。 -
childrenList<Widget>需要被垂直排列的子组件列表。
import 'package:flutter/material.dart';
void main() {
runApp(MainPage());
}
class MainPage extends StatelessWidget {
const MainPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Container(
width: double.infinity,
height: double.infinity,
color: Colors.red,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, // 两头对齐
// mainAxisAlignment: MainAxisAlignment.spaceAround, // 环绕模式
// mainAxisAlignment: MainAxisAlignment.spaceEvenly, // 均分模式
// mainAxisAlignment: MainAxisAlignment.start, // 顶部对齐
// mainAxisAlignment: MainAxisAlignment.center, // 居中对齐
// mainAxisAlignment: MainAxisAlignment.end, // 底部对齐
crossAxisAlignment: CrossAxisAlignment.start, // 交叉轴start
// crossAxisAlignment: CrossAxisAlignment.center, // 交叉轴center
// crossAxisAlignment: CrossAxisAlignment.end, // 交叉轴end
children: [
Container(
width: 100,
height: 100,
color: Colors.blue,
),
Container(
width: 100,
height: 100,
color: Colors.blue,
),
Container(
width: 100,
height: 100,
color: Colors.blue,
)
],
),
)
)
);
}
}Flex
允许沿一个主轴(水平或垂直)排列其子组件,灵活地控制这些子组件在主轴上的尺寸比例和空间分配。
-
directionAxis.horizontal、Axis.vertical主轴方向,决定子组件的排列方向。
-
mainAxisAlignmentMainAxisAlignment子组件在主轴方向上的对齐方式。
-
crossAxisAlignmentCrossAxisAlignment子组件在交叉轴方向上的对齐方式。
-
mainAxisSizeMainAxisSizeFlex容器自身在主轴上的尺寸策略。
Flex子组件经常使用Expanded和Flexible来控制空间分配。
Expanded与Flexible的区别:Expanded强制子组件填满所有剩余空间,Flexible根据自身大小调整,不强制占满空间。
import 'package:flutter/material.dart';
void main() {
runApp(MainPage());
}
class MainPage extends StatelessWidget {
const MainPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Container(
width: double.infinity,
height: double.infinity,
color: Colors.yellow,
child: Flex(
direction: Axis.horizontal,
children: [
Expanded(
flex: 2,
child: Container(
width: 100,
height: 100,
color: Colors.red,
),),
Expanded(
flex: 1,
child: Container(
width: 100,
height: 100,
color: Colors.green,
)),
],
),
),
)
);
}
}练习:顶部和底部高度固定,中间区域充满剩余空间
import 'package:flutter/material.dart';
void main() {
runApp(MainPage());
}
class MainPage extends StatelessWidget {
const MainPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Container(
width: double.infinity,
height: double.infinity,
color: Colors.yellow,
child: Flex(
direction: Axis.vertical,
children: [
Container(
height: 100,
color: Colors.red,
),
Expanded(
flex: 1,
child: Container(
height: 100,
color: Colors.green,
)),
Container(
height: 100,
color: Colors.pink,
),
],
),
),
)
);
}
}Warp
流式布局组件,当子组件在主轴方向上排列不下时,它会进行自动换行(或换列)。
-
directionAxis.horizontal(水平)、Axis.vertical(垂直)设置主轴方向,即排列方向。
-
spacing数值
主轴方向上,子组件之间的间距。
-
runSpacing数值
交叉轴方向上,行(或列)之间的间距。
-
alignmentWrapalignment子组件在主轴方向上的对齐方式。
-
runAlignmentWrapAlignment交叉轴方向上的对齐方式。
注意:Column、Row、Flex内容超出均不会换行。
Wrap组件更像是Flex组件加了换航特性。
import 'package:flutter/material.dart';
void main() {
runApp(MainPage());
}
class MainPage extends StatelessWidget {
const MainPage({Key? key}) : super(key: key);
List<Widget> getList() {
return List.generate(10, (index) {
return Container(
width: 100,
height: 100,
color: Colors.blue
);
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Container(
width: double.infinity,
height: double.infinity,
color: Colors.red,
child: Wrap(
direction: Axis.horizontal,
// alignment:WrapAlignment.spaceBetween,
spacing: 10,
runSpacing: 10,
children: getList(),
),
),
)
);
}
}Stack/Positioned
层叠布局组件,允许将多个子组件按照 Z 轴(深度方向)进行叠加排列。
-
alignmentAlignmentGeometry控制非定位子组件在
Stack内的对齐方式,默认左上角。 -
fitStackFit控制非定位子组件如何适应
Stack的尺寸。 -
clipBehaviorClip控制子组件超出
Stack边界时的裁剪方式。 -
childrenList<Widget>需要被层叠排列的子组件列表。
可以使用Stack配合Positioned对子组件进行精确定位控制。Positioned通过left、right、top、bottom来将子组件钉在Stack`的某个角落或边缘。
import 'package:flutter/material.dart';
void main() {
runApp(MainPage());
}
class MainPage extends StatelessWidget {
const MainPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Container(
width: double.infinity,
height: double.infinity,
color: Colors.yellow,
child: Stack(
children: [
Container(
width: 300,
height: 300,
color: Colors.red,
),
Positioned(
top: 10,
left: 10,
child: Container(
width: 100,
height: 100,
color: Colors.blue,
),
),
Positioned(
bottom: 10,
right: 10,
child: Container(
width: 100,
height: 100,
color: Colors.green,
),
),
],
),
),
),
);
}
}Text
在用户界面中显示文本的基础组件。
-
data必须,需要显示的文本内容。
-
style文本样式,可设置颜色、大小、粗细等。
-
textAlign文本在容器内的水平对齐方式,如
.left、.center。 -
maxLines文本显示的最大行数。
import 'package:flutter/material.dart';
void main() {
runApp(MainPage());
}
class MainPage extends StatelessWidget {
const MainPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Container(
alignment: Alignment.center,
// child: Text('Hello World',
// style: TextStyle(
// fontSize: 24,
// color: Colors.blue,
// fontWeight: FontWeight.bold,
// decoration: TextDecoration.underline,
// decorationColor: Colors.red,
// ),),
// child: Text('Hello World,Hello World,Hello World,Hello World,Hello World,Hello World,Hello World,Hello World,Hello World,Hello World,Hello World,Hello World,Hello World,Hello World,Hello World,Hello World,Hello World,Hello World,Hello World,Hello World,Hello World,Hello World,Hello World,Hello World,',
// maxLines: 2,
// overflow: TextOverflow.ellipsis,
// style: TextStyle(
// fontSize: 30
// ),),
child: Text.rich(TextSpan(
text: 'Hello ',
style: TextStyle(
fontSize: 30,
color: Colors.red,
),
children: [
TextSpan(
text: 'World',
style: TextStyle(
color: Colors.blue,
),
),
TextSpan(
text: '!',
),
]
)),
),
),
);
}
}Image
在用户界面中显示图片的核心部件
-
Image.asset()加载目录资源
assets中的图片。需要在pubspec.yaml文件中声明资源路径。 -
Image.network()直接从网络地址加载图片。
-
Image.file()加载设备本地存储中的图片文件。
-
Image.memory()加载内存中的图片数据。
常用属性
-
width/height设置图片区域的宽度和高度。
-
fit控制图片如何适应其显示区域,例如是否拉伸、裁剪或保持原比例。
-
alignmentAlignmentGeometry图片在其显示区域内的对齐方式,如
Alignment.center。 -
repeatImageRepeat当图片小于显示区域时,设置是否以及如何重复平铺图片。
import 'package:flutter/material.dart';
void main() {
runApp(MainPage());
}
class MainPage extends StatelessWidget {
const MainPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Container(
alignment: Alignment.center,
width: double.infinity,
height: double.infinity,
// child: Image(
// image: AssetImage('lib/images/test.png'),
// width: 200,
// height: 200,
// fit: BoxFit.contain
// // fit: BoxFit.cover,
// // fit: BoxFit.fill
// // fit: BoxFit.fitWidth,
// ),
child: Image(
image: NetworkImage("https://picsum.photos/200"),
width: 200,
height: 200,
fit: BoxFit.cover,
),
),
),
);
}
}# pubspec.yaml
assets:
- lib/images/TextField
实现文本输入功能的组件。
-
controller文本编辑控制器,用于获取、设置文档内容及监听变化。
-
decortation当时输入框的外观、如标签、提示文字、图标、边框等。
-
style定义输入文本样式
-
maxLines最大行数
-
onChanged输入内容发生变化时执行的回调函数。
-
onSubmitted用户提交输入时的回调函数。
import 'package:flutter/material.dart';
void main() {
runApp(MainPage());
}
class MainPage extends StatefulWidget {
MainPage({Key? key}) : super(key: key);
@override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
TextEditingController _usernameController = TextEditingController();
TextEditingController _passwordController = TextEditingController();
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('登陆'),
),
body: Container(
child: Column(
children: [
TextField(
controller: _usernameController,
onChanged: (value) => {
print(value)
},
onSubmitted: (value) => {
print(value)
},
decoration: InputDecoration(
contentPadding: EdgeInsets.only(left: 20),
hintText: '请输入用户名',
fillColor: Color.fromARGB(255, 222, 219, 207),
filled: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(25),
borderSide: BorderSide.none,
),
),
),
SizedBox(
height: 20,
),
TextField(
controller: _passwordController,
obscureText: true,
decoration: InputDecoration(
contentPadding: EdgeInsets.only(left: 20),
hintText: '请输入密码',
fillColor: Color.fromARGB(255, 222, 219, 207),
filled: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(25),
borderSide: BorderSide.none,
),
),
),
SizedBox(height: 20),
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(25),
color: Colors.black,
),
height: 50,
width: double.infinity,
child: TextButton(onPressed: () {
print("用户名:${_usernameController.text},密码:${_passwordController.text}");
}, child: Text('登陆', style: TextStyle(color: Colors.white))),
)
],
),
),
),
);
}
}滚动组件
常用滚动:
-
SingleChildScrollView让单个子组件可以用滚动,所有内容一次性渲染。
使用场景:长表单、设置页、内容不固定但是总量不多的页面。
-
ListView线性列表,通过
builder可以实现懒加载,性能优异。使用场景:聊天记录、新闻、常见的单列滚动的数据列表。
-
GridView网格布局列表,支持懒加载,可以固定列数。
使用场景:图片墙、商品网格、应用图标列表。
-
CustomScrollView复杂布局方案,通过组合多个
Sliver组件实现滚动使用场景:电商首页、社交App、个人主页多个滚动紧密联动。
-
PageView整页滚动效果,支持横向和纵向。
应用引导页、图片轮播图、书籍翻页。
SingleChildScrollView
只能包含一个子组件,如果滚动通过子组件,通常将其嵌套在Column或Row组件中。
滚动方向:通过scrollDirection属性控制,默认为垂直方向(Axis.vertical),也可以设置为水平方向(Axis.horizontal)
特点:一次性构建所有子组件,如果嵌套的Column或Row中包含大量子项,可能会导致性能问题,建议使用ListView。
控制滚动:绑定一个ScrollController对象给controller对象,使用animateTo/jumpTo方法控制。
import 'package:flutter/material.dart';
void main() {
runApp(MainPage());
}
class MainPage extends StatefulWidget {
MainPage({Key? key}) : super(key: key);
@override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
ScrollController _scrollController = ScrollController();
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('滚动测试')),
body: Stack(
children: [
SingleChildScrollView(
controller: _scrollController,
padding: EdgeInsets.symmetric(horizontal: 20),
child: Column(
children: List.generate(100, (index) {
return Container(
margin: EdgeInsets.only(top: 10),
height: 100,
color: Colors.blue,
child: Text(
'我是第${index + 1}个',
style: TextStyle(color: Colors.white, fontSize: 30),
),
alignment: Alignment.center,
);
}),
),
),
Positioned(
right: 0,
top: 10,
child: GestureDetector(
onTap: () {
_scrollController.animateTo(
_scrollController.position.maxScrollExtent,
duration: Duration(seconds: 1),
curve: Curves.ease,
);
},
child: Container(
width: 80,
height: 80,
alignment: Alignment.center,
child: Text(
'去底部',
style: TextStyle(color: Colors.white, fontSize: 20),
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(40),
color: Colors.red,
),
),
),
),
Positioned(
right: 0,
bottom: 10,
child: GestureDetector(
onTap: () {
print('点击了去底部按钮');
_scrollController.animateTo(
0,
duration: Duration(seconds: 1),
curve: Curves.ease,
);
},
child: Container(
width: 80,
height: 80,
alignment: Alignment.center,
child: Text(
'去顶部',
style: TextStyle(color: Colors.white, fontSize: 20),
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(40),
color: Colors.red,
),
),
),
),
],
),
),
);
}
}
ListView
用于构建可滚动列表的核心部件,并提供流畅滚动体验。
提供多种构造函数,如:
-
ListView:默认构造函数适用于静态数量有限数据一次性构建所有表项 -
“ListView.builder`:按需构建,不会在初始化时将所有列表项都创建,而是根据用户的滚动行为,动态的创建和销毁列表项目。
接收itemBuilder回调函数来按需构建列表项,通过itemCount控制列表长度。
ListView.separated:在ListView.builder的基础上,额外提供了构建分割线的能力。
接收itemBuilder、separatorBuilder、itemcount三个属性。
采用按需加载(懒加载),只构建当前可见区域的列表项,极大提升长列表性能。
import 'package:flutter/material.dart';
void main() {
runApp(MainSate());
}
class MainSate extends StatefulWidget {
MainSate({Key? key}) : super(key: key);
@override
_MainSateState createState() => _MainSateState();
}
class _MainSateState extends State<MainSate> {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('ListView')),
// body: ListView(
// children: List.generate(100, (index) {
// return Container(
// margin: EdgeInsets.only(top: 10),
// height: 80,
// color: Colors.blue,
// child: Text('第${index + 1}项'),
// alignment: Alignment.center,
// );
// }),
// )
// body: ListView.builder(
// itemCount: 10000, // 列表项数量
// itemBuilder: (BuildContext context, int index) {
// return Container(
// margin: EdgeInsets.only(top: 10),
// height: 80,
// color: Colors.blue,
// child: Text('第${index + 1}项'),
// alignment: Alignment.center,
// );
// },
// ),
body: ListView.separated(
itemBuilder: (BuildContext context, int index) {
return Container(
height: 80,
color: Colors.blue,
child: Text('第${index + 1}项'),
alignment: Alignment.center,
);
},
separatorBuilder: (BuildContext context, int index) {
return Container(
height: 10,
color: Colors.pink,
);
},
itemCount: 1000,
),
),
);
}
}GirdView
用于创建二维可滚动网格布局的核心组件。
提供多种构造函数,如:
-
Gridview.count以列数为优先。指定网格多少列,
Flutter自动计算列的宽度,在空间均匀排列。 -
GridView.extent通过
maxCrossAxisExtent来动态计算横向或者纵向可以展示多少个。 -
GridView.builder可同时实现
Gridview.count和GridView.extent功能,同时支持懒加载,只渲染可见区域。
GridView默认构造方式-(写起来最繁琐,很少使用)。
GridView.count-基于固定列数的网格布局(最常用之一)。
GridView.extent-基于固定子项最大宽度/高度的网格布局(最常用之二)。
GridView.builder-用于网格项数量巨大或动态生成的情况,需要接收gridDelegate布局属性。
gridDelegate:SliverGridDelegateWithFixedCrossAxisCount:固定列数
SliverGridDelegateWithMaxCrossAxisExtent:最大宽度
mainAxisSpacing:主轴间距
crossAxisSpacing:交叉轴间距
scrollDirection:设置滚动方向。
import 'package:flutter/material.dart';
void main() {
runApp(MainPage());
}
class MainPage extends StatefulWidget {
MainPage({Key? key}) : super(key: key);
@override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('ListView.separated')),
// body: GridView.count(
// scrollDirection: Axis.horizontal,
// crossAxisCount: 3,
// padding: EdgeInsets.all(10),
// mainAxisSpacing: 10,
// crossAxisSpacing: 10,
// children: List.generate(100, (index) {
// return Container(
// color: Colors.blue,
// child: Text('第${index + 1}项', style: TextStyle(color: Colors.white)),
// alignment: Alignment.center,
// );
// }),
// ),
// body: GridView.extent(
// maxCrossAxisExtent: 200,
// mainAxisSpacing: 10,
// crossAxisSpacing: 10,
// children: List.generate(100, (index) {
// return Container(
// color: Colors.blue,
// child: Text(
// '第${index + 1}项',
// style: TextStyle(color: Colors.white),
// ),
// alignment: Alignment.center,
// );
// }),
// ),
body: GridView.builder(
padding: EdgeInsets.all(10),
itemCount: 100,
// 按照列数固定
// gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
// crossAxisCount: 3,
// mainAxisSpacing: 10,
// crossAxisSpacing: 10,
// ),
// 按照宽度自适应
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 200,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
),
itemBuilder: (BuildContext context, int index) {
return Container(
color: Colors.blue,
child: Text(
'第${index + 1}项',
style: TextStyle(color: Colors.white),
),
alignment: Alignment.center,
);
},
),
),
);
}
}CustomScrollView
用于组合多个可滚动组件(列表、网格)、实现统一协调的滚动效果。
Flutter中描述可滚动视图内部一部分内容的组件,它是滚动视图的切片。
通过slivers属性接收一个Sliver组件列表。
Sliver组件对应的关系:
SliverList=>ListViewSliverGrid=>GridViewSliverAppBar=>AppBarSliverPadding=>paddingSliverToBoxAdapter=>ToBoxAdapter(用于包裹普通Widget)SliverPersistentHeader(粘性吸顶)
import 'package:flutter/material.dart';
void main() {
runApp(MainPage());
}
class MainPage extends StatefulWidget {
MainPage({Key? key}) : super(key: key);
@override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('CustomScrollView')),
body: CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: Container(
height: 260,
alignment: Alignment.center,
color: Colors.blue,
child: Text(
'轮播图',
style: TextStyle(color: Colors.white, fontSize: 30),
),
),
),
SliverToBoxAdapter(child: SizedBox(height: 10)),
SliverPersistentHeader(delegate: _StickCategory(), pinned: true),
SliverToBoxAdapter(child: SizedBox(height: 10)),
// SliverList.separated(
// itemCount: 100,
// itemBuilder: (BuildContext context, int index) {
// return Container(
// height: 100,
// alignment: Alignment.center,
// color: Colors.blue,
// child: Text(
// '列表${index + 1}',
// style: TextStyle(color: Colors.white, fontSize: 20),
// ),
// );
// },
// separatorBuilder: (BuildContext context, int index) {
// return SizedBox(height: 10);
// },
// ),
SliverGrid.count(
crossAxisCount: 2,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
children: List.generate(100, (index) {
return Container(
alignment: Alignment.center,
color: Colors.blue,
child: Text(
'列表${index + 1}',
style: TextStyle(color: Colors.white, fontSize: 20),
),
);
})
),
],
),
),
);
}
}
class _StickCategory extends SliverPersistentHeaderDelegate {
@override
Widget build(
BuildContext context,
double shrinkOffset,
bool overlapsContent,
) {
return Container(
color: Colors.white,
alignment: Alignment.center,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: 100,
itemBuilder: (context, index) {
return Container(
width: 100,
margin: EdgeInsets.symmetric(horizontal: 10),
alignment: Alignment.center,
color: Colors.blue,
child: Text(
'分类${index + 1}',
style: TextStyle(color: Colors.white, fontSize: 20),
),
);
},
),
);
}
@override
// TODO: implement maxExtent
double get maxExtent => 80;
@override
// TODO: implement minExtent
double get minExtent => 60;
@override
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
// TODO: implement shouldRebuild
return false;
}
}PageView
用于实现分页滚动容器视图的核心组件。
提供多种构建方式,默认构造方式、PageView.builder等。
import 'package:flutter/material.dart';
void main() {
runApp(MainPage());
}
class MainPage extends StatefulWidget {
MainPage({Key? key}) : super(key: key);
@override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
final PageController _pageController = PageController(initialPage: 0);
int _currentIndex = 0;
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('CustomScrollView')),
body: CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: Stack(
children: [
Container(
height: 260,
alignment: Alignment.center,
color: Colors.blue,
child: PageView.builder(
controller: _pageController,
itemCount: 10,
itemBuilder: (context, index) {
return Container(
height: 260,
alignment: Alignment.center,
child: Text(
'轮播图${index + 1}',
style: TextStyle(color: Colors.white, fontSize: 30),
),
);
},
),
),
Positioned(
bottom: 20,
left: 0,
right: 0,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(10, (index) {
return GestureDetector(
onTap: () {
_pageController.animateToPage(index, duration: Duration(milliseconds: 300), curve: Curves.easeInOut);
_currentIndex = index;
setState(() {});
},
child: Container(
width: 10,
height: 10,
margin: EdgeInsets.symmetric(horizontal: 10),
decoration: BoxDecoration(
color: _currentIndex == index ? Colors.red : Colors.white,
borderRadius: BorderRadius.circular(5),
),
),
);
}),
),
),
],
),
),
SliverToBoxAdapter(child: SizedBox(height: 10)),
SliverPersistentHeader(delegate: _StickCategory(), pinned: true),
SliverToBoxAdapter(child: SizedBox(height: 10)),
SliverGrid.count(
crossAxisCount: 2,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
children: List.generate(100, (index) {
return Container(
alignment: Alignment.center,
color: Colors.blue,
child: Text(
'列表${index + 1}',
style: TextStyle(color: Colors.white, fontSize: 20),
),
);
}),
),
],
),
),
);
}
}
class _StickCategory extends SliverPersistentHeaderDelegate {
@override
Widget build(
BuildContext context,
double shrinkOffset,
bool overlapsContent,
) {
return Container(
color: Colors.white,
alignment: Alignment.center,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: 100,
itemBuilder: (context, index) {
return Container(
width: 100,
margin: EdgeInsets.symmetric(horizontal: 10),
alignment: Alignment.center,
color: Colors.blue,
child: Text(
'分类${index + 1}',
style: TextStyle(color: Colors.white, fontSize: 20),
),
);
},
),
);
}
@override
// TODO: implement maxExtent
double get maxExtent => 80;
@override
// TODO: implement minExtent
double get minExtent => 60;
@override
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
// TODO: implement shouldRebuild
return false;
}
}事件
用户与应用程序交互时触发的各种动作,比如触摸屏幕、滑块、点击等。
常规方案:GestureDetector是Flutter中最常用、功能最丰富的手势检测组件。
import 'package:flutter/material.dart';
void main() {
runApp(MainPage());
}
class MainPage extends StatefulWidget {
MainPage({Key? key}) : super(key: key);
@override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Container(
child: Center(
child: GestureDetector(
onTap: () {
print('被点击了');
},
onDoubleTap: () {
print('被双击了');
},
child: Text('hello world'),
),
),
),
),
);
}
}Flutter提供了多种方式为组件添加点击交互。
-
专用按钮组件
ElevatedButton、TextButton、OutlineButton、FloatingActionButton内置点击动画和样式,通过
onPressed参数处理点击逻辑。 -
视觉反馈组件
InkWell提供点击事件
onTap,有MaterrialDesign风格的水纹扩散效果。 -
其它交互组件
IconButton、Switch、Checkbox具有特定功能的交互式控件、点击事件
onPressed。
import 'package:flutter/material.dart';
void main() {
runApp(MainPage());
}
class MainPage extends StatefulWidget {
MainPage({Key? key}) : super(key: key);
@override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Container(
child: Center(
child: TextButton(
onPressed: () {
print('被点击了2');
},
child: Text('hello world'),
),
),
),
),
);
}
}setState
状态更新
数据的变化需要更新UI图,需要执行setState方法,setState方法会造成build重新执行。
需求:计数器,点击加进行+1,点击减进行-1,UI视图需要进行相应的更新。
import 'package:flutter/material.dart';
void main() {
runApp(MainPage());
}
class MainPage extends StatefulWidget {
MainPage({Key? key}) : super(key: key);
@override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
int count = 0;
@override
Widget build(BuildContext context) {
return Container(
child: MaterialApp(
home: Scaffold(
body: Container(
child: Center(
child: Row(
children: [
TextButton(onPressed: () {
count += 1;
setState(() {});
}, child: Text('加')),
Text(count.toString()),
TextButton(onPressed: () {
count -= 1;
setState(() {});
}, child: Text('减')),
],
),
),
)
)
)
);
}
}组件通信
-
父传子
构造函数传递
简单的数据传递
-
子传父
回调函数
子组件通知父组件
-
祖先 => 后代
inheritedWidget跨层级数据共享
-
任意组件间
-
provider状态管理推荐方案
-
EventBus全局事件通信
-
Bloc/Riverpod复杂状态管理
-
父传子
子组件定义接收属性。
子组件在构造函数中接收参数。
父组件传递属性给子组件。
有状态组件在对外的类接收属性,对内的类通过widget对象获取对应属性。
注意:子组件定义接收属性需要使用final关键字,因为属性是由副组件决定,子组件不能随意更改。
需求:定义父组件,父组件传递一个message变量给子组件并显示。
import 'package:flutter/material.dart';
void main() {
runApp(MainPage());
}
class MainPage extends StatelessWidget {
const MainPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Container(
child: Column(
children: [
Text('父组件'),
Child(message: 'hello world'),
Child2(message: 'hello zs')
],
),
),
),
);
}
}
// 无状态子组件
class Child extends StatelessWidget {
final String? message;
const Child({Key? key, this.message}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(child: Text('子组件-${message}'));
}
}
// 有状态子组件
class Child2 extends StatefulWidget {
final String message;
Child2({Key? key, required this.message}) : super(key: key);
@override
_Child2State createState() => _Child2State();
}
class _Child2State extends State<Child2> {
@override
Widget build(BuildContext context) {
return Container(
child: Text('子组件2-${widget.message}'),
);
}
}练习
import 'package:flutter/material.dart';
void main() {
runApp(MainPage());
}
class MainPage extends StatefulWidget {
MainPage({Key? key}) : super(key: key);
@override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
List<String> _list = ['zs', 'ls', 'ww', 'zl'];
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: GridView.count(
crossAxisCount: 2,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
children: List.generate(_list.length, (index) {
return Child3(name: _list[index]);
}),
),
),
);
}
}
class Child3 extends StatefulWidget {
final String name;
Child3({Key? key, required this.name}) : super(key: key);
@override
_Child3State createState() => _Child3State();
}
class _Child3State extends State<Child3> {
@override
Widget build(BuildContext context) {
return Container(
child: Container(color: Colors.blue, child: Text('子组件3-${widget.name}')),
);
}
}子传父
父组件传递一个函数给子组件。
子组件调用该函数。
父组件通过回调函数获取参数。
需求:点击子组件删除父组件的菜品数据并更新列表。
import 'package:flutter/material.dart';
void main() {
runApp(MainPage());
}
class MainPage extends StatefulWidget {
MainPage({Key? key}) : super(key: key);
@override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
List<String> _list = ['zs', 'ls', 'ww', 'zl'];
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: GridView.count(
crossAxisCount: 2,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
children: List.generate(_list.length, (index) {
return Child3(name: _list[index], index: index, delName: (index) {
_list.removeAt(index);
setState(() {
});
} );
}),
),
),
);
}
}
class Child3 extends StatefulWidget {
final String name;
final int index;
final Function(int) delName;
Child3({Key? key, required this.name, required this.index, required this.delName}) : super(key: key);
@override
_Child3State createState() => _Child3State();
}
class _Child3State extends State<Child3> {
@override
Widget build(BuildContext context) {
return Stack(
fit: StackFit.expand,
children: [
Container(
child: Container(
color: Colors.blue,
alignment: Alignment.center,
child: Text('子组件3-${widget.name}', style: TextStyle(color: Colors.white, fontSize: 20)),
),
),
Positioned(
top: 10,
right: 10,
child: IconButton(onPressed: () {
widget.delName(widget.index);
}, icon: Icon(Icons.delete)),
),
],
);
}
}网络请求
网络请求是Flutter移动应用开发的核心功能,最常用的网络请求工具是Dio。
安装Dio
flutter pub add dioimport 'package:dio/dio.dart';
void main() {
Dio().get('https://randomuser.me/api/').then((value) {
print(value.data);
}).catchError((error) {
print(error);
});
}请求封装
import 'package:dio/dio.dart';
void main() {
}
// 封装工具类
class DioUtil {
final Dio _dio = Dio();
DioUtils() {
// 配置基础地址和超时时间
// _dio.options.baseUrl = 'https://randomuser.me/api/';
// _dio.options.connectTimeout = Duration(seconds: 10);
// _dio.options.receiveTimeout = Duration(seconds: 10);
// _dio.options.receiveTimeout = Duration(seconds: 10);
// 缩写形式
// https://geek.itheima.net/v1_0/channels
_dio.options
..baseUrl = 'https://geek.itheima.net/v1_0/'
..connectTimeout = Duration(seconds: 10)
..receiveTimeout = Duration(seconds: 10)
..contentType = 'application/json';
// 拦截器
_addInterceptors();
}
void _addInterceptors() {
// 添加请求拦截器
_dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) {
// 在请求发送前做些什么
return handler.next(options);
},
onResponse: (response, handler) {
// 对响应数据做点什么
if(response.statusCode! == 200 && response.statusCode! < 300) {
return handler.next(response);
}
return handler.reject(DioException(requestOptions: response.requestOptions));
},
onError: (error, handler) {
// 对响应错误做点什么
return handler.next(error);
},
));
}
get(String url, {Map<String, dynamic>? queryParameters}) async {
return await _dio.get(url, queryParameters: queryParameters);
}
}练习
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MainPage());
}
class MainPage extends StatefulWidget {
MainPage({Key? key}) : super(key: key);
@override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
@override
void initState() {
super.initState();
//发送请求
_getChannels();
}
List<Map<String, dynamic>> _list = [];
void _getChannels() async {
DioUtil util = DioUtil();
Response<dynamic> response = await util.get('channels');
Map<String, dynamic> data = response.data as Map<String, dynamic>;
List channels = data['data']['channels'] as List;
print(channels.cast<Map<String, dynamic>>() as List<Map<String, dynamic>>);
_list = channels.cast<Map<String, dynamic>>() as List<Map<String, dynamic>>;
setState(() {});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Flutter Base'),
),
body: GridView.extent(
maxCrossAxisExtent: 100,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
children: List.generate(_list.length, (index) {
return ChildName(item: _list[index]);
}),
),
)
);
}
}
class ChildName extends StatefulWidget {
ChildName({Key? key, required this.item}) : super(key: key);
final Map<String, dynamic> item;
@override
_ChildNameState createState() => _ChildNameState();
}
class _ChildNameState extends State<ChildName> {
@override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
color: Colors.blue,
child: Text('-${widget.item['name']}'),
);
}
}
// 封装工具类
class DioUtil {
final Dio _dio = Dio();
DioUtil() {
// 配置基础地址和超时时间
// _dio.options.baseUrl = 'https://randomuser.me/api/';
// _dio.options.connectTimeout = Duration(seconds: 10);
// _dio.options.receiveTimeout = Duration(seconds: 10);
// _dio.options.receiveTimeout = Duration(seconds: 10);
// 缩写形式
// https://geek.itheima.net/v1_0/channels
_dio.options
..baseUrl = 'https://geek.itheima.net/v1_0/'
..connectTimeout = Duration(seconds: 10)
..receiveTimeout = Duration(seconds: 10)
..contentType = 'application/json';
// 拦截器
_addInterceptors();
}
void _addInterceptors() {
// 添加请求拦截器
_dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) {
// 在请求发送前做些什么
return handler.next(options);
},
onResponse: (response, handler) {
// 对响应数据做点什么
if(response.statusCode! == 200 && response.statusCode! < 300) {
return handler.next(response);
}
return handler.reject(DioException(requestOptions: response.requestOptions));
},
onError: (error, handler) {
// 对响应错误做点什么
return handler.next(error);
},
));
}
Future<Response<dynamic>> get(String url, {Map<String, dynamic>? queryParameters}) async {
return await _dio.get(url, queryParameters: queryParameters);
}
}路由
路由管理是构建多页面应用的核心,它通过Navigator和Route来管理页面栈,实现页面跳转。
基本路由
基本路由适合页面不多、跳转逻辑简单的场景。
无法提前注册路由,跳转时创建MaterialPageRoute实例即可。
跳转新页面:Navigator.push(BuildContext context, Route route)
返回上一页:Navigator.pop(BuildContext context)
import 'package:flutter/material.dart';
void main() {
runApp(ListPage());
}
class ListPage extends StatefulWidget {
ListPage({Key? key}) : super(key: key);
@override
_ListPageState createState() => _ListPageState();
}
class _ListPageState extends State<ListPage> {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('列表页')),
body: ListView.builder(
itemCount: 100,
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (context) => DetailPage()));
print('点击了列表$index');
},
child: Container(
margin: EdgeInsets.only(top: 10),
height: 100,
alignment: Alignment.center,
color: Colors.blue,
child: Text('我是列表${index + 1}'),
),
);
},
),
),
);
}
}
class DetailPage extends StatefulWidget {
DetailPage({Key? key}) : super(key: key);
@override
_DetailPageState createState() => _DetailPageState();
}
class _DetailPageState extends State<DetailPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('详情页')),
body: Center(
child: TextButton(onPressed: () {
Navigator.pop(context);
}, child: Text('返回上一页')),
),
);
}
}基本路由传参
通过组件构造函数传递参数-父传子
通过组件构造函数接收参数-父传子
initState可获取到基础路由的构造函数的传参
import 'package:flutter/material.dart';
void main() {
runApp(ListPage());
}
class ListPage extends StatefulWidget {
ListPage({Key? key}) : super(key: key);
@override
_ListPageState createState() => _ListPageState();
}
class _ListPageState extends State<ListPage> {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('列表页')),
body: ListView.builder(
itemCount: 100,
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (context) => DetailPage(id: (index + 1).toString())));
print('点击了列表$index');
},
child: Container(
margin: EdgeInsets.only(top: 10),
height: 100,
alignment: Alignment.center,
color: Colors.blue,
child: Text('我是列表${index + 1}'),
),
);
},
),
),
);
}
}
class DetailPage extends StatefulWidget {
final String id;
DetailPage({Key? key, required this.id}) : super(key: key);
@override
_DetailPageState createState() => _DetailPageState();
}
class _DetailPageState extends State<DetailPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('详情页')),
body: Center(
child: TextButton(onPressed: () {
Navigator.pop(context);
}, child: Text('返回上一页-${widget.id}')),
),
);
}
}命名路由
应用页面增多后,使用命名路由可以提升代码可维护性。
用法:需要现在MaterialApp中注册一个路由表(routes)并设置initialRoute(首页)。
对比基本路由:
命名路由需要在MaterialApp的routes中预先注册路由表,适合中大型项目管理。
简单路由直接构建页面,更灵活,适合简单应用或快速原型开发。
-
pushNamed进入新页面
场景:常规页面跳转,如列表页跳转到详情页。
-
pushReplacementNamed替换当前页面
场景:登陆成功后跳转主页,并无法返回登陆页。
-
pushNamedAndRemoveUntil跳转新页面并清理栈
场景:退出登录后跳转登陆页,并清空所有历史页面。
-
popAndPushNamed返回并立即跳转新页面
场景:购物车结算后,返回商品列表并同时跳转到订单页。
-
popUntil连续返回直到条件满足
从设置页的深层级,一键返回到主设置页面。
import 'package:flutter/material.dart';
void main() {
runApp(MainPage());
}
class MainPage extends StatefulWidget {
MainPage({Key? key}) : super(key: key);
@override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
@override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/list',
routes: {
'/list': (context) => ListPage(),
'/detail': (context) => DetailPage(),
},
);
}
}
// 列表页
class ListPage extends StatefulWidget {
ListPage({Key? key}) : super(key: key);
@override
_ListPageState createState() => _ListPageState();
}
class _ListPageState extends State<ListPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('列表页')),
body: ListView.builder(
itemCount: 100,
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
Navigator.pushNamed(context, '/detail');
},
child: Container(
margin: EdgeInsets.only(top: 10),
height: 100,
alignment: Alignment.center,
color: Colors.blue,
child: Text('我是列表${index + 1}'),
),
);
},
),
);
}
}
// 详情页
class DetailPage extends StatefulWidget {
DetailPage({Key? key}) : super(key: key);
@override
_DetailPageState createState() => _DetailPageState();
}
class _DetailPageState extends State<DetailPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('详情页2')),
body: Center(
child: TextButton(onPressed: () {
// Navigator.popAndPushNamed(context, '/list');
Navigator.pop(context);
}, child: Text('返回上一页')),
),
);
}
}命名路由传参
通过路由传递参数实现页面间数据传递
命名路由:
传递参数:Navigator.pushNamed(context, 地址, arguments: {参数})
接收参数:ModalRoute.of(context)?.settings.arguments
接收时机:initState获取不到路由参数,放置在Flutter.microtask()中
import 'package:flutter/material.dart';
void main() {
runApp(MainPage());
}
class MainPage extends StatefulWidget {
MainPage({Key? key}) : super(key: key);
@override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
@override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/list',
routes: {
'/list': (context) => ListPage(),
'/detail': (context) => DetailPage(),
},
);
}
}
// 列表页
class ListPage extends StatefulWidget {
ListPage({Key? key}) : super(key: key);
@override
_ListPageState createState() => _ListPageState();
}
class _ListPageState extends State<ListPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('列表页')),
body: ListView.builder(
itemCount: 100,
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
Navigator.pushNamed(
context,
'/detail',
arguments: {"id": index + 1},
);
},
child: Container(
margin: EdgeInsets.only(top: 10),
height: 100,
alignment: Alignment.center,
color: Colors.blue,
child: Text('我是列表${index + 1}'),
),
);
},
),
);
}
}
// 详情页
class DetailPage extends StatefulWidget {
DetailPage({Key? key}) : super(key: key);
@override
_DetailPageState createState() => _DetailPageState();
}
class _DetailPageState extends State<DetailPage> {
@override
Widget build(BuildContext context) {
final args =
ModalRoute.of(context)?.settings.arguments as Map<String, dynamic>?;
final String? id = args?['id'].toString();
return Scaffold(
appBar: AppBar(title: Text('详情页2')),
body: Container(
width: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text('详情页-$id'),
TextButton(
onPressed: () {
Navigator.pushNamed(context, '/list');
},
child: Text('返回上一页'),
),
],
),
)
);
}
}高级路由控制
如果需要根据参数动态生成页面,或实现路由拦截,可以使用onGenerateRoute和onUnknownRoute。
import 'package:flutter/material.dart';
void main() {
runApp(MainPage());
}
class MainPage extends StatefulWidget {
const MainPage({super.key});
@override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
@override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/list',
routes: {
'/list': (context) => ListPage(),
},
onGenerateRoute: (settings) {
if (settings.name == '/detail') {
bool isLogin = false;
if(isLogin){
return MaterialPageRoute(builder: (context) => DetailPage());
}else{
return MaterialPageRoute(builder: (context) => LoginPage());
}
}
// return null;
print(settings.name);
},
onUnknownRoute: (settings) {
return MaterialPageRoute(builder: (context) => NotFound());
},
);
}
}
class ListPage extends StatefulWidget {
ListPage({Key? key}) : super(key: key);
@override
_ListPageState createState() => _ListPageState();
}
class _ListPageState extends State<ListPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('主页面')),
body: Center(
child: Column(
children: [
Text('这是主页面'),
TextButton(onPressed: () {
// Navigator.push(context, MaterialPageRoute(builder: (context) => DetailPage()));
Navigator.pushNamed(context, '/detail');
}, child: Text('点击跳转详情页')),
TextButton(onPressed: () {
Navigator.pushNamed(context, '/test');
}, child: Text('点击测试页')),
],
),
),
);
}
}
class DetailPage extends StatefulWidget {
DetailPage({Key? key}) : super(key: key);
@override
_DetailPageState createState() => _DetailPageState();
}
class _DetailPageState extends State<DetailPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('详情页')),
body: Center(
child: Text('这是详情页'),
),
);
}
}
// 登陆页
class LoginPage extends StatefulWidget {
LoginPage({Key? key}) : super(key: key);
@override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('登陆页')),
body: Center(
child: Text('这是登陆页'),
),
);
}
}
// 404
class NotFound extends StatelessWidget {
const NotFound({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('404')),
body: Center(
child: Image(
image: AssetImage('lib/images/test.png'),
),
),
);
}
}