mcpField
F
flutter-build-responsive-layout

flutter-build-responsive-layout

github.com/dhruvanbhalaraデザイン・UI3058 回インストール9 週間前に更新
リポジトリ

flutter-build-responsive-layout について

レスポンシブな画面を構築する Flutter 開発者向けのレイアウトスキルです。ウィンドウの制約に応じた調整を中心に、大画面での読みやすさ、サイズ変更可能なウィンドウ、多様な入力方法を扱います。

主な機能

  1. 01ウィンドウサイズに応じたレイアウト選択
  2. 02LayoutBuilder による制約の取得
  3. 03Flex ウィジェットによる空間配分
  4. 04大画面でのコンテンツ幅の制限
  5. 05長いリストの遅延生成
  6. 06複数の入力方法への対応

活用シーン

  1. 01スマートフォンと大画面向けのレイアウト設計
  2. 02サイズ変更や画面分割への対応
  3. 03ワイド画面でのフォームや文章の過度な拡張防止

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/dhruvanbhalara/skills --skill flutter-build-responsive-layout

インストール後、Claude Code で/flutter-build-responsive-layoutを入力して読み込まれたか確認します。

詳細

目次

スペース計測のガイドライン

レイアウトがアプリウィンドウに流動的に適応するようにするには(これは、ウィンドウをリサイズできるプラットフォームやマルチウィンドウのマルチタスクにおいて特に重要です)、次の原則に従って利用可能なスペースを判断します。

  • MediaQuery.sizeOf(context) を使用する: 完全な MediaQuery.of(context) にアクセスすると(MediaQuery のいずれかのプロパティの変更で再ビルドが発生します)、代わりに現代的で効率的な MediaQuery.sizeOf(context) を使用して、アプリケーションウィンドウの寸法を取得します。
  • LayoutBuilder を使用する: グローバルな画面寸法ではなく、親ウィジェットから割り当てられたサイズに基づいてコンポーネントを適応させたい場合は、レイアウトの判断を LayoutBuilder の内部に配置します。constraints.maxWidth を使用して、レンダリングする適切なウィジェットサブツリーを選択します。
  • グローバルな画面の向きビルダーを避ける: メインレイアウトを切り替えるために、ウィジェットツリーの上位でデバイスの画面の向きビルダー(OrientationBuilder または MediaQuery.orientationOf)に依存しないでください。デバイスの画面の向きは、特にマルチウィンドウや折りたたみ式デバイスのマルチタスクモードにおいて、実際に利用可能なレイアウトスペースを常に反映するとは限りません。
  • 物理ハードウェアの種類を確認しない: レイアウトのルールを決定するために、現在のハードウェアが「スマートフォン」「タブレット」「デスクトップ」のいずれであるかを確認しないでください。ウィンドウのリサイズ、ピクチャーインピクチャー、画面分割は現代のプラットフォームでは標準であるため、レイアウトは物理ハードウェアの種類ではなく、ウィンドウの制約に適応しなければなりません。

ウィジェットのサイズと制約

Flutter では、レイアウトは交渉プロセスによって構築されます。制約は下へ、サイズは上へ、そして親が位置を設定します。ウィジェットの寸法をエレガントに管理するために、次のルールを適用します。

  • Flex でスペースを分配する: Row、Column、または Flex レイアウトの内部で Expanded と Flexible を使用して、利用可能なスペースを兄弟間で比例配分します。
    • Expanded を使用して、子に主軸に沿った残りの利用可能なスペースをすべて消費させます。
    • Flexible を使用して、子が特定の上限まで自身のサイズを決められるようにし、flex 係数を使用して残りのスペースをどのように分配するかを定義します。
  • 最大幅を制限する: コンテンツがワイドディスプレイ上で無限に引き伸ばされるのを避けます。リストビュー、グリッド、フォームブロックを ConstrainedBox または最大幅の制限を指定した Container でラップし、Center ウィジェットを使用して中央に配置します。
  • リストを遅延レンダリングする: 多数または動的な数のアイテムを持つリストやグリッドを表示する場合は、常に ListView.builder または GridView.builder を使用して、画面外のウィジェットが遅延構築され、メモリ消費が低く保たれるようにします。

デバイスと画面の向きの動作

レスポンシブアプリケーションは、動的なフォームファクタと複数の入力方法に適応します。

  • 画面の向きの固定を避ける: アプリケーションを単一の画面の向きに固定することは強く推奨されません。折りたたみ式デバイスや大型フォームファクタでユーザー体験を損ない、不格好なレターボックス表示を引き起こします。
  • 安全なフォールバックを提供する: 厳格なビジネス上の要請により画面の向きの固定が避けられない場合は、互換モードやレガシーなスケーリングモードで誤った寸法を返す可能性がある MediaQuery ではなく、Display API を利用して物理画面の寸法を取得します。
  • 多様な入力をサポートする: すべてのインタラクティブなウィジェットが最小 48x48 論理ピクセルのタッチターゲットサイズを持つようにします。マウスのホバー、トラックパッドのジェスチャー、キーボードナビゲーションに応答するようにレイアウトコンポーネントを最適化します。

