Skip to content

電卓のUIを作成する(後編)

公開日:January 31, 2025更新日:January 31, 2025
FlutterDartCoding📄

前編では、電卓アプリの全体レイアウトを構築し、結果表示部分とボタン部分のUIを作成しました。後編では、ボタンの配置を調整し、カスタムウィジェットを使ってボタンを共通化します。

4. ボタンの配置とデザイン

前編で作成したボタン部分のレイアウトは、まだ改善の余地があります。ボタンの間隔が狭すぎることと、イコールボタンが他のボタンと同じサイズであることが気になります。

ボタンの間隔を調整するには、RowmainAxisAlignment プロパティと crossAxisAlignment プロパティを使用します。

  • mainAxisAlignment: 主軸方向(Row の場合は水平方向)の配置を制御する
  • crossAxisAlignment: 交差軸方向(Row の場合は垂直方向)の配置を制御する
dart
Container(
  height: 350.0, // Expandedを削除したためContainerの高さを調整
  color: Colors.black,
  child: Column(
    children: <Widget>[
      Expanded(
        child: Row(
          crossAxisAlignment: CrossAxisAlignment.stretch, // 追加
          children: <Widget>[
            _buildButton('7'),
            _buildButton('8'),
            _buildButton('9'),
            _buildButton('/'),
          ],
        ),
      ),
      Expanded(
        child: Row(
          crossAxisAlignment: CrossAxisAlignment.stretch, // 追加
          children: <Widget>[
            _buildButton('4'),
            _buildButton('5'),
            _buildButton('6'),
            _buildButton('x'),
          ],
        ),
      ),
      Expanded(
        child: Row(
          crossAxisAlignment: CrossAxisAlignment.stretch, // 追加
          children: <Widget>[
            _buildButton('1'),
            _buildButton('2'),
            _buildButton('3'),
            _buildButton('-'),
          ],
        ),
      ),
      Expanded(
        child: Row(
          crossAxisAlignment: CrossAxisAlignment.stretch, // 追加
          children: <Widget>[
            _buildButton('.'),
            _buildButton('0'),
            _buildButton('C'),
            _buildButton('+'),
          ],
        ),
      ),
      Expanded(
        child: Row(
          crossAxisAlignment: CrossAxisAlignment.stretch, // 追加
          children: <Widget>[
            _buildButton('=', flex: 2, color: Colors.orange), // 変更
          ],
        ),
      ),
    ],
  ),
),

各々のRowウィジェットをExpandedでラップしました。また、RowcrossAxisAlignment プロパティを CrossAxisAlignment.stretch に設定しました。これにより、ボタンが垂直方向にも広がるようになります。

_buildButton関数にflexcolorの引数を追加し、ボタンの大きさと色を変えられるように変更しました。

dart
Widget _buildButton(
  String buttonText, {
  int flex = 1,
  Color color = Colors.grey,
}) {
  return Expanded(
    flex: flex, // 追加
    child: ElevatedButton(
      onPressed: () {},
      style: ElevatedButton.styleFrom(
        backgroundColor: color,
        padding: const EdgeInsets.all(24.0),
        shape: const RoundedRectangleBorder(
          side: BorderSide(
            color: Colors.black, //枠線の色
            width: 1, //枠線の太さ
            style: BorderStyle.solid, //枠線の種類
          ),
        ),
      ),
      child: Text(
        buttonText,
        style: const TextStyle(
          fontSize: 24.0,
          color: Colors.white,
        ),
      ),
    ),
  );
}

flex 引数を使って、イコールボタンの幅を他のボタンの2倍にしています。color引数はボタンの色を変えられるようにしています。

このコードを実行すると、以下のような画面が表示されます。

ボタンの間隔が広がり、イコールボタンが他のボタンの2倍の幅になりました。

5. カスタムウィジェットでボタンを共通化

現在のコードでは、ボタンを作成するたびに ElevatedButton ウィジェットを記述しています。これをカスタムウィジェットとして共通化することで、コードの再利用性を高め、メンテナンスを容易にします。

dart
class CalculatorButton extends StatelessWidget {
  const CalculatorButton({
    super.key,
    required this.text,
    this.flex = 1,
    required this.onPressed,
    this.color = Colors.grey,
  });

  final String text;
  final int flex;
  final Color color;
  final VoidCallback onPressed;

