Skip to Content
🎉🎉🎉欢迎来到我的空间🎉🎉🎉
前端Flutter

开篇

  1. 什么是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

⚠️ vardynamic 的区别

  • var编译时类型推导(确定后不可变)。
  • dynamic运行时动态类型(随时可变,且会关闭编译时的类型安全检查)。

常量

如果声明的变量在初始化后不允许被修改,应使用 finalconst

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 中,有三种数值类型:intdoublenum

区别:int整型数字、double小数、double可整型可小数。

// int、double、num int intVal = 10; double dVal = 10.0; num nVal = 1.0; print(intVal); print(dVal); print(nVal);

注意:

  • doubleint不能直接赋值。

  • 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 语言中,循环主要使用whilefor循环。

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()

事件队列:FutureFuture.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); } }

工程化

配置环境

  1. 克隆Flutter地址(目录不要带中文)。

    • GitHub

      https://github.com/flutter/flutter.git
    • 码云

      git clone -b stable https://gitee.com/mirrors/Flutter.git ~/GitHub/flutter-master
  2. 检查mac环境变量。

    echo $SHELL // /bin/zsh

    如果显示/bin/zsh说明.zshrc是配置文件,如果是/bin/bash说明.bash_profile是配置文件。

  3. 终端打开配置文件

    nano ~/.zshrc
  4. 添加环境变量

    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的地址来。

  5. 按下键盘上的 Control + O(字母 O,代表 WriteOut 写入)再按下**Enter**。

    按下键盘上的 Control + X(代表 Exit 退出)。

    重启配置

    source ~/.zshrc
  6. 验证是否成功

    flutter --version

    注意:第一次运行这个指令,会安装依赖,耐心等待安装。

  7. 诊断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公司推行的一套设计风格,有很多的设计规范,如颜色、文字排版、动画等。

目的:MaterialAndroidWebIOSHarmonyOS多个平台提供统一的交互和视觉体验。

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'), ), ), ), ); } }
  • 有状态组件

    创建阶段:

    createStateWidget初始化调用,创建State对象。

    initStateState对象插入Widget树立刻执行,仅执行一次。

    didChangeDependenciesinitState后立刻执行,当所依赖的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提供了丰富强大的布局组件来构建用户界面。

  • 基础容器

    ContainerCenterAlignPadding

    提供装饰、对齐、边距等基础样式和布局控制,是使用频率极高的组件。

  • 线性布局

    RowColumn

    在水平或垂直方向线性排列子组件,是构建界面的基础。

  • 弹性布局

    FlexExpandedFlexible

    按照比例分配剩余空间,实现自适应布局,常与RowColumn配合使用。

  • 层叠布局

    StackPositioned

    让子组件重叠堆叠,用于实现图片上叠加的文字、悬浮按钮等效果。

  • 流式布局

    WrapFlow

    当主轴空间不足时自动换行或换列,常用于标签、滤镜等动态宽高内容的排列。

  • 滚动布局

    ListViewGaidView

    提供可滚动的列表或网格视图,高效展示大量数据。

Container

Container是功能丰富的布局组件,是一个多功能组合容器。

可通过多种方式约束定义大小,有明确优先级规则。

优先级:明确宽高 > Container约束 > 父组件约束 > 自适应组件大小

  • 布局

    alignment

    控制其child(子组件)在容器内部的对齐方式。

  • 尺寸

    widthheightconstraints

    设置容器的宽度和高度/为容器设置更复杂的尺寸约束(如最小/最大宽度)。

  • 间距

    paddingmargin

    按照比例分配剩余空间,实现紫石英布局,常与RowColumn配合使用。

  • 装饰

    colordecoration

    为容器设置一个简单的背景颜色/为容器设置复杂的背景装饰。

  • 变换

    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

为其子组件添加内边距

  • padding

    EdgeInsetsGeometry

    必须。定义内边距的大小和方向,通常使用EdgeInsets类来设置。

  • child

    Widget

    需要被添加内边距的子组件。

补充:

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

用于垂直排列其子组件的核心布局容器。

  • mainAxisAlignment

    MainAxisAlignment

    控制子组件在主轴(垂直方向)上的排列方式,如顶部对齐、居中或均匀分布。

  • crossAxisAlignment

    CrossAxisAlignment

    控制子组件在交叉轴(水平方向)上的对齐方式,如左对齐、右对齐或拉伸填满。

  • mainAxisSize

    MainAxisSize

    决定Column本身在垂直方向上的策略:是占满所有可用空间(max),还是仅包裹子组件的内容(min)。

  • children

    List<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

用于水平排列其子组件的核心布局容器。

  • mainAxisAlignment

    MainAxisAlignment

    控制子组件在主轴(垂直方向)上的排列方式,如顶部对齐、居中或均匀分布。

  • crossAxisAlignment

    CrossAxisAlignment

    控制子组件在交叉轴(水平方向)上的对齐方式,如左对齐、右对齐或拉伸填满。

  • mainAxisSize

    MainAxisSize

    决定Column本身在垂直方向上的策略:是占满所有可用空间(max),还是仅包裹子组件的内容(min)。

  • children

    List<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

