Flutter でのタブレット対応を考える Readable Content Guide

iOS には Readable Content Guide という考え方があり、iPad などの大きな端末では横幅いっぱいにコンテンツが表示されないようになっている。
参考: iOSでの読みやすい幅 - クックパッド開発者ブログ

Flutter で iPad を意識せず実装するとボタンが横に間伸びしていたり、横幅の比率で表示するコンテンツが異常に大きくなってしまう。最悪レイアウトが崩壊する。
それをいい感じに表示するために横幅の最大値を制限した Widget を作成する。

普通に実装した場合

横幅いっぱいに表示するボタンが2個並んでいる画面。

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('example app'),
        ),
        body: Container(
          padding: const EdgeInsets.only(left: 18, right: 18),
          child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                const SizedBox(height: 18),
                SizedBox(
                    height: 88,
                    width: double.infinity,
                    child: ElevatedButton(
                        child: const Text('ログインする'), onPressed: () {})),
                const SizedBox(height: 32),
                SizedBox(
                    height: 88,
                    width: double.infinity,
                    child: ElevatedButton(
                      child: const Text('新規会員登録する'),
                      onPressed: () {},
                    )),
              ]),
        ),
      ),
    );
  }
}

画面はこのように表示される。幅が広い画面だと間伸びして表示されてしまいダサい。

通常幅の画面 横幅の広い画面
f:id:star__hoshi:20220225101852p:plain f:id:star__hoshi:20220225101932p:plain

画面の最大幅を制限する

ReadableWidthContainer という、 maxWidth を設定しているだけの Widget を作成する。

import 'package:flutter/material.dart';

class ReadableWidthContainer extends StatelessWidget {
  const ReadableWidthContainer({Key? key, required this.child})
      : super(key: key);
  final Widget child;

  @override
  Widget build(BuildContext context) {
    return Container(
      alignment: Alignment.center,
      child: ConstrainedBox(
          constraints: const BoxConstraints(maxWidth: 600), child: child),
    );
  }
}

これを利用して画面を表示すると以下のように表示される。コンテンツが中央によって、600を最大幅として表示される。

通常幅の画面 横幅の広い画面
f:id:star__hoshi:20220225102508p:plain f:id:star__hoshi:20220225102510p:plain

(ボタンが2つしか表示されていないボタンだとメリットが分かりにくいが…)

ReadableWidthContainerを利用した main.dart は以下。

import 'package:flutter/material.dart';

import '/components/readable_width_container.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('example app'),
        ),
        body: ReadableWidthContainer(
          child: Container(
            padding: const EdgeInsets.only(left: 18, right: 18),
            child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  const SizedBox(height: 18),
                  SizedBox(
                      height: 88,
                      width: double.infinity,
                      child: ElevatedButton(
                          child: const Text('ログインする'), onPressed: () {})),
                  const SizedBox(height: 32),
                  SizedBox(
                      height: 88,
                      width: double.infinity,
                      child: ElevatedButton(
                        child: const Text('新規会員登録する'),
                        onPressed: () {},
                      )),
                ]),
          ),
        ),
      ),
    );
  }
}

おわり

これでサイズの大きな端末でもレイアウトが壊れずに実装できるようになった。