ワークフロー: アダプティブレイアウトの構築

レスポンシブレイアウトを構築して検証するには、次のチェックリストに従ってください。

  • ターゲットレイアウトを特定する: カスタムのレスポンシブ動作が必要なページまたはセクションを選択します。
  • LayoutBuilder を実装する: サブツリーを LayoutBuilder ウィジェットでラップします。
  • 制約の幅を抽出する: ビルダーコールバックで constraints.maxWidth を読み取ります。
  • 明確なブレークポイントを定義する: 確立されたブレークポイント(例: タブレット/大画面レイアウトでは 600 論理ピクセル)を使用します。
  • サブツリーを定義する:
    • maxWidth > 600 の場合: マルチペインまたは分割画面レイアウトを返します(例: ナビゲーションサイドバーをコンテンツの横に配置する)。
    • maxWidth <= 600 の場合: シングルカラムまたはモバイル最適化レイアウトを返します。
  • 検査して検証する: アプリを実行し、ウィンドウを動的にサイズ変更するかシミュレーターを回転させて、レイアウトのオーバーフローやトランジション時のジャンクが発生しないことを確認します。

ワークフロー: 大画面向けの最適化

大画面上での可読性を最適化するには、次のチェックリストに従ってください。

  • 引き伸ばされたコンポーネントを特定する: 画面幅全体に広がっている全画面のテキストブロック、入力フォーム、リスト項目を見つけます。
  • 制約を適用する: 引き伸ばされたコンテンツを ConstrainedBox ウィジェットでラップします。
  • 最大制限を定義する: 読みやすさのための最大幅を設定します(例: BoxConstraints(maxWidth: 800.0))。
  • コンテンツを中央揃えにする: ConstrainedBox を Center ウィジェットでラップして、ワイドモニターでもレイアウトのバランスを保ちます。
  • 検査して検証する: デスクトップビルドまたはタブレットシミュレーターでレイアウトをテストし、可読性が優れたまま保たれ、コンテンツが引き伸ばされないことを確認します。

例

ブレークポイントを用いたアダプティブレイアウト

この例では、利用可能なスペースに応じて、モバイル最適化レイアウトと大画面向けの分割ペインレイアウトを切り替える方法を示します。

import 'package:flutter/material.dart';

class AdaptiveHomeLayout extends StatelessWidget {
  const AdaptiveHomeLayout({super.key});

  static const double tabletBreakpoint = 600.0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: LayoutBuilder(
        builder: (context, constraints) {
          if (constraints.maxWidth > tabletBreakpoint) {
            return _buildTabletLayout();
          }
          return _buildMobileLayout();
        },
      ),
    );
  }

  Widget _buildTabletLayout() {
    return Row(
      children: [
        const SizedBox(
          width: 240,
          child: NavigationSidebar(),
        ),
        const VerticalDivider(width: 1),
        Expanded(
          child: const MainContentArea(),
        ),
      ],
    );
  }

  Widget _buildMobileLayout() {
    return const MainContentArea();
  }
}

class NavigationSidebar extends StatelessWidget {
  const NavigationSidebar({super.key});

  @override
  Widget build(BuildContext context) {
    return ListView(
      padding: const EdgeInsets.symmetric(vertical: 16),
      children: const [
        ListTile(
          leading: Icon(Icons.dashboard),
          title: Text('Dashboard'),
        ),
        ListTile(
          leading: Icon(Icons.settings),
          title: Text('Settings'),
        ),
      ],
    );
  }
}

class MainContentArea extends StatelessWidget {
  const MainContentArea({super.key});

  @override
  Widget build(BuildContext context) {
    return const Center(
      child: Text('Main Content Area'),
    );
  }
}
ワイド画面での幅の制約

この例では、プロフィールまたは設定フォームがワイドなデスクトップ画面に不格好に引き伸ばされるのを防ぎます。

import 'package:flutter/material.dart';

class ConstrainedProfileForm extends StatelessWidget {
  const ConstrainedProfileForm({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Edit Profile'),
      ),
      body: Center(
        child: ConstrainedBox(
          constraints: const BoxConstraints(
            maxWidth: 600.0,
          ),
          child: ListView(
            padding: const EdgeInsets.all(24.0),
            children: [
              const TextFormField(
                decoration: InputDecoration(
                  labelText: 'Full Name',
                  border: OutlineInputBorder(),
                ),
              ),
              const SizedBox(height: 16),
              const TextFormField(
                decoration: InputDecoration(
                  labelText: 'Email Address',
                  border: OutlineInputBorder(),
                ),
              ),
              const SizedBox(height: 24),
              ElevatedButton(
                onPressed: () {},
                child: const Text('Save Changes'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

コメント