  @override
  Widget build(BuildContext context) {
    return Expanded(
      flex: flex,
      child: ElevatedButton(
        onPressed: onPressed,
        style: ElevatedButton.styleFrom(
          backgroundColor: color,
          padding: const EdgeInsets.all(24.0),
          shape: const RoundedRectangleBorder(
            side: BorderSide(
              color: Colors.black,
              width: 1,
              style: BorderStyle.solid,
            ),
          ),
        ),
        child: Text(
          text,
          style: const TextStyle(
            fontSize: 24.0,
            color: Colors.white,
          ),
        ),
      ),
    );
  }
}

CalculatorButton という名前の新しいステートレスウィジェットを作成しました。このウィジェットは、ボタンに表示するテキスト、フレックス値、ボタンの色、onPressedコールバックを引数として受け取ります。

_buildButton 関数を削除し、CalculatorButton ウィジェットを使うように変更します。

dart
Container(
  height: 350.0,
  color: Colors.black,
  child: Column(
    children: <Widget>[
      Expanded(
        child: Row(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: <Widget>[
            CalculatorButton(
              text: '7',
              onPressed: () {},
            ),
            CalculatorButton(
              text: '8',
              onPressed: () {},
            ),
            CalculatorButton(
              text: '9',
              onPressed: () {},
            ),
            CalculatorButton(
              text: '/',
              onPressed: () {},
            ),
          ],
        ),
      ),
      Expanded(
        child: Row(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: <Widget>[
            CalculatorButton(
              text: '4',
              onPressed: () {},
            ),
            CalculatorButton(
              text: '5',
              onPressed: () {},
            ),
            CalculatorButton(
              text: '6',
              onPressed: () {},
            ),
            CalculatorButton(
              text: 'x',
              onPressed: () {},
            ),
          ],
        ),
      ),
      Expanded(
        child: Row(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: <Widget>[
            CalculatorButton(
              text: '1',
              onPressed: () {},
            ),
            CalculatorButton(
              text: '2',
              onPressed: () {},
            ),
            CalculatorButton(
              text: '3',
              onPressed: () {},
            ),
            CalculatorButton(
              text: '-',
              onPressed: () {},
            ),
          ],
        ),
      ),
      Expanded(
        child: Row(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: <Widget>[
            CalculatorButton(
              text: '.',
              onPressed: () {},
            ),
            CalculatorButton(
              text: '0',
              onPressed: () {},
            ),
            CalculatorButton(
              text: 'C',
              onPressed: () {},
            ),
            CalculatorButton(
              text: '+',
              onPressed: () {},
            ),
          ],
        ),
      ),
      Expanded(
        child: Row(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: <Widget>[
            CalculatorButton(
              text: '=',
              flex: 2,
              onPressed: () {},
              color: Colors.orange,
            ),
          ],
        ),
      ),
    ],
  ),
),

CalculatorButton ウィジェットを使うことで、ボタン作成部分のコードがすっきりしました。


コラム:Flutterのウィジェットのライフサイクル

Flutterのウィジェットにはライフサイクルがあります。ステートフルウィジェットのライフサイクルは、以下のようになっています。

  1. createState(): ステートフルウィジェットが作成されると、createState メソッドが呼ばれ、State オブジェクトが作成されます。
  2. initState(): State オブジェクトが作成された直後に、initState メソッドが呼ばれます。このメソッドは一度だけ呼ばれ、初期化処理などに使われます。
  3. didChangeDependencies(): initState の後、および依存するオブジェクト(InheritedWidget など)が変更されたときに、didChangeDependencies メソッドが呼ばれます。
  4. build(): UIを構築するために、build メソッドが呼ばれます。このメソッドは、setState が呼ばれた後など、頻繁に呼ばれる可能性があります。
  5. didUpdateWidget(): ウィジェットの設定が変更されたときに、didUpdateWidget メソッドが呼ばれます。
  6. setState(): State オブジェクトの状態を変更し、UIを再構築するために、setState メソッドを呼び出します。
  7. deactivate(): State オブジェクトがウィジェットツリーから削除される前に、deactivate メソッドが呼ばれます。
  8. dispose(): State オブジェクトがウィジェットツリーから完全に削除されるときに、dispose メソッドが呼ばれます。リソースの解放などに使われます。

これらのライフサイクルメソッドを適切にオーバーライドすることで、ウィジェットの状態変化に応じた処理を実装できます。