flutter架 构项目基础

This commit is contained in:
崔宝新
2026-09-04 15:22:20 +08:00
parent 70967229d0
commit 0c5aee59c0
265 changed files with 35484 additions and 0 deletions
+118
View File
@@ -0,0 +1,118 @@
# 【Demo】响应式布局:一套代码适配手机 / Pad
> 源文件:`lib/pages/demo/responsive_demo.dart` | 分类:基础布局
> 一句话:屏幕适配 ≠ 响应式布局 —— 一个是"等比缩放",一个是"改变结构"。
---
## 一、这个 Demo 解决什么问题
同一个页面,手机单列列表、Pad 双列卡片墙,**一套代码自动切换**。
新手最容易混淆的概念在这里讲清楚。
### 两个维度(务必分清)
| 概念 | 手段 | 解决什么 |
|---|---|---|
| **屏幕适配** | flutter_screenutil(`.w/.h/.sp`) | 不同屏幕比例不变形(尺寸等比缩放) |
| **响应式布局** | 断点 + LayoutBuilder(本 Demo) | 不同屏幕宽度改变布局**结构**(单列/双列) |
> 两者配合使用:尺寸用 `.w/.h/.sp` 缩放,结构用断点切换。
---
## 二、怎么运行 / 演示内容
```dart
runApp(const MaterialApp(home: ResponsiveDemoPage()));
```
- 顶部模式条实时显示"手机模式 / Pad 模式 + 当前宽度 dp";
- 手机(<600dp)→ 单列 ListTile 列表;
- Pad(≥600dp)→ 多列卡片网格(列数按宽度自适应)。
---
## 三、核心知识点(新手向)
### 1. 断点标准(Material 官方)
| 宽度 | 设备 |
|---|---|
| < 600dp | 手机 |
| 600 ~ 839dp | 小平板 / 折叠屏 |
| >= 840dp | 大平板 / 桌面 |
> ⚠ 关键:判断的是**屏幕宽度**,不是 Platform!
> iOS 也有 iPad,Android 也有平板 —— 用平台判断永远分不清它们。
### 2. 两种拿宽度的方式
```dart
MediaQuery.sizeOf(context).width // 整个屏幕宽度
LayoutBuilder(...) // 父容器实际给你的可用宽度
```
- 页面铺满屏幕时两者一样;
- 页面内有**可伸缩区域**(侧栏收起/展开)时必须用 `LayoutBuilder`;
- 本 Demo 用 `LayoutBuilder`,因为能拿到最真实的可用宽度。
### 3. 核心切换逻辑
```dart
LayoutBuilder(
builder: (context, constraints) {
final width = constraints.maxWidth;
final isTablet = width >= _tabletBreakpoint; // 600dp
// 列数自适应:每列至少 280dp 宽,最多 4 列
final columns = (width / _minCardWidth).floor().clamp(1, 4);
return isTablet
? _buildGrid(columns) // Pad:多列网格
: _buildList(); // 手机:单列列表
},
)
```
---
## 四、关键代码剖析
```dart
// 手机列表项
ListView.separated(
padding: EdgeInsets.all(12.w),
itemCount: _items.length,
separatorBuilder: (_, _) => SizedBox(height: 8.h),
itemBuilder: (context, i) => _ListTileItem(item: _items[i]),
)
// Pad 卡片网格(同一个数据源 _items,只是换了一种展示容器)
GridView.builder(
padding: EdgeInsets.all(12.w),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: columns, // 由宽度算出来的列数
mainAxisSpacing: 12.w,
crossAxisSpacing: 12.w,
childAspectRatio: 1.4, // 宽高比,数字大了卡片变矮
),
itemCount: _items.length,
itemBuilder: (context, i) => _CardItem(item: _items[i]),
)
```
**关键思想:数据不变,变的是"容器"** —— 同一份 `_items`,手机渲染成列表项,Pad 渲染成卡片。
组件只负责"怎么显示一条数据",页面负责"按宽度选容器"。
---
## 五、踩坑记录
1. **别用 `Platform.isAndroid` 判断手机/Pad** —— 永远用宽度断点;
2. `MediaQuery.sizeOf(context).width` 拿的是全屏宽,页面嵌在可伸缩容器里会算错 —— 用 LayoutBuilder;
3. `(width / 280).floor()` 在窄屏可能算出 0 列 —— 必须 `.clamp(1, 4)` 兜底;
4. 断点值用常量集中管理(`_tabletBreakpoint = 600`),别散落在各页面写死魔法数字。
---
## 六、扩展思考
- **官方推荐**:`flutter/foundation` 的 `defaultTargetPlatform` + 宽度断点结合;或引入 `responsive_builder` 包;
- 平板横竖屏切换:竖屏 8.4" ≈ 800dp,横屏 1280dp —— 断点要覆盖两种情况;
- 进阶模式:`LayoutBuilder` + `CustomScrollView`([sliver_demo.md](sliver_demo.md))组合做"响应式长列表";
- 本项目 theme_demo 的 `PageTheme` 按页面层级给配色,响应式按宽度给结构 —— 两个维度正交,可自由组合。