速查 01 · Flutter
按问题索引。Ctrl+F 页内搜索。 系统学习见 Flutter 教程系列,原理见 03 篇渲染原理。
目录
常用 Widget 速查
前端 → Flutter 对照
| 前端 | Flutter |
|---|---|
<div> | Container / SizedBox / Padding |
<span> | Text / Text.rich |
| flex 布局 | Row / Column / Flex |
position: absolute | Stack + Positioned |
overflow: auto | SingleChildScrollView / ListView |
display: grid | GridView / Wrap |
z-index | Stack 中的顺序(后面的在上层) |
position: fixed | Stack 放最外层 或 BottomAppBar |
<button onClick> | ElevatedButton(onPressed:) / GestureDetector / InkWell |
<input> | TextField + TextEditingController |
<img> | Image.network / Image.asset |
loading 转圈 | CircularProgressIndicator |
v-if / {cond && <X/>} | if (cond) X()(Dart 的 if 可直接写在集合里) |
v-for / .map() | ListView.builder / for (var x in list) X() |
box-shadow | BoxShadow in BoxDecoration |
border-radius | BorderRadius.circular(8) |
高频 Widget 一行写法
dart
const SizedBox(height: 12) // 间距(比 Container+height 轻量)
const Spacer() // Row/Column 中撑开剩余空间
Expanded(child: X()) // 占满剩余空间
Flexible(child: X()) // 可小可大,不强制撑满
Wrap(spacing: 8, runSpacing: 8, children: []) // 自动换行(标签流)
const Divider(height: 1)
ClipRRect(borderRadius: BorderRadius.circular(8), child: Image.network(url))
Opacity(opacity: 0.5, child: X()) // 透明(比改颜色省重建)
Visibility(visible: cond, child: X())
const Center(child: X())
SafeArea(child: X()) // 避开刘海/状态栏
Material(color: Colors.transparent, child: InkWell(onTap: () {}, child: X())) // 水波纹点击布局类
常见布局模板
dart
// 左右结构(左固定宽,右占满)
Row(children: [const SizedBox(width: 80), Expanded(child: right)])
// 上下结构(内容 + 底部按钮)
Column(children: [Expanded(child: content), const SubmitButton()])
// 叠加(右上角角标)
Stack(children: [content, const Positioned(right: 8, top: 8, child: Badge())])
// 居中 + 最大宽度(Web/平板适配)
Center(child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 480),
child: content,
))
// 滚动 + 键盘不遮挡
SingleChildScrollView(
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: form,
)尺寸与约束
dart
MediaQuery.of(context).size.width // 屏幕宽(会触发重建,慎用)
MediaQuery.sizeOf(context).width // ✅ 推荐:只在尺寸变化时重建
LayoutBuilder(builder: (ctx, c) => c.maxWidth > 600 ? wide() : narrow()) // 响应式
double.infinity // 撑满父约束允许的最大值
BoxConstraints.tightFor(width: 100) // 固定尺寸约束报错速查
| 报错信息(关键词) | 原因 | 解法 |
|---|---|---|
RenderFlex overflowed by XX pixels | Row/Column 内容超出 | 内容用 Expanded 包;或外层改 SingleChildScrollView |
Vertical viewport was given unbounded height | ListView 放在 Column 里没有高度约束 | 用 Expanded 包 ListView,或加 shrinkWrap: true |
Incorrect use of ParentDataWidget | Expanded/Flexible 的直接父级不是 Row/Column/Flex | 检查父组件,或移除 Expanded |
setState() called after dispose() | 异步回调时页面已销毁 | 回调里先判 if (!mounted) return; |
Looking up a deactivated widget's ancestor | 用了已卸载页面的 context | 用全局 navigatorKey,或提前取 Provider 再 await |
A RenderFlex overflowed + 键盘弹起 | 键盘挤压 | Scaffold(resizeToAvoidBottomInset: true) + SingleChildScrollView |
Null check operator used on a null value | ! 用在 null 上 | 改 ?. + ?? 默认值;检查异步时序 |
type 'X' is not a subtype of 'Y' | 类型不匹配(常见于 JSON 解析) | 检查 Map<String, dynamic> 的实际类型 |
MissingPluginException | 平台通道未实现,或热重载后原生代码未生效 | 冷启动(Full Restart);核对 channel 名,见21 篇 |
Waiting for another flutter command | 有卡住的 flutter 进程 | 删 flutter/bin/cache/lockfile,或重启 IDE |
生命周期
StatefulWidget 完整顺序
createState → initState → didChangeDependencies → build
→ (setState / 依赖变化) → didUpdateWidget → build
→ deactivate → dispose| 方法 | 调用时机 | 适合做什么 |
|---|---|---|
initState | 插入树时一次 | 初始化 controller、发起首次请求(不能用 context 做依赖查找,用 WidgetsBinding.addPostFrameCallback) |
didChangeDependencies | initState 后、依赖变化时 | 依赖 InheritedWidget/Provider 的初始化 |
didUpdateWidget | 父组件重建导致配置变化 | 对比 oldWidget 做响应 |
dispose | 永久移除 | 必须释放 controller/stream/timer |
dart
late final TextEditingController _ctrl;
late final StreamSubscription _sub;
@override
void initState() {
super.initState();
_ctrl = TextEditingController();
_sub = stream.listen((_) => setState(() {}));
}
@override
void dispose() {
_ctrl.dispose(); // 漏了就内存泄漏
_sub.cancel();
super.dispose(); // 写在最后
}原理与 Key 体系见 03 篇。
状态与重建
dart
// Riverpod 精确订阅(只有 name 变才重建)
final name = ref.watch(userProvider.select((u) => u.name));
// 读一次,不订阅
ref.read(counterProvider.notifier).increment();
// 监听副作用(弹窗/导航)
ref.listen(errorProvider, (prev, next) { if (next != null) showSnackBar(next); });
// 只在 build 之外刷新
if (mounted) setState(() {});减少重建三件套:const 构造 · select 精确订阅 · 把变化部分下沉成小 Widget。 性能排查见 22 篇。
导航路由
dart
// GoRouter(本项目方案,见 13 篇)
context.push('/detail/$id'); // 入栈
context.go('/home'); // 替换栈
context.pop(); // 返回
context.replace('/login'); // 替换当前页
// 原生 Navigator
Navigator.of(context).push(MaterialPageRoute(builder: (_) => const DetailPage()));
Navigator.of(context).pushNamed('/detail', arguments: {'id': 1});
// 返回值
final result = await context.push<bool>('/edit');异步与网络
dart
// FutureUI:三态渲染
FutureBuilder<Data>(
future: repo.load(),
builder: (ctx, snap) {
if (snap.connectionState == ConnectionState.waiting) return const CircularProgressIndicator();
if (snap.hasError) return Text('出错: ${snap.error}');
return Content(snap.data!);
},
)
// 并发等待
final (a, b) = await (repo.loadA(), repo.loadB()).wait; // Dart 3 Records
// 超时
final r = await api.fetch().timeout(const Duration(seconds: 10));
// 防抖(搜索框)
Timer? _t;
void onChanged(String v) {
_t?.cancel();
_t = Timer(const Duration(milliseconds: 300), () => search(v));
}数据层链路见 09 篇。
列表与滚动
dart
// 长列表(懒加载,必用 builder)
ListView.builder(itemCount: n, itemBuilder: (ctx, i) => Tile(data: items[i]))
// 定高列表(性能更好)
ListView.builder(itemExtent: 72, ...)
// 下拉刷新
RefreshIndicator(onRefresh: () => notifier.refresh(), child: ListView(...))
// 滚动到底部
_scrollController.animateTo(_scrollController.position.maxScrollExtent, ...)
// 监听到底加载更多
_scrollController.addListener(() {
if (_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent - 200) notifier.loadMore();
})分页骨架见 12 篇。
样式与主题
dart
// 圆角 + 阴影 + 边框
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0xFFE5E5E5)),
boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 8)],
),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: content,
)
// 文本样式
const Text('标题', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600))
Text('单行省略', maxLines: 1, overflow: TextOverflow.ellipsis)
// 主题色
Theme.of(context).colorScheme.primary
Theme.of(context).textTheme.titleMedium设计 Token 规范见 11 篇。
平台判断与适配
dart
import 'dart:io' show Platform;
if (Platform.isAndroid) ...
if (kIsWeb) ... // package:flutter/foundation
if (Platform.isIOS) ...
// 屏幕适配(本项目用 ScreenUtil,见 11 篇)
24.w // 按宽度适配
12.h // 按高度适配
14.sp // 字体适配⚠️ kIsWeb 判断时不要用 dart:io(Web 不支持),用 universal_io 或条件导入。
常用命令
bash
fvm flutter pub get # 装依赖(项目统一加 fvm 前缀)
fvm flutter pub add <package> # 添加依赖
fvm flutter run # 运行
fvm flutter run -t lib/main_playground.dart # 运行练习场入口
fvm flutter analyze lib # 静态检查
fvm flutter test # 跑测试
fvm flutter build apk --release # 打 Android 包
fvm flutter build ipa --release # 打 iOS 包
fvm dart run build_runner build --delete-conflicting-outputs # 代码生成
fvm flutter clean # 清缓存(构建异常时先试这个)
fvm flutter pub outdated # 查看可升级依赖⚠️ 以上命令在本项目都要加
fvm前缀(版本锁定),见 16 篇。📦 更全的命令(模拟器启停、adb、构建打包、救急排障等)见 09 · Flutter 命令行速查。