FlutterでAppBarにボタンを置く際のエラー対策

Flutter_icon Flutter
この記事は約21分で読めます。

この記事は以下の方を対象に書いています。

  • AppBarにたくさんボタンを配置したいけど、画面サイズを小さくするとエラーになる

AppBarにボタンを配置

まずは、AppBarにボタンをたくさん配置してみます。

AppBarにボタンを配置する方法は2種類あります。

  • titleプロパティに配置
  • actionsプロパティに配置

今回は一般的なactionsにボタンを4つ配置してみます。

import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
   // これでデバック用のリボンを消せる
      debugShowCheckedModeBanner: false,
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
     // ボタンを配置
        actions: [
          ElevatedButton(onPressed: () {}, child: const Text('AAA')),
          ElevatedButton(onPressed: () {}, child: const Text('BBB')),
          ElevatedButton(onPressed: () {}, child: const Text('CCC')),
          ElevatedButton(onPressed: () {}, child: const Text('DDD')),
        ],
      ),
    );
  }
}
flutter-sample-1
AppBarにボタンを配置

これで、AppBarにボタンの配置ができました。

ちなみに、titleプロパティにボタンを置きたい場合はこのようになります。

appBar: AppBar(
  title: Row(
    mainAxisAlignment: MainAxisAlignment.start,
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      Text(widget.title),
      const SizedBox(width: 30,),
      ElevatedButton(onPressed: () {}, child: const Text('AAA')),
      ElevatedButton(onPressed: () {}, child: const Text('BBB')),
      ElevatedButton(onPressed: () {}, child: const Text('CCC')),
      ElevatedButton(onPressed: () {}, child: const Text('DDD')),
    ],
  ),
),
flutter-sample-2
titleプロパティにボタンを配置

エラー発生

actionsにボタンを配置できましたが、問題点があります。

※titleプロパティにボタンを置いた場合も同様です。

画面サイズが小さいときに以下のエラーが発生します。

flutter-sample-3
エラー

このエラーは、ボタンなどのウィジェットが描画できなくなった際に発生します。

Webなら勝手にスクロールしてくれたりしますが、Flutterではそうはいきません。

自分で対応しなくてはなりません。

今回はその対応策を紹介します。

エラー対応策

エラーの対応策としては、「画面サイズが一定のサイズ以下になったら消す」です。

ただ、消すとそのボタンの役割をどこかで対応する必要があります。

このような対応を「レスポンシブ対応」と呼びます。

今回は、画面サイズが一定以下になったら

ドロワーを追加してそこにボタンを移動させたいと思います。

画面サイズによって描画内容を変更

まずは、画面サイズによってボタンを描画したり、しなかったらするコードを書きます。

画面サイズは「MediaQuery」を使用して取得します。

actions: [
  MediaQuery.of(context).size.width > 600
  ?
      Row(
        children: [
          ElevatedButton(onPressed: () {}, child: const Text('AAA')),
          ElevatedButton(onPressed: () {}, child: const Text('BBB')),
          ElevatedButton(onPressed: () {}, child: const Text('CCC')),
          ElevatedButton(onPressed: () {}, child: const Text('DDD')),
        ],
      )
      :
      Container()
],
flutter-sample-4
画面サイズによって描画内容を変更

ドロワーにボタンを移動

次は、画面サイズが小さいときにAppBarのボタンを描画せずに、

ドロワーにボタンを作成してみます。

import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
      debugShowCheckedModeBanner: false,
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
        actions: [
          MediaQuery.of(context).size.width > 600
          ?
              Row(
                children: [
                  ElevatedButton(onPressed: () {}, child: const Text('AAA')),
                  ElevatedButton(onPressed: () {}, child: const Text('BBB')),
                  ElevatedButton(onPressed: () {}, child: const Text('CCC')),
                  ElevatedButton(onPressed: () {}, child: const Text('DDD')),
                ],
              )
              :
              Container()
        ],
      ),
    // ドロワーの設定
      drawer: MediaQuery.of(context).size.width > 600
          ?
          null // null を返すとドロワーのアイコンが表示されない
          :
          Drawer(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              mainAxisAlignment: MainAxisAlignment.start,
              mainAxisSize: MainAxisSize.min,
              children: [
                ElevatedButton(onPressed: () {}, child: const Text('AAA')),
                ElevatedButton(onPressed: () {}, child: const Text('BBB')),
                ElevatedButton(onPressed: () {}, child: const Text('CCC')),
                ElevatedButton(onPressed: () {}, child: const Text('DDD')),
              ],
            )
          ),
    );
  }
}
flutter-sample-5
レスポンシブ対応後

これでAppBarのレスポンシブ対応は完了です。

