flutter-build-responsive-layout

flutter-build-responsive-layout

熱門

使用 `LayoutBuilder`、`MediaQuery` 或 `Expanded/Flexible` 打造能適應不同螢幕尺寸的版面配置。當你需要讓 UI 在手機、平板及電腦等各種裝置型態上都能展現良好視覺效果時使用。

2783星標
163分支
更新於 2026/8/5
SKILL.md
唯讀
名稱
flutter-build-responsive-layout
描述

使用 `LayoutBuilder`、`MediaQuery` 或 `Expanded/Flexible` 打造能適應不同螢幕尺寸的版面配置。當你需要讓 UI 在手機、平板及電腦等各種裝置型態上都能展現良好視覺效果時使用。

實作自適應版面配置

目錄

空間量測指南

精確測量可用空間,確保版面配置是依據「應用程式視窗」而非僅依據「實體裝置」進行自適應調整。

  • 使用 MediaQuery.sizeOf(context) 來取得整個應用程式視窗的尺寸。
  • 使用 LayoutBuilder 根據父階 Widget 所分配到的空間來決定版面配置。評估 constraints.maxWidth 以決定要回傳哪個合適的 Widget 樹。
  • 切勿在 Widget 樹頂層附近使用 MediaQuery.orientationOfOrientationBuilder 來切換版面。裝置螢幕方向無法準確反映應用程式視窗實際可用的空間。
  • 請勿檢查硬體類型(例如區分「手機」或「平板」)。Flutter 應用程式可能會運行於可調整大小的視窗、多視窗模式或子母畫面(Picture-in-Picture)中。請嚴格依據可用的視窗空間來做出所有版面決策。

Widget 尺寸與約束條件

理解並運用 Flutter 的核心版面配置規則:約束往下傳,尺寸往上報,父階決定位置(Constraints go down. Sizes go up. Parent sets position.)。

  • 分配空間:RowColumnFlex 中使用 ExpandedFlexible
    • 使用 Expanded 強制子元件填滿所有剩餘可用空間(相當於設定 fit: FlexFit.tightflex 為 1.0 的 Flexible)。
    • 使用 Flexible 允許子元件在特定上限內自行決定尺寸,同時仍保有伸縮能力。使用 flex 權重係數來定義同階(sibling)元件之間劃分空間的比例。
  • 限制寬度: 避免 Widget 在大螢幕上佔滿所有水平空間。將 GridViewListView 等 Widget 包裹在 ConstrainedBoxContainer 中,並於 BoxConstraints 中設定 maxWidth
  • 延遲渲染(Lazy Rendering): 當渲染數量未知或大量的列表項目時,務必使用 ListView.builderGridView.builder

裝置與螢幕轉向行為

確保應用程式在各種裝置型態及輸入方式下皆能正常運作。

  • 請勿鎖定螢幕方向。 鎖定方向會在摺疊螢幕裝置上引發嚴重的版面配置問題,通常會導致畫面出現黑邊(letterboxing,即應用程式置中且四周留黑)。Android 大螢幕等級規範要求同時支援直向與橫向模式。
  • 鎖定方向時的備用方案: 若業務需求強制要求鎖定方向,請使用 Display API 取得實體螢幕尺寸,而非使用 MediaQuery。在相容性模式下,MediaQuery 無法取得較大的視窗尺寸。
  • 支援多種輸入方式: 實作對基礎滑鼠、觸控板及鍵盤快捷鍵的支援。確保觸控標的(touch targets)尺寸合宜,並提供良好的鍵盤導覽存取性(accessibility)。

工作流程:構建自適應版面

請遵循此工作流程來實作能自動適應可用 BoxConstraints 的版面配置。

任務進度:

  • [ ] 確認需要具備自適應行為的目標 Widget。
  • [ ] 將 Widget 樹包裹在 LayoutBuilder 中。
  • [ ] 從 builder 回呼函式中提取 constraints.maxWidth
  • [ ] 定義自適應斷點(例如 largeScreenMinWidth = 600)。
  • [ ] maxWidth > largeScreenMinWidth 回傳大螢幕版面(例如使用 Row 將導覽側邊欄與內容區域並排呈現)。
  • [ ] maxWidth <= largeScreenMinWidth 回傳小螢幕版面(例如使用 Column 或標準的導覽頁面結構)。
  • [ ] 執行驗證器 -> 調整應用程式視窗大小 -> 檢視版面過渡效果 -> 修復溢出(overflow)錯誤。

工作流程:針對大螢幕進行最佳化

請遵循此工作流程,防止 UI 元素在大螢幕顯示器上發生不自然的拉伸。

任務進度:

  • [ ] 找出全寬組件(例如 ListView、文字區塊、表單)。
  • [ ] 若要最佳化列表:ListView.builder 轉換為 GridView.builder,並配合 SliverGridDelegateWithMaxCrossAxisExtent 根據視窗大小自動調整欄數。
  • [ ] 若要最佳化表單或文字區塊: 將組件包裹在 ConstrainedBox 中。
  • [ ] 為 ConstrainedBox 套用 BoxConstraints(maxWidth: [optimal_width])
  • [ ] 將 ConstrainedBox 包裹在 Center Widget 中,使受限內容在大螢幕上保持置中。
  • [ ] 執行驗證器 -> 在桌面端/平板目標上測試 -> 檢視水平拉伸情況 -> 調整 maxWidth 或網格範圍(grid extents)。

程式碼範例

使用 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('Item $index'),
              );
            },
          ),
        ),
      ),
    );
  }
}