flutter-build-responsive-layout

flutter-build-responsive-layout

熱門

使用 `LayoutBuilder`、`MediaQuery` 或 `Expanded/Flexible` 建立能適應不同螢幕尺寸的版面。當你需要 UI 在手機和平板/桌機上都能呈現良好效果時使用。

2525星標
152分支
更新於 2026/6/18
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.orientationOfOrientationBuilder 來切換版面。裝置方向無法準確反映應用程式視窗的可用空間。
  • 不要檢查硬體類型(例如「手機」vs「平板」)。Flutter 應用程式可在可調整大小的視窗、多視窗模式和子母畫面中執行。所有版面決策應嚴格基於可用視窗空間。

Widget 尺寸與約束

理解並應用 Flutter 的核心版面規則:約束向下傳遞。尺寸向上傳遞。父層設定位置。

  • 分配空間:RowColumnFlex widget 中使用 ExpandedFlexible
    • 使用 Expanded 強制子 widget 填滿所有剩餘可用空間(相當於 flex 係數為 1.0 且 fit: FlexFit.tightFlexible)。
    • 使用 Flexible 允許子 widget 在特定限制內自行調整大小,同時仍能展開/收縮。使用 flex 係數定義兄弟 widget 之間的空間消耗比例。
  • 限制寬度: 防止 widget 在大螢幕上佔用所有水平空間。將 GridViewListView 等 widget 包裹在 ConstrainedBoxContainer 中,並在 BoxConstraints 中設定 maxWidth
  • 延遲渲染: 當渲染未知或大量項目的列表時,務必使用 ListView.builderGridView.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 包裹在 Center widget 中,使受限內容在大螢幕上保持居中。
  • [ ] 執行驗證器 -> 在桌機/平板上測試 -> 檢視水平拉伸情況 -> 調整 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'),
              );
            },
          ),
        ),
      ),
    );
  }
}