允许沿一个主轴(水平或垂直)排列其子组件,灵活地控制这些子组件在主轴上的尺寸比例和空间分配。

  • direction

    Axis.horizontalAxis.vertical

    主轴方向,决定子组件的排列方向。

  • mainAxisAlignment

    MainAxisAlignment

    子组件在主轴方向上的对齐方式。

  • crossAxisAlignment

    CrossAxisAlignment

    子组件在交叉轴方向上的对齐方式。

  • mainAxisSize

    MainAxisSize

    Flex容器自身在主轴上的尺寸策略。

Flex子组件经常使用ExpandedFlexible来控制空间分配。

ExpandedFlexible的区别: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

流式布局组件,当子组件在主轴方向上排列不下时,它会进行自动换行(或换列)。

  • direction

    Axis.horizontal(水平)、Axis.vertical(垂直)

    设置主轴方向,即排列方向。

  • spacing

    数值

    主轴方向上,子组件之间的间距。

  • runSpacing

    数值

    交叉轴方向上,行(或列)之间的间距。

  • alignment

    Wrapalignment

    子组件在主轴方向上的对齐方式。

  • runAlignment

    WrapAlignment

    交叉轴方向上的对齐方式。

注意:ColumnRowFlex内容超出均不会换行。

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 轴(深度方向)进行叠加排列。

  • alignment

    AlignmentGeometry

    控制非定位子组件在Stack内的对齐方式,默认左上角。

  • fit

    StackFit

    控制非定位子组件如何适应Stack的尺寸。

  • clipBehavior

    Clip

    控制子组件超出Stack边界时的裁剪方式。

  • children

    List<Widget>

    需要被层叠排列的子组件列表。

可以使用Stack配合Positioned对子组件进行精确定位控制。Positioned通过leftrighttopbottom来将子组件钉在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

    控制图片如何适应其显示区域,例如是否拉伸、裁剪或保持原比例。

  • alignment

    AlignmentGeometry

    图片在其显示区域内的对齐方式,如Alignment.center

  • repeat

    ImageRepeat

    当图片小于显示区域时,设置是否以及如何重复平铺图片。

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

只能包含一个子组件,如果滚动通过子组件,通常将其嵌套在ColumnRow组件中。

滚动方向:通过scrollDirection属性控制,默认为垂直方向(Axis.vertical),也可以设置为水平方向(Axis.horizontal

特点:一次性构建所有子组件,如果嵌套的ColumnRow中包含大量子项,可能会导致性能问题,建议使用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的基础上,额外提供了构建分割线的能力。

​ 接收itemBuilderseparatorBuilderitemcount三个属性。

采用按需加载(懒加载),只构建当前可见区域的列表项,极大提升长列表性能。

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.countGridView.extent功能,同时支持懒加载,只渲染可见区域。

GridView默认构造方式-(写起来最繁琐,很少使用)。

GridView.count-基于固定列数的网格布局(最常用之一)。

GridView.extent-基于固定子项最大宽度/高度的网格布局(最常用之二)。

GridView.builder-用于网格项数量巨大或动态生成的情况,需要接收gridDelegate布局属性。

gridDelegateSliverGridDelegateWithFixedCrossAxisCount:固定列数

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 => ListView
  • SliverGrid => GridView
  • SliverAppBar => AppBar
  • SliverPadding => padding
  • SliverToBoxAdapter => 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; } }

事件

用户与应用程序交互时触发的各种动作,比如触摸屏幕、滑块、点击等。

常规方案:GestureDetectorFlutter中最常用、功能最丰富的手势检测组件。

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提供了多种方式为组件添加点击交互。

  • 专用按钮组件

    ElevatedButtonTextButtonOutlineButtonFloatingActionButton

    内置点击动画和样式,通过onPressed参数处理点击逻辑。

  • 视觉反馈组件

    InkWell

    提供点击事件onTap,有MaterrialDesign风格的水纹扩散效果。

  • 其它交互组件

    IconButtonSwitchCheckbox

    具有特定功能的交互式控件、点击事件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 dio
import '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); } }

路由

路由管理是构建多页面应用的核心,它通过NavigatorRoute来管理页面栈,实现页面跳转。

基本路由

基本路由适合页面不多、跳转逻辑简单的场景。

无法提前注册路由,跳转时创建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(首页)。

对比基本路由:

​ 命名路由需要在MaterialApproutes中预先注册路由表,适合中大型项目管理。

​ 简单路由直接构建页面,更灵活,适合简单应用或快速原型开发。

  • 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('返回上一页'), ), ], ), ) ); } }

高级路由控制

如果需要根据参数动态生成页面,或实现路由拦截,可以使用onGenerateRouteonUnknownRoute

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'), ), ), ); } }