【Flutter】ボタンを押下してそのボタンの表示を変える(簡単バージョン)

目次

概要

MVVMを意識した実用的なコードはこちら
このページはMVVMなど実用的なコードを意識せず、Flutterのデフォルトのコードをもとに記載している。

一つのファイルで作れるので、比較的理解しやすいだろう。

ソースコード

トランプカードのオブジェクトクラス

import 'TrumpMark.dart';

class TrumpCard {
  bool isSelected;
  int number;
  TrumpMark mark;

  TrumpCard(this.isSelected, this.number, this.mark);

  void tap() {
    this.isSelected = !this.isSelected;
  }
}

トランプマークの列挙体

enum TrumpMark {
  spade,
  heart,
  dia,
  club
}

extension TrumpMarkAdditional on TrumpMark {
  String get trumpMarkString {
    switch (this) {
      case TrumpMark.spade:
        return '♠️';
      case TrumpMark.heart:
        return '❤️';
      case TrumpMark.dia:
        return '♦️';
      case TrumpMark.club:
        return '♣️';
    }
  }
}

アプリ実行部分

import 'package:concentration/TrumpButton/TrumpButtonView.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

import 'TrumpButton/TrumpButtonViewSimple.dart';

void main() {
  runApp(ProviderScope(child: MyApp()));
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
        useMaterial3: true,
      ),
      home: const TrumpButtonViewSimple(title: 'Flutter Demo Home Page'),
    );
  }
}

View表示部分

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

import '../Common/TrumpCard.dart';
import '../Common/TrumpMark.dart';

class StateTrumpButton extends StatefulWidget {
  final Function() notifyParent;
  TrumpCard trumpCard;
  StateTrumpButton({super.key, required this.notifyParent, required this.trumpCard});

  @override
  State<StatefulWidget> createState() => _StateTrumpButton(notifyParent: notifyParent, trumpCard: trumpCard);
}

class _StateTrumpButton extends State<StateTrumpButton> {
  final Function() notifyParent;
  TrumpCard trumpCard;

  _StateTrumpButton({required this.notifyParent, required this.trumpCard});

  @override
  Widget build(BuildContext context) {
    return OutlinedButton(
        onPressed: () { notifyParent(); },
        style: OutlinedButton.styleFrom(
          minimumSize: Size(MediaQuery.of(context).size.width/4, 40),
          backgroundColor: Colors.white10,
          foregroundColor: Colors.black,
        ),
        child: trumpDisplay(trumpCard)
    );
  }

  Widget trumpDisplay(TrumpCard trumpCard) {
    if (trumpCard.isSelected) {
      return Text("${trumpCard.mark.trumpMarkString} ${trumpCard.number + 1}");
    } else {
      return Text("?", textAlign: TextAlign.center);
    }
  }
}

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

  @override
  State<TrumpButtonViewSimple> createState() => _TrumpButtonViewSimpleState();
}

class _TrumpButtonViewSimpleState extends State<TrumpButtonViewSimple> {
  List<TrumpCard> trumpCards = [];

  @override
  Widget build(BuildContext context) {
    final itemsPerRow = 13;

    // 52枚分のトランプのオブジェクトを生成
    initTrumpCards();
    // トランプボタンの配列を作成
    final List<StateTrumpButton> trumpButtons = createTrumpButtons();
    // トランプをマークに応じて13枚ずつ分ける
    final slicedTrumpButtons = trumpButtons.slices(itemsPerRow).toList();

    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: LayoutBuilder(
          builder: (context, constraints) => SingleChildScrollView(
            physics: AlwaysScrollableScrollPhysics(),
            child: ConstrainedBox(
              constraints: BoxConstraints(minWidth: constraints.maxWidth, minHeight: constraints.maxHeight),
              child: Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    for (var slicedTrumpButton in slicedTrumpButtons)
                      columnTrumpButtons(slicedTrumpButton),
                  ]
              ),
            ),
          )
      )
    );
  }

  void initTrumpCards() {
    List<TrumpCard> trumpCards = [];
    for(TrumpMark mark in TrumpMark.values) {
      for (int num = 0; num < 13; num ++) {
        trumpCards.add(TrumpCard(false, num, mark));
      }
    }
    this.trumpCards = trumpCards;
  }

  List<StateTrumpButton> createTrumpButtons() {
    List<StateTrumpButton> trumpButtons = [];

    for (TrumpCard trumpCard in trumpCards) {
      var trumpButton = StateTrumpButton(notifyParent: () { tapped(trumpCard); },
                                         trumpCard: trumpCard,);

      trumpButtons.add(trumpButton);
    }
    return trumpButtons;
  }

  void tapped(TrumpCard trumpCard) {
    setState(() {
      trumpCard.tap();
    });
  }

  Widget columnTrumpButtons(List<StateTrumpButton> trumpButtons) {
    return Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          for (var trumpButton in trumpButtons)
            trumpButton,
        ]
    );
  }
}

