Kodokon kodokon.com

アダプティブ:LayoutBuilder、MediaQuery、プラットフォーム

親の制約、ウィンドウ、そして各OSの慣習に適応するインターフェースを作りましょう。

9 分 · 3 問

このレッスンを Kodokon で開く

MediaQueryは物理的な画面ではなく、論理的なウィンドウを記述します。サイズ、padding(ノッチ、システムバー)、viewInsets(キーボード)、textScalerなどです。よくある落とし穴があります。MediaQuery.of(context)はウィジェットをこれらすべてのプロパティに購読させます。キーボードが開くとviewInsetsが変わり、購読しているすべてのウィジェットが再構築されます。幅しか読まないものも含めてです。Flutter 3.10以降、MediaQueryDataInheritedModelを通じて公開されます。的を絞ったアクセサMediaQuery.sizeOf(context)paddingOfviewInsetsOfなどは、ウィジェットを求められた側面だけに購読させます。熟練者の反射神経は、.of(context).sizeをコードベースから追放することです。

DART
import 'package:flutter/material.dart';

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

  @override
  Widget build(BuildContext context) {
    final width = MediaQuery.sizeOf(context).width;
    final columns = switch (width) {
      >= 900 => 4,
      >= 600 => 3,
      _ => 2,
    };
    return GridView.builder(
      gridDelegate:
          SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: columns,
      ),
      itemCount: 24,
      itemBuilder: (context, i) => Card(
        child: Center(child: Text('Tile $i')),
      ),
    );
  }
}
sizeOfと関係パターン。読みやすいブレークポイント。

MediaQueryは「ウィンドウはどれくらいの大きさか?」に答え、LayoutBuilderは「は自分にどれだけの場所をくれるか?」に答えます。この違いは再利用可能なコンポーネントにとって決定的です。あるときは全画面で、あるときは320 pxの列の中で表示されるパネルは、ウィンドウではなく自分の制約に反応しなければなりません。さもないと、実際は窮屈なのにタブレットの上にいると思い込んでしまいます。役立つ内部の詳細があります。LayoutBuilderのbuilderは、制約が判明しているレイアウトフェーズのあいだに実行されます。したがってその中で同期的なsetStateを起こすことはできません。フレームワークがそれを禁じています。

DART
import 'package:flutter/material.dart';

class MasterDetail extends StatelessWidget {
  const MasterDetail({
    super.key,
    required this.list,
    required this.detail,
  });

  final Widget list;
  final Widget detail;

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        if (constraints.maxWidth < 720) {
          return list;
        }
        return Row(
          children: [
            SizedBox(width: 320, child: list),
            const VerticalDivider(width: 1),
            Expanded(child: detail),
          ],
        );
      },
    );
  }
}
コンポーネントは、どこに置かれても、受け取った場所に適応する。

iOSとAndroidの違いについては、ウィジェット側の真実の源はTheme.of(context).platformです。これはdefaultTargetPlatformから導かれつつ、オーバーライドを尊重します。テストからiOSの描画を検証したり、別のプラットフォームをシミュレートしたりするのに欠かせません。多くの適応はすでにフレームワークに組み込まれています。スクロールの物理(iOSのバウンス対Androidのグロー)、ページ遷移、端からのスワイプで戻るジェスチャーなどです。.adaptiveコンストラクタ(Switch.adaptiveCircularProgressIndicator.adaptive)は、自分でCupertinoの描画に切り替わります。手動の分岐は、アクションのラベル付けやダイアログのボタンの位置のような、本当に食い違う慣習のためにとっておきましょう。

DART
import 'package:flutter/material.dart';

class SettingsTile extends StatelessWidget {
  const SettingsTile({
    super.key,
    required this.value,
    required this.onChanged,
  });

  final bool value;
  final ValueChanged<bool> onChanged;

  @override
  Widget build(BuildContext context) {
    final platform = Theme.of(context).platform;
    final isApple = platform == TargetPlatform.iOS ||
        platform == TargetPlatform.macOS;
    return ListTile(
      title: Text(
        isApple ? 'Settings' : 'Preferences',
      ),
      trailing: Switch.adaptive(
        value: value,
        onChanged: onChanged,
      ),
    );
  }
}
Switch.adaptiveはiOS/macOSでCupertinoのトグルを描画する。

理解度チェック

このレッスンの要点をしっかり覚えているか確認しましょう。

  1. MediaQuery.of(context)とMediaQuery.sizeOf(context)の違いは何ですか?
    • sizeOfはウィジェットをサイズの側面だけに購読させます。キーボードを開いてもそれを再構築しない
    • sizeOfは実際の画面のピクセルでの物理的なサイズを返す
    • ofは非推奨で、フレームワークから削除される
  2. レイアウトを適応させるのに、MediaQueryよりLayoutBuilderを選ぶべきなのはどんなときですか?
    • 判断がウィンドウのサイズではなく、親によって割り当てられた場所に依存するとき
    • デバイスの向きを知る必要があるとき
    • テーマの変更で再構築を避けたいとき
    • 決して。二つは交換可能
  3. ウィジェットのコードでPlatform.isIOSを避けるのはなぜですか?
    • dart:ioから来ており、ウェブでは利用できず、テストやテーマが使うオーバーライドを無視するから
    • defaultTargetPlatformより目に見えて遅いから
    • macOSでtrueを返すから