SKILL.md
readonlyread-only
name
flutter-build-responsive-layout
description
使用 `LayoutBuilder`、`MediaQuery` 或 `Expanded/Flexible` 建立能適應不同螢幕尺寸的版面。當你需要 UI 在手機和平板/桌機上都能呈現良好效果時使用。
實作適應性版面
目錄
空間測量指南
準確判斷可用空間,確保版面能適應應用程式視窗,而非僅限於實體裝置。
- 使用
MediaQuery.sizeOf(context)取得整個應用程式視窗的大小。 - 使用
LayoutBuilder根據父 widget 分配的空間來決定版面。評估constraints.maxWidth以決定回傳適當的 widget 樹。 - 不要在 widget 樹頂層使用
MediaQuery.orientationOf或OrientationBuilder來切換版面。裝置方向無法準確反映應用程式視窗的可用空間。 - 不要檢查硬體類型(例如「手機」vs「平板」)。Flutter 應用程式可在可調整大小的視窗、多視窗模式和子母畫面中執行。所有版面決策應嚴格基於可用視窗空間。
Widget 尺寸與約束
理解並應用 Flutter 的核心版面規則:約束向下傳遞。尺寸向上傳遞。父層設定位置。
- 分配空間: 在
Row、Column或Flexwidget 中使用Expanded和Flexible。- 使用
Expanded強制子 widget 填滿所有剩餘可用空間(相當於flex係數為 1.0 且fit: FlexFit.tight的Flexible)。 - 使用
Flexible允許子 widget 在特定限制內自行調整大小,同時仍能展開/收縮。使用flex係數定義兄弟 widget 之間的空間消耗比例。
- 使用
- 限制寬度: 防止 widget 在大螢幕上佔用所有水平空間。將
GridView或ListView等 widget 包裹在ConstrainedBox或Container中,並在BoxConstraints中設定maxWidth。 - 延遲渲染: 當渲染未知或大量項目的列表時,務必使用
ListView.builder或GridView.builder。
裝置與方向行為
確保應用程式在所有裝置外型與輸入方式下都能正確運作。
- 不要鎖定螢幕方向。 鎖定方向會導致可折疊裝置出現嚴重的版面問題,通常會造成信箱效應(應用程式居中並帶有黑邊)。Android 大尺寸格式層級要求同時支援直向與橫向。
- 鎖定方向的備援方案: 如果業務需求嚴格要求鎖定方向,請使用
Display API取得實體螢幕尺寸,而非MediaQuery。在相容模式下,MediaQuery無法接收到較大的視窗尺寸。 - 支援多種輸入: 實作對基本滑鼠、觸控板和鍵盤快捷鍵的支援。確保觸控目標尺寸適當,且鍵盤導航可存取。
工作流程:建構適應性版面
請依照以下工作流程實作能適應可用 BoxConstraints 的版面。
任務進度:
- [ ] 識別需要適應性行為的目標 widget。
- [ ] 將 widget 樹包裹在
LayoutBuilder中。 - [ ] 從 builder 回呼中提取
constraints.maxWidth。 - [ ] 定義適應性斷點(例如
largeScreenMinWidth = 600)。 - [ ] 如果
maxWidth > largeScreenMinWidth: 回傳大螢幕版面(例如使用Row將導覽側邊欄與內容區域並排顯示)。 - [ ] 如果
maxWidth <= largeScreenMinWidth: 回傳小螢幕版面(例如使用Column或標準導覽方式)。 - [ ] 執行驗證器 -> 調整應用程式視窗大小 -> 檢視版面轉換 -> 修正溢位錯誤。
工作流程:針對大螢幕最佳化
請依照以下工作流程防止 UI 元素在大螢幕上過度拉伸。
任務進度:
- [ ] 識別全寬元件(例如
ListView、文字區塊、表單)。 - [ ] 如果最佳化列表: 將
ListView.builder轉換為GridView.builder,並使用SliverGridDelegateWithMaxCrossAxisExtent根據視窗大小自動調整欄數。 - [ ] 如果最佳化表單或文字區塊: 將元件包裹在
ConstrainedBox中。 - [ ] 對
ConstrainedBox套用BoxConstraints(maxWidth: [optimal_width])。 - [ ] 將
ConstrainedBox包裹在Centerwidget 中,使受限內容在大螢幕上保持居中。 - [ ] 執行驗證器 -> 在桌機/平板上測試 -> 檢視水平拉伸情況 -> 調整
maxWidth或網格範圍。
範例
使用 LayoutBuilder 的適應性版面
示範根據可用寬度在行動版與桌面版版面之間切換。
import 'package:flutter/material.dart';
const double largeScreenMinWidth = 600.0;
class AdaptiveLayout extends StatelessWidget {
const AdaptiveLayout({super.key});
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth > largeScreenMinWidth) {
return _buildLargeScreenLayout();
} else {
return _buildSmallScreenLayout();
}
},
);
}
Widget _buildLargeScreenLayout() {
return Row(
children: [
const SizedBox(width: 250, child: Placeholder(color: Colors.blue)),
const VerticalDivider(width: 1),
Expanded(child: const Placeholder(color: Colors.green)),
],
);
}
Widget _buildSmallScreenLayout() {
return const Placeholder(color: Colors.green);
}
}
在大螢幕上限制寬度
示範防止 widget 佔用所有水平空間。
import 'package:flutter/material.dart';
class ConstrainedContent extends StatelessWidget {
const ConstrainedContent({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 800.0, // 最大寬度,便於閱讀
),
child: ListView.builder(
itemCount: 50,
itemBuilder: (context, index) {
return ListTile(
title: Text('項目 $index'),
);
},
),
),
),
);
}
}






