Flutter & Dart 学习指南
本指南旨在帮助你快速掌握 Dart 语言和 Flutter 框架的核心知识,从基础语法到实战开发,循序渐进。
一、Dart 语言基础
1. 变量
Dart 使用 var、final、const 和显式类型来声明变量。
// 使用 var,类型自动推断为 int
var age = 30;
// 使用表达式
var age1 = 20 + 5;
// 显式声明类型
String name = '张三';
int count = 10;
double price = 19.99;
bool isActive = true;
// dynamic:动态类型,可以赋任意类型的值
dynamic anything = 'hello';
anything = 123; // 不会报错
// Object:所有类型的基类
Object obj = 'world';
obj = 456; // 不会报错最佳实践:优先使用 var 让编译器推断类型;当需要明确表示变量不可变时使用 final 或 const。
2. 常量
// const:编译时常量,值在编译时确定
const pi = 3.14159;
const greeting = 'Hello';
const combined = '$greeting, Dart!'; // 可以拼接其他 const
// const 不能包含运行时才能确定的值
// const timestamp = DateTime.now(); // 错误!
// final:运行时常量,只能赋值一次
final currentTime = DateTime.now(); // 正确,运行时赋值
final List<int> numbers = [1, 2, 3];
// numbers = [4, 5, 6]; // 错误:不能重新赋值
numbers.add(4); // 正确:可以修改内容(集合本身不是常量)
// final 常量集合
final List<int> fixedNumbers = const [1, 2, 3];
// fixedNumbers.add(4); // 错误:集合也是不可变的| 特性 | const | final |
|---|---|---|
| 赋值时机 | 编译时 | 运行时 |
| 重新赋值 | 不允许 | 不允许 |
| 集合内容可修改 | 否 | 是(除非用 const []) |
| 内存优化 | 相同值共享内存 | 不共享 |
3. 空安全(Null Safety)
Dart 2.12+ 引入了空安全,默认所有类型不可为空。
dart中通过编译静态检查将运行时空指针提前暴露给开发者,减少线上崩溃
// 非空类型:不能为 null
String name = '张三';
// name = null; // 编译错误
// 可空类型:加 ? 表示可以为 null
String? nullableName;
nullableName = null; // 正确
nullableName = '李四'; // 也正确
// 断言非空:! 表示我确定这里不为 null
String sureName = nullableName!; // 如果 nullableName 为 null 会运行时错误
// 安全调用:?. 如果对象为 null 则返回 null
int? length = nullableName?.length;
// 空值合并:?? 提供默认值
String displayName = nullableName ?? '匿名用户';
// 空值合并赋值:??=
String? config;
config ??= 'default'; // 如果 config 为 null,赋值为 'default'
// 级联操作符:.. 可以链式调用,即使对象为 null 也可用 ?..
class Person {
String? name;
int? age;
void sayHello() => print('Hello, $name');
}
var person = Person()
..name = '王五'
..age = 25
..sayHello();| 可空类型 | ? | 声明可空变量 | String? x = null; |
| 安全访问 | ?. | 声明当前对象为null时候,跳过后续操作,返回null | x?.startWith(); x为null时候后面代码不执行 |
| 非空断言 | !. | 开发者保证变量非空,否则运行时崩溃 | x!.startWith(); x虽然可能为空,但是我保证在运行时不为空 |
| 空合并 | ?? | 左侧为null,返回右侧的值 | x ?? “Default” x为空,返回true,一般用于设置默认值 |
4.动态类型dynamic
- Dart中,dynamic用来声明动态类型变量
- 允许变量运行时自由改变类型,同时绕过编译时的静态检查、
void main(List<String> args) {
dynamic a = 10;
dynamic b = "Hello";
dynamic c = true;
print(a); // Output: 10
print(b); // Output: Hello
print(c); // Output: true
a = "World";
}
4.1 对比var
- Dynamic:运行时可以自由改变类型,无编译检查,方法和属性直接调用
- var:根据初始值进行推断类型,确定类型后无法改变,有编译检查,仅限推断的属性和方法
5. 内置数据类型
5.1 String(字符串)
// 单引号和双引号都可以
String s1 = '单引号字符串';
String s2 = "双引号字符串";
// 多行字符串
String multiline = '''
这是
多行
字符串
''';
// 字符串插值
String name = 'Dart';
int version = 3;
String singer = '林俊杰';
String str = '我喜欢$singer';
String str1 = 'I like ${singer+name}'
print('我正在学习 $name 版本 $version');
print('2 + 3 = ${2 + 3}'); // 表达式用 ${}
// 字符串拼接
String full = 'Hello' + ' ' + 'World';
// 或使用相邻字符串
String adjacent = 'Hello' ' ' 'World'; // 等同于 'Hello World'
// 常用方法
String text = ' Hello Dart ';
print(text.trim()); // 'Hello Dart'
print(text.toLowerCase()); // ' hello dart '
print(text.toUpperCase()); // ' HELLO DART '
print(text.contains('Dart')); // true
print(text.startsWith(' H')); // true
print(text.replaceAll('Dart', 'Flutter')); // ' Hello Flutter '
print(text.substring(2, 7)); // 'Hello'
print(text.split(' ')); // ['', '', 'Hello', 'Dart', '', '']
// raw 字符串:不转义
String path = r'C:\Users\name\file.txt'; // 输出: C:\Users\name\file.txt5.2 数值类型(num / int / double)
// int:整数
int age = 25;
int hex = 0x1A; // 十六进制,值为 26
int big = 1000000000000000000; // Dart 支持大整数
// double:浮点数
double price = 19.99;
double scientific = 1.5e3; // 1500.0
// num:int 和 double 的父类
num value = 10; // 可以是 int
value = 10.5; // 也可以是 double
// 常用操作
print(10 / 3); // 3.333... (double)
print(10 ~/ 3); // 3 (整除,int)
print(10 % 3); // 1 (取余)
print(10.isEven); // true
print(10.isOdd); // false
print((-10).abs()); // 10
print(3.14159.toStringAsFixed(2)); // '3.14'
print(int.parse('42')); // 42
print(double.parse('3.14')); // 3.14
print(42.toString()); // '42'注意:
int age = 25; double numb = 25.1; //不能直接赋值 numb = age;//会报错 numb = age.toDouble();//这样就能赋值 age = numb;//会报错 age = numb.toInt();
5.3 布尔类型(bool)
bool isReady = true;
bool isEmpty = false;
// 条件判断
if (isReady) {
print('准备好了');
}
// Dart 不支持隐式类型转换,以下会报错
// if (1) { ... } // 错误:int 不能作为 bool 使用
// if ('hello') { ... } // 错误
// 比较运算符
print(5 > 3); // true
print(5 == 5); // true
print(5 != 3); // true5.4 List(列表)
可以理解为数组
void main(List<String> args) {
List<String> latters = ["a", "b", "c"];
print("letters:$latters");
latters.add("d");
print("letters:$latters");
latters.addAll(["e", "f"]);
print("letters:$latters");
latters.insert(2, "g"); //在索引为2的位置插入g,原来的元素向后移动
print("letters:$latters");
latters.removeAt(2); //删除索引为2的元素
print("letters:$latters");
latters.remove("b"); //删除元素b
print("letters:$latters");
latters.removeLast(); //删除最后一个元素
print("letters:$latters");
latters.removeRange(1, 3); //删除索引为1到3的元素,结束索引不包含在删除范围内
print("letters:$latters");
}// 创建列表
var fruits = ['苹果', '香蕉', '橙子'];
List<int> numbers = [1, 2, 3, 4, 5];
// 使用泛型限定类型
var emptyList = <String>[];
// 常用操作
fruits.add('葡萄'); // 尾部添加元素
fruits.addAll(['西瓜', '梨']); // 尾部添加列表所有元素
fruits.insert(0, '芒果'); // 在指定位置插入
fruits.remove('香蕉'); // 移除指定元素
fruits.removeAt(0); // 移除指定索引
print(fruits.length); // 列表长度
print(fruits.isEmpty); // 是否为空
print(fruits.contains('苹果')); // 是否包含
// 展开操作符 ...
var moreFruits = ['草莓', ...fruits, '蓝莓'];
// 空感知展开 ...?
List<String>? nullableList;
var safeList = ['a', ...?nullableList]; // 如果为 null 则忽略
// 集合条件 if/for
var isVip = true;
var menu = [
'首页',
'商品',
if (isVip) 'VIP专区', // 条件包含
'我的',
];
var nums = [for (var i = 1; i <= 5; i++) i * 2]; // [2, 4, 6, 8, 10]
// 常用高阶方法
var evens = numbers.where((n) => n.isEven).toList(); // [2, 4]
var doubled = numbers.map((n) => n * 2).toList(); // [2, 4, 6, 8, 10]
var sum = numbers.reduce((a, b) => a + b); // 15
numbers.forEach((n) => print(n));
numbers.sort((a, b) => b.compareTo(a)); // 降序排序
// 生成固定长度列表
var fixed = List.filled(3, 'a'); // ['a', 'a', 'a']
// fixed.add('b'); // 错误:固定长度列表不能 add
// 生成可增长列表
var growable = List.generate(5, (index) => index * index); // [0, 1, 4, 9, 16]5.5 Map(映射/字典)
// 创建 Map
var scores = {
'张三': 90,
'李四': 85,
'王五': 92,
};
Map<String, int> ages = {'Alice': 25, 'Bob': 30};
// 操作
scores['赵六'] = 88; // 添加/修改
print(scores['张三']); // 90
print(scores['不存在']); // null
scores.remove('李四'); // 移除
print(scores.length); // 键值对数量
print(scores.keys); // 所有键
print(scores.values); // 所有值
print(scores.containsKey('王五')); // true
print(scores.containsValue(100)); // false
// 遍历
scores.forEach((name, score) {
print('$name: $score');
});
for (var entry in scores.entries) {
print('${entry.key} = ${entry.value}');
}
// 使用 putIfAbsent 实现缓存
var cache = <String, String>{};
var value = cache.putIfAbsent('key', () => 'expensive computation');
// 使用 map 方法转换
var greetings = scores.map((name, score) {
return MapEntry(name, '$name 得了 $score 分');
});5.6 Set(集合)
// Set:无序且不重复的元素集合
var uniqueNumbers = {1, 2, 3, 3, 3}; // {1, 2, 3}
Set<String> tags = {'flutter', 'dart', 'mobile'};
// 操作
tags.add('web');
tags.add('flutter'); // 重复,不会添加
tags.remove('dart');
print(tags.length);
print(tags.contains('flutter')); // true
// 集合运算
var a = {1, 2, 3};
var b = {2, 3, 4};
print(a.union(b)); // {1, 2, 3, 4}
print(a.intersection(b)); // {2, 3}
print(a.difference(b)); // {1}
// List 去转 Set
var unique = [1, 2, 2, 3, 3, 3].toSet().toList(); // [1, 2, 3]5.7 Record(记录,Dart 3.0+)
// Record:轻量级的匿名不可变数据结构
var record = ('Hello', 42, true);
print(record.$1); // 'Hello'
print(record.$2); // 42
print(record.$3); // true
// 命名字段
var user = (name: '张三', age: 25, isVip: true);
print(user.name); // '张三'
print(user.age); // 25
// 作为函数返回值(替代只能返回一个值的限制)
(String, int) getUserInfo() {
return ('李四', 30);
}
var (userName, userAge) = getUserInfo(); // 解构赋值
print('$userName 年龄 $userAge');
// 列表解构
var [first, second, ...rest] = [1, 2, 3, 4, 5];
// Map 解构
var {'name': n, 'age': a} = {'name': '王五', 'age': 28};6. 函数
6.1 基础函数
// 完整写法
int add(int a, int b) {
return a + b;
}
// 箭头函数(单行表达式)
int subtract(int a, int b) => a - b;
// 无返回值
void greet(String name) {
print('你好, $name!');
}
// 可选位置参数:用 [] 包裹
String formatDate(int year, int month, [int? day]) {
if (day != null) {
return '$year-$month-$day';
}
return '$year-$month';
}
formatDate(2024, 5); // '2024-5'
formatDate(2024, 5, 27); // '2024-5-27'
// 命名参数:更推荐使用,代码可读性更好
void createUser({
required String name,
required int age,
String? email,
bool isActive = true, // 提供默认值
}) {
print('创建用户: $name, 年龄: $age, 邮箱: $email, 激活: $isActive');
}
createUser(name: '张三', age: 25);
createUser(name: '李四', age: 30, email: 'li@example.com', isActive: false);
// 位置 + 命名参数混合
void paint(String color, {double? opacity, int thickness = 1}) {
// ...
}6.2 函数作为一等公民
// 函数可以赋值给变量
var multiply = (int a, int b) => a * b;
print(multiply(3, 4)); // 12
// 函数可以作为参数
void execute(int a, int b, int Function(int, int) operation) {
print(operation(a, b));
}
execute(10, 5, add); // 15
execute(10, 5, (a, b) => a * b); // 50
// 函数可以作为返回值
Function makeMultiplier(int factor) {
return (int value) => value * factor;
}
var triple = makeMultiplier(3);
print(triple(5)); // 15
// 闭包
Function makeCounter() {
int count = 0;
return () => ++count; // 捕获外部变量
}
var counter = makeCounter();
print(counter()); // 1
print(counter()); // 2
print(counter()); // 36.3 异步函数
// async/await:处理异步操作
Future<String> fetchUserData() async {
// 模拟网络延迟
await Future.delayed(Duration(seconds: 2));
return '用户数据加载完成';
}
// 使用
void main() async {
print('开始加载...');
var data = await fetchUserData();
print(data); // 2秒后输出
}
// Future.then 写法(不使用 await)
fetchUserData().then((data) {
print(data);
}).catchError((error) {
print('出错了: $error');
});
// 并行执行多个 Future
Future<void> loadMultipleData() async {
var futures = [
fetchUserData(),
fetchUserData(),
fetchUserData(),
];
var results = await Future.wait(futures); // 等待全部完成
print(results); // ['用户数据加载完成', '用户数据加载完成', '用户数据加载完成']
}
// Stream:处理连续的异步事件
Stream<int> countStream(int max) async* {
for (int i = 1; i <= max; i++) {
await Future.delayed(Duration(milliseconds: 500));
yield i; // 产生一个值
}
}
// 监听 Stream
void main() async {
await for (var count in countStream(5)) {
print(count); // 每隔 500ms 输出 1, 2, 3, 4, 5
}
}6.4 必传参数&可传参数
必传
void main(){
add(1,2);
}
int add(int a,int b){
return a+b;
}可传
可选位置参数
- 必须在必传参数后面,采用中括号包裹
- 语法:函数名(String a,[String? b,…]) 传递时候按照顺序传递
void main() {
print(combine('Hello', 'World'));
print(combine('Hello'));
}
String combine(String a, [String? b]) {
return a + (b ?? '');
}可选命名参数
void main() {
// 可选命名参数
print(combine2("张三"));//{name: 张三, sex: 男, age: 0}
print(combine2("李四", sex: "男", age: 18));//{name: 李四, sex: 男, age: 18}
}
Map<String, dynamic> combine2(String name, {String? sex = "男", int? age = 0}) {
return {"name": name, "sex": sex, "age": age};
}6.5 匿名函数
Function test = (){};//后面的是匿名函数,然后将匿名函数赋值给test这个变量6.6 箭头函数
()=> a+b;// 匿名箭头函数
int add(int a,int b)=> a+b;//箭头函数7. 面向对象编程
7.1 类与对象
class Person {
// 实例变量(属性)
String name;
int age;
// 构造方法
Person(this.name, this.age); // 简写形式
// 命名构造方法
Person.guest()
: name = '游客',
age = 0;
// 工厂构造方法
factory Person.fromJson(Map<String, dynamic> json) {
return Person(json['name'], json['age']);
}
// 方法
void sayHello() {
print('你好,我是 $name,今年 $age 岁');
}
// Getter 和 Setter
String get info => '$name ($age岁)';
set birthday(int year) => age = DateTime.now().year - year;
// 重写 toString
@override
String toString() => 'Person(name: $name, age: $age)';
}
// 使用
void main() {
var person = Person('张三', 25);
person.sayHello();
print(person.info); // 张三 (25岁)
var guest = Person.guest();
guest.sayHello(); // 你好,我是 游客,今年 0 岁
var fromJson = Person.fromJson({'name': '李四', 'age': 30});
print(fromJson); // Person(name: 李四, age: 30)
}7.2 继承
class Animal {
String name;
Animal(this.name);
void speak() => print('$name 发出声音');
}
class Dog extends Animal {
String breed;
// 调用父类构造方法
Dog(String name, this.breed) : super(name);
@override
void speak() => print('$name 汪汪叫');
void fetch() => print('$name 去捡球了');
}
var dog = Dog('旺财', '金毛');
dog.speak(); // 旺财 汪汪叫
dog.fetch(); // 旺财 去捡球了7.3 抽象类与接口
// 抽象类:不能直接实例化
abstract class Shape {
double get area; // 抽象方法(没有实现)
void describe(); // 抽象方法
// 普通方法也可以有实现
void printArea() {
print('面积: $area');
}
}
// Dart 中没有 interface 关键字,类本身就是接口
// 使用 implements 实现接口(必须实现所有成员)
class Circle implements Shape {
double radius;
Circle(this.radius);
@override
double get area => 3.14 * radius * radius;
@override
void describe() => print('这是一个圆,半径 $radius');
@override
void printArea() => print('圆面积: $area');
}
// 使用 extends 继承抽象类(只需实现抽象方法)
class Rectangle extends Shape {
double width, height;
Rectangle(this.width, this.height);
@override
double get area => width * height;
@override
void describe() => print('矩形 ${width}x$height');
}
var circle = Circle(5);
circle.describe();
circle.printArea(); // 圆面积: 78.57.4 Mixin(混入)
在不使用传统继承的情况下,向类中添加新功能
-
使用mixin定义一个新对象
-
使用with关键字将定义的对象**
混入**到当前对象 -
一个类支持
with多个mixin,调用优先级遵循**后来居上,栈的原则,即先混入会被后混入覆盖**
// Mixin:在多个类层次结构中复用代码
mixin Flyable {
void fly() => print('我在飞!');
}
mixin Swimmable {
void swim() => print('我在游泳!');
}
// 使用 with 关键字混入
class Duck extends Animal with Flyable, Swimmable {
Duck(String name) : super(name);
}
var duck = Duck('唐老鸭');
duck.speak(); // 唐老鸭 发出声音
duck.fly(); // 我在飞!
duck.swim(); // 我在游泳!
// 限制 Mixin 的使用范围
mixin Runnable on Animal {
void run() => print('$name 在奔跑');
}7.5 扩展方法(Extension)
// 给现有类型添加新方法
extension StringExtension on String {
String get capitalize {
if (isEmpty) return this;
return '${this[0].toUpperCase()}${substring(1)}';
}
String repeat(int times) {
return List.filled(times, this).join();
}
}
// 使用
print('hello'.capitalize); // 'Hello'
print('abc '.repeat(3)); // 'abc abc abc '
// 扩展 int
extension IntExtension on int {
Duration get seconds => Duration(seconds: this);
Duration get minutes => Duration(minutes: this);
}
print(5.seconds); // Duration 0:00:05
print(10.minutes); // Duration 0:10:007.6 多态
- Dart中的类是指同一个错做作用于不同对象,可以产生不同效果
实现:
- 继承
- 抽象类与接口
8. 枚举与模式匹配
// 增强枚举(Dart 2.17+)
enum Status {
pending('待处理', Icons.hourglass_empty),
processing('处理中', Icons.sync),
completed('已完成', Icons.check_circle),
failed('失败', Icons.error);
final String label;
final IconData icon;
const Status(this.label, this.icon);
bool get isDone => this == Status.completed;
}
// 使用
var status = Status.processing;
print(status.label); // '处理中'
print(status.isDone); // false
// 模式匹配(Dart 3.0+)
String describe(Status status) {
return switch (status) {
Status.pending => '请耐心等待',
Status.processing => '正在努力处理中',
Status.completed => '已经完成啦!',
Status.failed => '出错了,请重试',
};
}
// 密封类(Sealed Class)用于穷尽性检查
sealed class Result<T> {}
class Success<T> extends Result<T> {
final T data;
Success(this.data);
}
class Error<T> extends Result<T> {
final String message;
Error(this.message);
}
// switch 会强制处理所有子类型
String handleResult(Result<int> result) {
return switch (result) {
Success(data: var d) => '成功: $d',
Error(message: var m) => '错误: $m',
};
}9. 泛型
- Dart允许使用类型参数,限定类型的同时又让类型更加灵活,让代码更加简装和维护性更强
// 泛型类
class Box<T> {
T value;
Box(this.value);
T getValue() => value;
}
var intBox = Box<int>(42);
var stringBox = Box<String>('hello');
// 泛型方法
T first<T>(List<T> list) => list.first;
// 泛型约束
class ComparableBox<T extends Comparable<T>> {
T a, b;
ComparableBox(this.a, this.b);
T get max => a.compareTo(b) > 0 ? a : b;
}
var numBox = ComparableBox<int>(10, 20);
print(numBox.max); // 20
// 常用泛型类型别名
typealias JsonMap = Map<String, dynamic>;10. 异常处理
// 抛出异常
void checkAge(int age) {
if (age < 0) {
throw ArgumentError('年龄不能为负数');
}
if (age < 18) {
throw Exception('未成年人禁止访问');
}
}
// 捕获异常
try {
checkAge(15);
} on ArgumentError catch (e) {
print('参数错误: ${e.message}');
} on Exception catch (e) {
print('其他异常: $e');
} catch (e, stackTrace) {
// 捕获所有异常,同时获取堆栈
print('异常: $e');
print('堆栈: $stackTrace');
} finally {
// 无论是否异常都会执行
print('清理工作');
}
// 自定义异常
class NetworkException implements Exception {
final int statusCode;
final String message;
NetworkException(this.statusCode, this.message);
@override
String toString() => 'NetworkException($statusCode): $message';
}11. 运算符
11.1 算数运算符
| 运算符 | 作用 |
|---|---|
| + | 加 |
| - | 减 |
| * | 乘 |
| / | 除 |
| ~/ | 整除 |
| % | 取余数 |
11.2 赋值运算符
= /= += -= */
11.3 逻辑运算符
| && | 与 |
|---|---|
| || | 或 |
| ! | 非 |
参与变量必须为bool
11.4 比较运算符
| == | 是否相等 |
|---|---|
| != | 是否不等 |
| > | 大于 |
| >= | 大于等于 |
< | 小于 |
<= | 小于等于 |
12. 循环
12.1 for
void main(List<String> args) {
for (int i = 0; i < 5; i++) {
print(i);
}
}12.2 while
void main(){
var page = [1,2,3,4,5];
int index = 0;
while(index < 5){
print(page[index]);
}
}13. 异步编程
Dart是单线程语言,所以异步编程很有必要
- 单线程+事件循环机制完成耗时任务处理
13.1 事件循环
同步->微任务队列->事件队列->结束
- 和浏览器的事件循环机制很相似
微任务队列:Future.microtask()
事件队列:Future、Future.delay()、I/O操作(文件、网络)
13.2 Future
| Uncompleted(等待) | Pending |
|---|---|
| Completed with a value | Fulfilled |
| Completed with a error | Rejected |
Future((){
}).then((){
throw Exception
})二、Flutter 框架基础
1. Widget 核心概念
Widget 是 Flutter 中一切的基础,是描述 UI 配置的不变对象。
// StatelessWidget:无状态,UI 只依赖传入的配置
class MyText extends StatelessWidget {
final String content;
// 使用 key 帮助 Flutter 识别 widget
const MyText(this.content, {super.key});
@override
Widget build(BuildContext context) {
// build 方法描述 UI 结构
return Text(content);
}
}
// StatefulWidget:有状态,UI 可以随时间变化
class Counter extends StatefulWidget {
const Counter({super.key});
@override
State<Counter> createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int _count = 0;
void _increment() {
// setState 通知 Flutter 状态变化,需要重建 UI
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: _increment,
child: Text('点击次数: $_count'),
);
}
}选择原则:
- 如果 Widget 不需要变化 → 用
StatelessWidget - 如果 Widget 需要响应交互或数据变化 → 用
StatefulWidget
2. 基础 Widget 速查
2.1 文本与样式
Text(
'Hello Flutter',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.blue,
letterSpacing: 1.2,
height: 1.5,
),
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
)
// 富文本
RichText(
text: TextSpan(
style: TextStyle(color: Colors.black, fontSize: 16),
children: [
TextSpan(text: '同意 '),
TextSpan(
text:《用户协议》,
style: TextStyle(color: Colors.blue),
recognizer: TapGestureRecognizer()..onTap = () => openUrl(),
),
],
),
)2.2 容器与装饰
Container(
width: 200,
height: 100,
// 内边距
padding: EdgeInsets.all(16),
// 外边距
margin: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
// 装饰
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey.shade300),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 8,
offset: Offset(0, 4),
),
],
gradient: LinearGradient(
colors: [Colors.blue, Colors.purple],
),
),
// 子元素对齐
alignment: Alignment.center,
child: Text('内容'),
)
// 快捷方式
Padding(
padding: EdgeInsets.all(16),
child: Text('有内边距的文本'),
)
Center(child: Text('居中'))
Align(
alignment: Alignment.bottomRight,
child: Text('右下角'),
)
// SizedBox 用于固定尺寸或留白
SizedBox(width: 20, height: 20) // 固定尺寸
SizedBox.shrink() // 尽可能小
SizedBox.expand() // 尽可能大2.3 图片
// 本地图片(需要在 pubspec.yaml 中配置 assets)
Image.asset('assets/images/logo.png')
// 网络图片
Image.network(
'https://example.com/image.jpg',
width: 200,
height: 200,
fit: BoxFit.cover, // 裁剪填充
loadingBuilder: (context, child, progress) {
if (progress == null) return child;
return CircularProgressIndicator();
},
errorBuilder: (context, error, stack) {
return Icon(Icons.error);
},
)
// 圆形头像
CircleAvatar(
radius: 40,
backgroundImage: NetworkImage('https://example.com/avatar.jpg'),
child: Text('AB'), // 作为 fallback
)
// Icon
Icon(Icons.favorite, color: Colors.red, size: 32)
Icon(Icons.settings, color: Theme.of(context).primaryColor)2.4 按钮
// 填充按钮
ElevatedButton(
onPressed: () => print('点击'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
child: Text('确认'),
)
// 文本按钮
TextButton(
onPressed: () {},
child: Text('取消'),
)
// 轮廓按钮
OutlinedButton(
onPressed: () {},
child: Text('选择'),
)
// 图标按钮
IconButton(
onPressed: () {},
icon: Icon(Icons.delete),
tooltip: '删除',
)
// 带图标的按钮
ElevatedButton.icon(
onPressed: () {},
icon: Icon(Icons.add),
label: Text('新增'),
)
// Floating Action Button
FloatingActionButton(
onPressed: () {},
child: Icon(Icons.add),
)2.5 输入框
TextField(
controller: _textController, // TextEditingController
decoration: InputDecoration(
labelText: '用户名',
hintText: '请输入用户名',
prefixIcon: Icon(Icons.person),
suffixIcon: IconButton(
icon: Icon(Icons.clear),
onPressed: () => _textController.clear(),
),
border: OutlineInputBorder(),
filled: true,
fillColor: Colors.grey.shade100,
),
keyboardType: TextInputType.emailAddress,
obscureText: false, // true 时隐藏输入(密码)
maxLength: 20,
onChanged: (value) => print('输入: $value'),
onSubmitted: (value) => print('提交: $value'),
)
// 表单中使用
TextFormField(
validator: (value) {
if (value == null || value.isEmpty) return '不能为空';
if (value.length < 6) return '至少6个字符';
return null; // 验证通过
},
)
// Controller 使用
class _MyFormState extends State<MyForm> {
final _controller = TextEditingController();
@override
void dispose() {
_controller.dispose(); // 记得释放!
super.dispose();
}
}3. 布局 Widget
3.1 线性布局(Row / Column)
// Row:水平排列
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, // 主轴对齐
crossAxisAlignment: CrossAxisAlignment.center, // 交叉轴对齐
children: [
Icon(Icons.home),
Text('首页'),
Spacer(), // 占据剩余空间
Icon(Icons.arrow_forward),
],
)
// Column:垂直排列
Column(
mainAxisSize: MainAxisSize.min, // 最小高度
children: [
Text('标题', style: TextStyle(fontSize: 20)),
SizedBox(height: 8), // 间距
Text('描述内容'),
],
)
// MainAxisAlignment 选项:
// start(默认)、end、center、spaceBetween、spaceAround、spaceEvenly
// CrossAxisAlignment 选项:
// start、end、center、stretch、baseline3.2 弹性布局(Expanded / Flexible)
Row(
children: [
// Expanded:占据所有剩余空间
Expanded(
child: Container(color: Colors.red),
),
// Flexible:按 flex 比例分配
Flexible(
flex: 2, // 占 2 份
child: Container(color: Colors.green),
),
Flexible(
flex: 1, // 占 1 份
child: Container(color: Colors.blue),
),
],
)
// 红色占剩余空间,绿色和蓝色按 2:1 分配3.3 层叠布局(Stack)
Stack(
alignment: Alignment.center,
children: [
// 底层
Image.network('https://example.com/bg.jpg'),
// 上层
Positioned( // 绝对定位
bottom: 16,
left: 16,
child: Text('底部文字', style: TextStyle(color: Colors.white)),
),
Positioned.fill( // 填满整个 Stack
child: Container(
color: Colors.black.withOpacity(0.3),
),
),
],
)3.4 滚动布局
// ListView:列表滚动
ListView(
padding: EdgeInsets.all(16),
children: [
ListTile(title: Text('项目 1')),
ListTile(title: Text('项目 2')),
ListTile(title: Text('项目 3')),
],
)
// 长列表优化:builder 模式只构建可见项
ListView.builder(
itemCount: 1000,
itemBuilder: (context, index) {
return ListTile(
title: Text('项目 $index'),
subtitle: Text('描述'),
leading: Icon(Icons.folder),
trailing: Icon(Icons.chevron_right),
onTap: () => print('点击 $index'),
);
},
)
// 分隔线列表
ListView.separated(
itemCount: 20,
itemBuilder: (context, index) => ListTile(title: Text('Item $index')),
separatorBuilder: (context, index) => Divider(height: 1),
)
// 网格
GridView.count(
crossAxisCount: 2, // 两列
crossAxisSpacing: 10, // 列间距
mainAxisSpacing: 10, // 行间距
children: List.generate(6, (i) => Container(color: Colors.blue)),
)
// 网格 builder(适合大量数据)
GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: 1, // 宽高比
),
itemCount: 100,
itemBuilder: (context, index) => Card(child: Text('$index')),
)
// 自定义滚动 + 吸顶
CustomScrollView(
slivers: [
SliverAppBar(
title: Text('可折叠标题'),
floating: true,
expandedHeight: 200,
flexibleSpace: FlexibleSpaceBar(
background: Image.asset('assets/banner.jpg', fit: BoxFit.cover),
),
),
SliverToBoxAdapter(child: Text('普通内容')),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => ListTile(title: Text('Item $index')),
childCount: 50,
),
),
],
)
// 下拉刷新 + 上拉加载
RefreshIndicator(
onRefresh: () async => await loadData(),
child: ListView(...),
)3.5 其他常用布局
// Wrap:自动换行
Wrap(
spacing: 8, // 水平间距
runSpacing: 8, // 垂直间距
children: [
Chip(label: Text('标签1')),
Chip(label: Text('标签2')),
Chip(label: Text('标签3')),
// ... 超出宽度自动换行
],
)
// 比例布局
AspectRatio(
aspectRatio: 16 / 9,
child: Container(color: Colors.blue),
)
// FractionallySizedBox:百分比尺寸
FractionallySizedBox(
widthFactor: 0.8, // 80% 宽度
child: ElevatedButton(onPressed: () {}, child: Text('宽按钮')),
)
// LayoutBuilder:根据父容器尺寸自适应
LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth > 600) {
return Row(children: [...]); // 宽屏布局
}
return Column(children: [...]); // 窄屏布局
},
)
// OrientationBuilder:根据屏幕方向调整
OrientationBuilder(
builder: (context, orientation) {
return GridView.count(
crossAxisCount: orientation == Orientation.portrait ? 2 : 4,
);
},
)4. 导航与路由
4.1 基础导航
// 页面跳转
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondPage()),
);
// 带返回值的跳转
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => SelectionPage()),
);
print('返回结果: $result');
// 返回上一页
Navigator.pop(context);
// 返回并传值
Navigator.pop(context, '选中的数据');
// 替换当前页面(无法返回)
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => HomePage()),
);
// 清空栈并跳转到新页面
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => LoginPage()),
(route) => false, // 保留判断为 true 的页面
);4.2 命名路由
// main.dart 中配置
MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => HomePage(),
'/detail': (context) => DetailPage(),
'/settings': (context) => SettingsPage(),
},
// 动态路由传参
onGenerateRoute: (settings) {
if (settings.name == '/profile') {
final args = settings.arguments as Map<String, dynamic>;
return MaterialPageRoute(
builder: (context) => ProfilePage(userId: args['userId']),
);
}
return null;
},
)
// 跳转
Navigator.pushNamed(context, '/detail');
Navigator.pushNamed(context, '/profile', arguments: {'userId': '123'});4.3 推荐:使用 go_router
# pubspec.yaml
dependencies:
go_router: ^13.0.0// router.dart
import 'package:go_router/go_router.dart';
final router = GoRouter(
initialLocation: '/',
routes: [
GoRoute(
path: '/',
builder: (context, state) => HomePage(),
),
GoRoute(
path: '/detail/:id', // 路径参数
builder: (context, state) {
final id = state.pathParameters['id']!;
return DetailPage(id: id);
},
),
GoRoute(
path: '/profile',
builder: (context, state) {
final extra = state.extra as Map<String, dynamic>?;
return ProfilePage(data: extra);
},
),
],
);
// main.dart
MaterialApp.router(
routerConfig: router,
)
// 跳转
textButton(
onPressed: () => context.go('/'), // 直接跳转,替换当前
child: Text('首页'),
)
textButton(
onPressed: () => context.push('/detail/123'), // 压栈,可返回
child: Text('详情'),
)
textButton(
onPressed: () => context.pop(), // 返回
child: Text('返回'),
)5. 状态管理
5.1 基础方案:setState
适合简单的局部状态:
class LikeButton extends StatefulWidget {
const LikeButton({super.key});
@override
State<LikeButton> createState() => _LikeButtonState();
}
class _LikeButtonState extends State<LikeButton> {
bool _isLiked = false;
@override
Widget build(BuildContext context) {
return IconButton(
icon: Icon(_isLiked ? Icons.favorite : Icons.favorite_border),
color: _isLiked ? Colors.red : null,
onPressed: () => setState(() => _isLiked = !_isLiked),
);
}
}5.2 推荐:Riverpod
# pubspec.yaml
dependencies:
flutter_riverpod: ^2.4.0// main.dart
void main() {
runApp(ProviderScope(child: MyApp())); // 用 ProviderScope 包裹
}
// 定义 Provider
// counter_provider.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
// StateProvider:简单状态
final counterProvider = StateProvider<int>((ref) => 0);
// StateNotifierProvider:复杂状态逻辑
final todoListProvider = StateNotifierProvider<TodoListNotifier, List<Todo>>((ref) {
return TodoListNotifier();
});
class TodoListNotifier extends StateNotifier<List<Todo>> {
TodoListNotifier() : super([]);
void addTodo(String title) {
state = [...state, Todo(id: DateTime.now().toString(), title: title)];
}
void toggleTodo(String id) {
state = state.map((todo) {
if (todo.id == id) return todo.copyWith(isDone: !todo.isDone);
return todo;
}).toList();
}
void removeTodo(String id) {
state = state.where((todo) => todo.id != id).toList();
}
}
// 使用 ConsumerWidget
class CounterPage extends ConsumerWidget {
const CounterPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Scaffold(
appBar: AppBar(title: Text('计数器')),
body: Center(child: Text('$count', style: TextStyle(fontSize: 48))),
floatingActionButton: FloatingActionButton(
onPressed: () => ref.read(counterProvider.notifier).state++,
child: Icon(Icons.add),
),
);
}
}
// 局部监听使用 Consumer
class SomeWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Consumer(
builder: (context, ref, child) {
final count = ref.watch(counterProvider);
return Text('$count');
},
);
}
}
// AsyncValue:处理异步状态
final userProvider = FutureProvider<User>((ref) async {
final response = await dio.get('/user');
return User.fromJson(response.data);
});
// 使用
class UserProfile extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final userAsync = ref.watch(userProvider);
return userAsync.when(
data: (user) => Text(user.name),
loading: () => CircularProgressIndicator(),
error: (err, stack) => Text('错误: $err'),
);
}
}5.3 Bloc/Cubit 模式
适合大型项目:
# pubspec.yaml
dependencies:
flutter_bloc: ^8.1.0// State
typedef CounterState = int;
// Cubit
class CounterCubit extends Cubit<CounterState> {
CounterCubit() : super(0);
void increment() => emit(state + 1);
void decrement() => emit(state - 1);
}
// 使用
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => CounterCubit(),
child: CounterView(),
);
}
}
class CounterView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: BlocBuilder<CounterCubit, CounterState>(
builder: (context, count) {
return Center(child: Text('$count'));
},
),
floatingActionButton: FloatingActionButton(
onPressed: () => context.read<CounterCubit>().increment(),
child: Icon(Icons.add),
),
);
}
}状态管理选择指南:
| 方案 | 适用场景 | 复杂度 |
|---|---|---|
setState | 单个 Widget 的简单状态 | 低 |
ValueNotifier | 局部共享状态,不需要重建大量 Widget | 低 |
Riverpod | 中大型应用,异步数据,依赖注入 | 中 |
Bloc/Cubit | 大型应用,严格的分层架构,复杂业务逻辑 | 中高 |
GetX | 快速原型,但生产环境慎用 | 低 |
6. 网络请求
6.1 使用 Dio(推荐)
# pubspec.yaml
dependencies:
dio: ^5.4.0import 'package:dio/dio.dart';
class ApiService {
late final Dio _dio;
ApiService() {
_dio = Dio(BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: Duration(seconds: 10),
receiveTimeout: Duration(seconds: 10),
headers: {'Content-Type': 'application/json'},
));
// 添加拦截器
_dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) {
// 添加认证 Token
options.headers['Authorization'] = 'Bearer $token';
print('请求: ${options.method} ${options.path}');
handler.next(options);
},
onResponse: (response, handler) {
print('响应: ${response.statusCode}');
handler.next(response);
},
onError: (error, handler) {
print('错误: ${error.message}');
handler.next(error);
},
));
}
Future<List<User>> getUsers() async {
final response = await _dio.get('/users');
return (response.data as List)
.map((json) => User.fromJson(json))
.toList();
}
Future<User> getUser(String id) async {
final response = await _dio.get('/users/$id');
return User.fromJson(response.data);
}
Future<User> createUser(CreateUserDto dto) async {
final response = await _dio.post('/users', data: dto.toJson());
return User.fromJson(response.data);
}
}
// 数据模型
class User {
final String id;
final String name;
final String email;
User({required this.id, required this.name, required this.email});
factory User.fromJson(Map<String, dynamic> json) => User(
id: json['id'],
name: json['name'],
email: json['email'],
);
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
'email': email,
};
}6.2 使用 http 包(轻量)
dependencies:
http: ^1.1.0import 'package:http/http.dart' as http;
import 'dart:convert';
Future<void> fetchData() async {
final response = await http.get(Uri.parse('https://api.example.com/data'));
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
print(data);
} else {
throw Exception('请求失败: ${response.statusCode}');
}
}
// POST 请求
Future<void> postData() async {
final response = await http.post(
Uri.parse('https://api.example.com/data'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({'name': '张三', 'age': 25}),
);
}7. 本地存储
# pubspec.yaml
dependencies:
shared_preferences: ^2.2.0 # 简单键值存储
sqflite: ^2.3.0 # SQLite 数据库
hive: ^2.2.0 # 高性能本地存储
path_provider: ^2.1.0 # 获取文件路径7.1 SharedPreferences
import 'package:shared_preferences/shared_preferences.dart';
class LocalStorage {
static late SharedPreferences _prefs;
static Future<void> init() async {
_prefs = await SharedPreferences.getInstance();
}
static Future<void> setString(String key, String value) async {
await _prefs.setString(key, value);
}
static String? getString(String key) => _prefs.getString(key);
static Future<void> setBool(String key, bool value) async {
await _prefs.setBool(key, value);
}
static Future<void> remove(String key) async {
await _prefs.remove(key);
}
}
// 使用
await LocalStorage.setString('token', 'abc123');
final token = LocalStorage.getString('token');7.2 Hive
import 'package:hive_flutter/hive_flutter.dart';
// 初始化
await Hive.initFlutter();
// 打开 Box(类似于表)
var box = await Hive.openBox('settings');
// 存储
await box.put('theme', 'dark');
await box.put('user', {'name': '张三', 'age': 25});
// 读取
var theme = box.get('theme', defaultValue: 'light');
var user = box.get('user');
// 监听变化
box.watch().listen((event) {
print('Key ${event.key} changed');
});8. 主题与国际化
8.1 主题配置
MaterialApp(
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
textTheme: TextTheme(
headlineLarge: TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
bodyLarge: TextStyle(fontSize: 16),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
),
),
darkTheme: ThemeData.dark(useMaterial3: true),
themeMode: ThemeMode.system, // 跟随系统
)
// 在 Widget 中使用主题
Text('标题', style: Theme.of(context).textTheme.headlineLarge)
Container(color: Theme.of(context).colorScheme.primary)
Icon(Icons.star, color: Theme.of(context).colorScheme.secondary)8.2 国际化
# pubspec.yaml
dependencies:
flutter_localizations:
sdk: flutter
intl: ^0.19.0MaterialApp(
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: [
Locale('zh', 'CN'),
Locale('en', 'US'),
],
)
// 获取当前语言
Locale myLocale = Localizations.localeOf(context);9. 常用功能代码片段
9.1 对话框
// 确认对话框
Future<bool?> showConfirmDialog(BuildContext context) async {
return showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: Text('确认删除'),
content: Text('此操作不可撤销,确定要删除吗?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: Text('取消'),
),
TextButton(
onPressed: () => Navigator.pop(context, true),
child: Text('删除', style: TextStyle(color: Colors.red)),
),
],
),
);
}
// 底部弹出
showModalBottomSheet(
context: context,
builder: (context) => Container(
height: 300,
child: Center(child: Text('底部内容')),
),
);
// SnackBar
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('操作成功'),
duration: Duration(seconds: 2),
action: SnackBarAction(
label: '撤销',
onPressed: () {},
),
),
);9.2 图片选择
dependencies:
image_picker: ^1.0.0import 'package:image_picker/image_picker.dart';
final picker = ImagePicker();
// 拍照
final photo = await picker.pickImage(source: ImageSource.camera);
// 从相册选择
final image = await picker.pickImage(
source: ImageSource.gallery,
maxWidth: 1024,
maxHeight: 1024,
imageQuality: 85,
);
// 获取路径
if (image != null) {
print(image.path);
}9.3 动画
// 隐式动画(自动过渡)
AnimatedContainer(
duration: Duration(milliseconds: 300),
width: _isExpanded ? 200 : 100,
height: _isExpanded ? 200 : 100,
color: _isExpanded ? Colors.red : Colors.blue,
curve: Curves.easeInOut,
child: ...,
)
AnimatedOpacity(
duration: Duration(milliseconds: 500),
opacity: _visible ? 1.0 : 0.0,
child: Text('渐隐渐现'),
)
// Hero 动画(页面间共享元素)
// 页面 A
Hero(
tag: 'image-1', // 唯一标识
child: Image.asset('photo.jpg'),
)
// 页面 B
Hero(
tag: 'image-1', // 相同标识
child: Image.asset('photo.jpg'),
)
// 显式动画控制器
class FadeIn extends StatefulWidget {
final Widget child;
const FadeIn({super.key, required this.child});
@override
State<FadeIn> createState() => _FadeInState();
}
class _FadeInState extends State<FadeIn> with SingleTickerProviderStateMixin {
late final AnimationController _controller;
late final Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(milliseconds: 500),
vsync: this,
);
_animation = CurvedAnimation(
parent: _controller,
curve: Curves.easeIn,
);
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return FadeTransition(
opacity: _animation,
child: widget.child,
);
}
}9.4 平台通道(调用原生代码)
import 'package:flutter/services.dart';
class PlatformChannel {
static const platform = MethodChannel('com.example.app/channel');
// 调用原生方法
static Future<String> getBatteryLevel() async {
try {
final result = await platform.invokeMethod('getBatteryLevel');
return '电量: $result%';
} on PlatformException catch (e) {
return '获取失败: ${e.message}';
}
}
// 接收原生调用
static void setupHandler() {
platform.setMethodCallHandler((call) async {
if (call.method == 'fromNative') {
print('收到原生消息: ${call.arguments}');
}
return null;
});
}
}10. 项目结构与开发规范
10.1 推荐目录结构
lib/
├── main.dart # 入口文件
├── app.dart # MaterialApp 配置
├── router.dart # 路由配置
├── core/ # 核心共享代码
│ ├── constants/ # 常量定义
│ │ ├── api_constants.dart
│ │ ├── app_colors.dart
│ │ └── app_strings.dart
│ ├── theme/ # 主题配置
│ │ └── app_theme.dart
│ ├── utils/ # 工具类
│ │ ├── date_utils.dart
│ │ └── validators.dart
│ └── extensions/ # 扩展方法
│ └── context_extension.dart
├── data/ # 数据层
│ ├── models/ # 数据模型
│ │ ├── user.dart
│ │ └── user.g.dart # json_serializable 生成
│ ├── repositories/ # 仓库
│ │ └── user_repository.dart
│ └── datasources/ # 数据源
│ ├── remote/ # 远程
│ │ └── api_client.dart
│ └── local/ # 本地
│ └── local_storage.dart
├── domain/ # 业务层
│ ├── entities/ # 实体(业务对象)
│ └── usecases/ # 用例
├── presentation/ # 表现层(UI)
│ ├── screens/ # 页面
│ │ ├── home_screen.dart
│ │ └── profile_screen.dart
│ ├── widgets/ # 可复用组件
│ │ ├── loading_indicator.dart
│ │ └── error_widget.dart
│ └── providers/ # 状态管理
│ └── auth_provider.dart
└── services/ # 第三方服务
└── push_notification_service.dart
test/ # 测试
├── unit/
├── widget/
└── integration/10.2 开发规范
// 命名规范
// 文件/目录:lower_snake_case
// 类/枚举:PascalCase
// 变量/函数:camelCase
// 常量:camelCase 或 lowerCamelCase
// 1. 总是使用 const 构造如果可能
const SizedBox(height: 16) // 好
SizedBox(height: 16) // 避免
// 2. Widget 方法不要太长,抽离小组件
// 不好
Widget build(BuildContext context) {
return Column(
children: [
Container(...50 行代码...),
Container(...50 行代码...),
],
);
}
// 好
Widget build(BuildContext context) {
return Column(
children: [
HeaderSection(),
ContentSection(),
],
);
}
// 3. 使用 trailing comma 让格式化更美观
Widget build() {
return Container(
width: 100,
height: 100,
color: Colors.red,
); // 有逗号,每个属性一行
}
// 4. 字符串使用单引号
String text = 'Hello'; // 好
String text2 = "Hello"; // 避免(除非字符串内有单引号)
// 5. 使用 super parameters
class MyWidget extends StatelessWidget {
const MyWidget({super.key}); // Dart 2.17+
}
// 6. 总是处理 async 异常
Future<void> fetchData() async {
try {
final data = await api.getData();
} catch (e) {
// 处理错误
}
}
// 7. BuildContext 异步使用时检查 mounted
if (mounted) {
Navigator.pop(context);
}三、Flutter 开发速查表
常用命令
# 创建项目
flutter create my_app
cd my_app
# 运行
flutter run # 运行到连接的设备
flutter run -d emulator-5554 # 指定设备
flutter run --release # 发布模式运行
# 依赖管理
flutter pub get # 安装依赖
flutter pub add dio # 添加依赖
flutter pub upgrade # 升级依赖
flutter pub outdated # 检查过时依赖
# 代码生成
flutter pub run build_runner build # 生成代码
flutter pub run build_runner watch # 监听并自动生成
flutter pub run build_runner build --delete-conflicting-outputs # 冲突时删除重新生成
# 构建
flutter build apk # Android APK
flutter build apk --split-per-abi # 按 ABI 分包
flutter build appbundle # Android App Bundle
flutter build ios # iOS
flutter build web # Web
# 测试与分析
flutter test # 运行测试
flutter analyze # 静态分析
flutter format lib/ # 格式化代码
flutter doctor # 检查环境
# 设备管理
flutter devices # 列出设备
flutter emulators # 列出模拟器
flutter emulators --launch Pixel_6_API_33 # 启动模拟器pubspec.yaml 配置模板
name: my_app
description: A new Flutter project.
publish_to: "none"
version: 1.0.0+1
environment:
sdk: ">=3.0.0 <4.0.0"
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.6
# 状态管理
flutter_riverpod: ^2.4.9
# 网络请求
dio: ^5.4.0
retrofit: ^4.0.3
# 本地存储
shared_preferences: ^2.2.2
hive_flutter: ^1.1.0
# 路由
go_router: ^13.0.1
# 代码生成
freezed_annotation: ^2.4.1
json_annotation: ^4.8.1
# 工具
logger: ^2.0.2
flutter_screenutil: ^5.9.0 # 屏幕适配
cached_network_image: ^3.3.0 # 图片缓存
# UI 组件
shimmer: ^3.0.0 # 骨架屏
fluttertoast: ^8.2.4 # Toast
flutter_slidable: ^3.0.1 # 侧滑
# 功能
image_picker: ^1.0.7
url_launcher: ^6.2.2
share_plus: ^7.2.1
# 国际化
flutter_localizations:
sdk: flutter
intl: ^0.19.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^3.0.1
# 代码生成
build_runner: ^2.4.7
freezed: ^2.4.5
json_serializable: ^6.7.1
retrofit_generator: ^8.0.6
hive_generator: ^2.0.1
flutter:
uses-material-design: true
assets:
- assets/images/
- assets/icons/
- assets/fonts/
fonts:
- family: CustomFont
fonts:
- asset: assets/fonts/CustomFont-Regular.ttf
- asset: assets/fonts/CustomFont-Bold.ttf
weight: 700四、学习路线图
入门阶段(1-2 周)
- Dart 基础:变量、类型、函数、类、异步
- Flutter Widget:StatelessWidget、StatefulWidget、常用布局
- 基础项目:制作一个待办事项应用或计算器
进阶阶段(2-4 周)
- 状态管理:掌握 Riverpod 或 Bloc
- 网络请求:REST API 集成、数据序列化
- 本地存储:SharedPreferences、SQLite
- 导航路由:go_router、深度链接
- 项目:制作一个带网络请求的新闻应用或天气应用
高级阶段(持续学习)
- 性能优化:列表优化、图片缓存、渲染优化
- 自定义 Widget:CustomPainter、RenderObject
- 平台通道:调用原生功能
- 测试:单元测试、Widget 测试、集成测试
- CI/CD:自动化构建和发布
- 架构模式:Clean Architecture、DDD
五、推荐资源
| 资源 | 链接 | 说明 |
|---|---|---|
| Flutter 官方文档 | https://docs.flutter.dev | 最权威的学习资料 |
| Dart 语言文档 | https://dart.dev/language | Dart 语法详解 |
| Flutter 中文社区 | https://flutter.cn | 中文资源和文档 |
| pub.dev | https://pub.dev | Dart/Flutter 包仓库 |
| Flutter Gallery | https://gallery.flutter.dev | Widget 示例展示 |
| Flutter 官方示例 | https://github.com/flutter/samples | 各种场景示例代码 |
提示:本指南涵盖了 Dart 和 Flutter 的核心知识点,建议边学边练。Flutter 开发最重要的是多写 Widget、多实践布局。遇到问题优先查阅官方文档,它是目前最全面和最新的资料。