あとはデザインですね!

デザイン調整

最後にデザインがデフォルトのままなので、修正しておきます。

import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
      debugShowCheckedModeBanner: false,
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
        backgroundColor: Colors.white,
        foregroundColor: Colors.black54,
        actions: [
          MediaQuery.of(context).size.width > 600
          ?
              Row(
                children: [
                  Container(
                    padding: const EdgeInsets.all(10),
                    child: TextButton.icon(
                      onPressed: () {},
                      icon: const Icon(Icons.add),
                      label: const Text('AAA'),
                      style: ButtonStyle(
                          backgroundColor: MaterialStateProperty.all(Colors.black45),
                          foregroundColor: MaterialStateProperty.all(Colors.white)
                      ),
                    ),
                  ),
                  Container(
                    padding: const EdgeInsets.all(10),
                    child: TextButton.icon(
                      onPressed: () {},
                      icon: const Icon(Icons.search),
                      label: const Text('BBB'),
                      style: ButtonStyle(
                          backgroundColor: MaterialStateProperty.all(Colors.black45),
                          foregroundColor: MaterialStateProperty.all(Colors.white)
                      ),
                    ),
                  ),
                  Container(
                    padding: const EdgeInsets.all(10),
                    child: TextButton.icon(
                      onPressed: () {},
                      icon: const Icon(Icons.settings),
                      label: const Text('CCC'),
                      style: ButtonStyle(
                          backgroundColor: MaterialStateProperty.all(Colors.black45),
                          foregroundColor: MaterialStateProperty.all(Colors.white)
                      ),
                    ),
                  ),
                  Container(
                    padding: const EdgeInsets.all(10),
                    child: TextButton.icon(
                      onPressed: () {},
                      icon: const Icon(Icons.task_alt),
                      label: const Text('DDD'),
                      style: ButtonStyle(
                          backgroundColor: MaterialStateProperty.all(Colors.black45),
                          foregroundColor: MaterialStateProperty.all(Colors.white)
                      ),
                    ),
                  ),
                ],
              )
              :
              Container()
        ],
      ),
      drawer: MediaQuery.of(context).size.width > 600
          ?
          null
          :
          Drawer(
            child: Container(
              decoration: const BoxDecoration(
                gradient: LinearGradient(
                  colors: [Colors.blue, Colors.amber],
                  begin: Alignment.topLeft,
                  end: Alignment.bottomRight
                )
              ),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                mainAxisAlignment: MainAxisAlignment.start,
                mainAxisSize: MainAxisSize.min,
                children: [
                  Container(
                    alignment: Alignment.center,
                    padding: const EdgeInsets.symmetric(vertical: 30), // 縦方向にスペース
                    decoration: const BoxDecoration(
                        color: Colors.transparent // 背景を透明に
                    ),
                    child: Text(widget.title, style: TextStyle(color: Colors.white, fontSize: 20, fontStyle: FontStyle.italic)),
                  ),
                  Container(
                    padding: const EdgeInsets.all(10),
                    child: TextButton.icon(
                      onPressed: () {},
                      icon: const Icon(Icons.add),
                      label: const Text('AAA'),
                      style: ButtonStyle(
                        foregroundColor: MaterialStateProperty.all(Colors.white)
                      ),
                    ),
                  ),
                  Container(
                    padding: const EdgeInsets.all(10),
                    child: TextButton.icon(
                      onPressed: () {},
                      icon: const Icon(Icons.search),
                      label: const Text('BBB'),
                      style: ButtonStyle(
                          foregroundColor: MaterialStateProperty.all(Colors.white)
                      ),
                    ),
                  ),
                  Container(
                    padding: const EdgeInsets.all(10),
                    child: TextButton.icon(
                      onPressed: () {},
                      icon: const Icon(Icons.settings),
                      label: const Text('CCC'),
                      style: ButtonStyle(
                          foregroundColor: MaterialStateProperty.all(Colors.white)
                      ),
                    ),
                  ),
                  Container(
                    padding: const EdgeInsets.all(10),
                    child: TextButton.icon(
                      onPressed: () {},
                      icon: const Icon(Icons.task_alt),
                      label: const Text('DDD'),
                      style: ButtonStyle(
                          foregroundColor: MaterialStateProperty.all(Colors.white)
                      ),
                    ),
                  ),
                ],
              ),
            )
          ),
    );
  }
}
flutter-sample-6
レスポンシブ対応(デザイン修正)

最後に

どうでしたでしょうか?

解決策になれば幸いです。

エラー対応は面倒ですが、そのあとのデザイン変更を楽しみに頑張ってください!

コメント

タイトルとURLをコピーしました