5.0 KiB
5.0 KiB
【Demo】Align 对齐布局 + Riverpod 全局用户信息
源文件:
lib/pages/demo/align_demo.dart| 分类:基础布局 一句话:把子组件放到父容器任意位置,顺便演示本项目最小化的 Riverpod 状态读取。
一、这个 Demo 解决什么问题
新手写 UI 时常遇到一个需求:"把某个卡片放到盒子的左下角 / 右上角 / 居中"。
Align 就是 Flutter 里的"定位器"——给定一个区域,把子组件按 alignment 摆到指定位置。
本 Demo 额外演示了 Riverpod 全局用户信息的最小用法:
- 页面改成
ConsumerWidget,用ref.watch(currentUserProvider)读全局用户; - 登录后任何页面都能拿到用户(用户名 / 班级 / 积分),且数据变化时卡片自动刷新。
对照 Vue3:
Align类似position: absolute+ 容器相对定位;ref.watch类似 Pinia 的响应式 state —— 一处修改,处处生效。
二、怎么运行 / 演示内容
runApp(const ProviderScope(
child: MaterialApp(home: AlignDemoPage()),
));
页面主体是一个灰色 300×300 盒子,左下角悬浮一张蓝色"用户信息卡"。 登录后卡片内容会跟着变(未登录显示"未登录")。
三、核心知识点(新手向)
1. Flutter 布局的两条铁律
- 父组件给子组件传"约束"(Constraints),子组件在约束范围内决定自己的"大小";
- 所有布局问题(溢出、撑不开)都可以回到这条规则上找原因。
2. Align 的工作方式
- Align 会把自身区域扩大到父组件允许的最大范围(默认撑满);
- 然后按
alignment把子组件放到指定位置; - 子组件如果不写宽高,Align 取它的固有尺寸;写了就按写的来。
Align(
alignment: Alignment.bottomLeft, // 子组件放到父盒子左下角
child: Container(...), // 用户信息卡片
)
3. Alignment 坐标系
Alignment(x, y) 的 x、y 范围都是 -1.0 ~ 1.0:
| 值 | 位置 |
|---|---|
| (-1, -1) | 左上 |
| (0, 0) | 居中 |
| (1, 1) | 右下 |
Alignment.centerLeft |
左中 |
常用常量:topLeft / topCenter / topRight / centerLeft / center / centerRight / bottomLeft / bottomCenter / bottomRight。
4. Riverpod 最小用法(本项目统一模式)
class AlignDemoPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final user = ref.watch(currentUserProvider).value; // 订阅全局用户
// user 为 null = 未登录;登录了就是 CurrentUser 对象
}
}
ref.watch必须在build里调用(声明式依赖,状态变了自动重建);ref.read只能放回调里(一次性读取,不建立依赖)。
四、关键代码剖析
body: Container(
width: 300.w,
height: 300.h,
color: Colors.grey[300],
child: Align(
alignment: Alignment.bottomLeft,
child: Container(
width: 240.w, // 写了宽高 → 由子组件自己决定
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 10.h),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(8.w),
),
child: Column(
mainAxisSize: MainAxisSize.min, // 卡片高度跟随内容
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('用户:${user?.userName ?? '未登录'}'),
Text('班级:${user?.className ?? '-'}'),
Text('积分:${user?.userPoints ?? 0}'),
],
),
),
),
),
要点:
mainAxisSize: MainAxisSize.min—— Column 不占满父级,高度跟随内容(否则卡片会撑满整个 Align 区域);user?.xxx ?? 默认值—— 空安全写法:用户为空时显示占位。
五、踩坑记录
- 用了
ref的组件必须继承ConsumerWidget/ConsumerStatefulWidget,普通StatelessWidget拿不到ref(编译报错)。 ref.watch放 build 里,ref.read放回调里,反了会导致不刷新或性能问题。- 加了
.w/.h/.sp的 widget 不能再用const—— 尺寸是运行期算的,const会编译报错(const_eval_extension_method)。 Align的alignment只对没被Positioned包裹的子组件生效(Positioned是 Stack 的专属,见 stack_demo.md)。
六、扩展思考
Center就是Align(alignment: Alignment.center)的别名,记住 Align 就记住了 Center;- 同族定位组件:
Padding(内边距)、FractionallySizedBox(按比例尺寸)、SizedBox(固定尺寸); - 想"按百分比偏移"用
Alignment(x, y);想"按像素偏移"把 Align 换成Padding或包一层Transform.translate; - Riverpod 进阶:
ref.watch(provider).value是AsyncValue的取值方式,三态(loading/error/data)用法见 exam_demo.md 与docs/exam_architecture.md的"零、新手前置"章节。