デモ動画

詳細

まずはプログラムの解説から。

大前提として、今回はトランプの内容を表示するためのクラスを新たに定義している。

class StateTrumpButton extends StatefulWidget {
  final Function() notifyParent;
  TrumpCard trumpCard;
  StateTrumpButton({super.key, required this.notifyParent, required this.trumpCard});

  @override
  State<StatefulWidget> createState() => _StateTrumpButton(notifyParent: notifyParent, trumpCard: trumpCard);
}

class _StateTrumpButton extends State<StateTrumpButton> {
  final Function() notifyParent;
  TrumpCard trumpCard;

  _StateTrumpButton({required this.notifyParent, required this.trumpCard});

  @override
  Widget build(BuildContext context) {
    return OutlinedButton(
        onPressed: () { notifyParent(); },
        style: OutlinedButton.styleFrom(
          minimumSize: Size(MediaQuery.of(context).size.width/4, 40),
          backgroundColor: Colors.white10,
          foregroundColor: Colors.black,
        ),
        child: trumpDisplay(trumpCard)
    );
  }

  Widget trumpDisplay(TrumpCard trumpCard) {
    if (trumpCard.isSelected) {
      return Text("${trumpCard.mark.trumpMarkString} ${trumpCard.number + 1}");
    } else {
      return Text("?", textAlign: TextAlign.center);
    }
  }
}

一つ一つ見ていこう。

StateTrumpButtonクラスと_StateTrumpButtonクラス

まずこれらのクラスの根幹部分はこの部分。

@override
Widget build(BuildContext context) {
  return OutlinedButton(
      onPressed: () { notifyParent(); },
      style: OutlinedButton.styleFrom(
        minimumSize: Size(MediaQuery.of(context).size.width/4, 40),
        backgroundColor: Colors.white10,
        foregroundColor: Colors.black,
      ),
      child: trumpDisplay(trumpCard)
  );
}

これは単に外枠のボタンを表示しているということ。
それに色々なものが付随している。

_StateTrumpButton全体に視野を広げてみよう

このクラスは以下のメンバ(変数)を持っている。

項目内容
final Function() notifyParent;ボタンがタップされた時の処理
TrumpCard trumpCard;TrumpCardクラスの
「選択されているか」「トランプのマーク」「トランプの数字」
といったデータを持っている。
Widget trumpDisplay(TrumpCard trumpCard)メソッドボタンの見た目を表す
OutlinedButtonのchildに設定するもの

ここまでは大丈夫かな?
つまり、以下をこのクラスで定義している

  • 外枠ボタンであること
  • ボタンの見た目
  • ボタンが押された時の処理

StateTrumpButtonクラスを見てみよう

ここでは_StateTrumpButtonに一つおまけをつけて表示している。
このextendsの後ろにある「StatefulWidget」だ。
これは、値が変わったら画面の表示内容も更新するということを表している。
こうすることで、ボタンがタップされた時の見た目を変更しているわけです。

ここまでで分かったこと

  • _StateTrumpButtonクラスでボタンの設定をしていること
  • StateTrumpButtonクラスでボタンを押した時に何か状態の変化があったら、そのボタンの表示を更新するようにすること

TrumpButtonViewSimpleと_TrumpButtonViewSimpleState

ここは画面の設定部分になる。
といっても、特に難し事は書いていない。

項目内容
@override Widget build(BuildContext context)画面の表示の設定を書いている
void initTrumpCards()トランプ52枚分のボタンのオブジェクトを生成する
List<StateTrumpButton> createTrumpButtons()トランプ52枚分のデータをもとにボタンを生成する
void tapped(TrumpCard trumpCard)ボタンがタップされた時の処理
Widget columnTrumpButtons(List trumpButtons)生成したボタンを羅列する

ボタンを押した時に値を変更して、それを画面に反映させる

それを行っているのが以下の部分

void tapped(TrumpCard trumpCard) {
  setState(() {
    trumpCard.tap();
  });
}

trumpCard.tap()メソッドは特に難しい事はしていない。
以下のように選択されているかどうかの状態を変更しているだけ。

void tap() {
  this.isSelected = !this.isSelected;
}

肝となるのはsetStateの部分。
このsetStateの中で変更されたものは、このメソッドが書かれているクラス内の範囲で画面へ反映される。
実際、このtrumpCardメソッドが書かれているStateTrumpButtonクラスの表示が更新されているのがわかるだろう。

参考ページ

  • 「」
  • 「」