【Flutter】電卓・計算機アプリの作成 2

2020年5月29日金曜日

Flutter

t f B! P L
トップ

前回に引き続き、Flutterの勉強として電卓アプリ(計算機)を作ったので、その作り方を順を追って、全3回で説明していきます。



今回は「ボタンUIと描写更新」を実装していきます。


では始めていきましょう。


表示(_TextFiledState)

今から表示されている文字の更新を行うメソッドを作っていきます。
このメソッドはキーボタン(Button)から呼ぶ予定です。

メソッドの作成

文字の追加を行う_UpdateText()を作成します。
void _UpdateText(String letter){
    if(letter == '=' || letter == 'C')
        _expression = '';
    else
        _expression += letter;
}

しかしこれではUI描写の更新は行われません。
UI描写の更新にはsetState()メソッドが必要です。

setState()
任意で呼べるメソッド
Widgetツリーを再構成して、変更を反映させる
簡単な変数の代入だけでなく非同期処理でも使える

Flutterの基礎(Qiita)

なので、先ほどのコードを少し変更します。

void _UpdateText(String letter){
    setState(() {
        if(letter == '=' || letter == 'C')
            _expression = '';
        else
            _expression += letter;
    });
}

ここまでで、main.dart全体は以下のようになりました。

// 省略
class TextField extends StatefulWidget {
  _TextFiledState createState() => _TextFiledState();
}

class _TextFiledState extends State<TextField> {
  String _expression = '1+1';

/////追加/////
void _UpdateText(String letter){
    setState(() {
        if(letter == '=' || letter == 'C')
            _expression = '';
        else
            _expression += letter;
    });
}
/////ここまで/////

  @override
  Widget build(BuildContext context) {
  //省略

これでUIの描写更新を行うメソッド(_UpdateText())の作成はできました。
次にそのメソッドを呼び出すところを作っていきます。

Streamを作る

_UpdateText()はButtonから呼び出したいので、_UpdateTextをstaticに。。とやってみましたが、できませんでした。


どうやら setState() はstaticメンバにできないようです。


そこで、今回はStreamを使ってみることにしました。
(多分もっとよい方法があるはず。。)


Streamについては
動かして理解する!dartのStreamとrxdart!
を参考にしました。


Streamの存在をついさっき知ったばかりなので、私の理解は...
    StreamってRxDartの元らしい、、Rx...?、UniRx(UnityC#)...?、ふーん、なるほど()
って感じです。はい。


ではコードを書いていきます。


まずimport文を書きます。

import 'dart:async';

続いて、Streamを生成します。
クラスからのアクセスが出来るように [static] かつ [ _ (アンダースコア) なし]にします。

static final controller = StreamController<string>();

Streamの内容の登録はinitState()で行わせます。

initState()
最初に一度呼ばれる
Widgetツリーの初期化を実行

Flutterの基礎(Qiita)

static final controller = StreamController<String>();
@override
void initState() {
    controller.stream.listen((event) => _UpdateText(event));
}

TextField付近のコードの確認
import 'dart:async';
import 'package:flutter/material.dart';

void main() => runApp(MyApp());
// 〜省略〜
// 表示
class TextField extends StatefulWidget {
    _TextFiledState createState() => _TextFiledState();
}
class _TextFiledState extends State<TextField> {
    String _expression = '1+1';

    void _UpdateText(String letter){
        setState(() {
            if(letter == '='|| letter == 'C')
                _expression = '';
            else
                _expression += letter;
        });
    }

    @override
    Widget build(BuildContext context) {
        return Expanded(
            flex: 1,
            child: Container(
                child: Align(
                    alignment: Alignment.centerRight,
                    child: Text(
                        _expression,
                        style: TextStyle(
                            fontSize: 64.0,
                        ),
                    ),
                ),
            )
        );
    }

    static final controller = StreamController<String>();
    @override
    void initState() {
        controller.stream.listen((event) => _UpdateText(event));
    }
}
//==============================================================================
// キーボード
class Keyboard extends StatelessWidget {
// 〜省略〜

キーボード(Button)

Buttonクラスにボタンを押したときの反応を書いていきます。


現在、FlatButtonから次のように警告が出ていると思います。(AndroidStudioなら)
[PIC 1]

そこに書いてある通り、OnPressed()を追加していきます。
パラメータの値にボタンを押したときの反応を書きます。

// 〜省略〜
child: FlatButton(
    child: Center(
        child: Text(
            _key,
            style: TextStyle(fontSize: 46.0),
        ),
    ),
    onPressed: (){
        _TextFiledState.controller.sink.add(_key);
    },
// 〜省略〜

これで、ボタンを押したときの反応を設定できました。
(これでボタン入力が働くという訳ではありません。次の「動かす」で動くようになります。)


OnPressedを設定したことによって、グレーアウトしていたボタンが元の色になりました。

[PIC 2]

ちょっと色が濃くて煩いので、薄くしておきます。


// 〜省略〜
child: Text(
    _key,
    style: TextStyle(
        fontSize: 46.0,
        color: Colors.black54,
    ),
// 〜省略〜

Button付近のコードの確認
// 〜省略〜
                )
            )
        );
    }
}
// キーボタン
class Button extends StatelessWidget {
    final _key;
    Button(this._key);
    @override
    Widget build(BuildContext context) {
        return Container(
            child: FlatButton(
                child: Center(
                    child: Text(
                        _key,
                        style: TextStyle(
                            fontSize: 46.0,
                            color: Colors.black54,
                        )
                    ),
                ),
                onPressed: (){
                    _TextFiledState.controller.sink.add(_key);
                },
            )
        );
    }
}

動かす

OnPressedで発行したStreamの購読はinitState()で行われます。
そして、initState()はアプリ起動時に呼ばれます。


つまり、HoReloadではinitState()呼ばれません。


再ビルドします。

[PIC 4]

いい感じに動いています。

[PIC 5]

まとめ

UIボタンを押すと画面が更新されるようになりました。
次回は電卓機能の中心、計算機能の実装を行います。

次回(電卓・計算機アプリの作成 3)

現時点のmain.dart
import 'dart:async';
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
        return MaterialApp(
            home: Scaffold(
                body: Column(
                    mainAxisAlignment: MainAxisAlignment.start,
                    children: [
                        TextField(),
                        Keyboard(),
                    ],
                )
            )
        );
    }
}
//==============================================================================
// 表示
class TextField extends StatefulWidget {
    _TextFiledState createState() => _TextFiledState();
}
class _TextFiledState extends State<TextField> {
    String _expression = '';

    void _UpdateText(String letter){
        setState(() {
            if(letter == '=' || letter == 'C')
                _expression = '';
            else
                _expression += letter;
        });
    }

    @override
    Widget build(BuildContext context) {
        return Expanded(
            flex: 1,
            child: Container(
                child: Align(
                    alignment: Alignment.centerRight,
                    child: Text(
                        _expression,
                        style: TextStyle(
                            fontSize: 64.0,
                        ),
                    ),
                ),
            )
        );
    }

    static final controller = StreamController<String>();
    @override
    void initState() {
        controller.stream.listen((event) => _UpdateText(event));
   }
}
//==============================================================================
// キーボード
class Keyboard extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
        return Expanded(
            flex: 2,
            child: Center(
                child: Container(
                    color: const Color(0xff87cefa),
                    child: GridView.count(
                        crossAxisCount: 4,
                        mainAxisSpacing: 3.0,
                        crossAxisSpacing: 3.0,
                        children: [
                            '7', '8', '9', '÷',
                            '4', '5', '6', '×',
                            '1', '2', '3', '-',
                            'C', '0', '=', '+',
                        ].map((key) {
                            return GridTile(
                                child: Button(key),
                            );
                        }).toList(),
                    ),
                )
            )
        );
    }
}
// キーボタン
class Button extends StatelessWidget {
    final _key;
    Button(this._key);
    @override
    Widget build(BuildContext context) {
        return Container(
            child: FlatButton(
                child: Center(
                    child: Text(
                        _key,
                        style: TextStyle(
                            fontSize: 46.0,
                            color: Colors.black54,
                        ),
                    ),
                ),
                onPressed: (){
                    _TextFiledState.controller.sink.add(_key);
                },
            )
        );
    }
}

自己紹介

自分の写真
県立高校理数科2年
Unity2年目
Twitter
Note

人気の投稿

[Vim] coc.nvim + coc-clangdの構文チェックでboostのエラーが発生する

C++の拡張ライブラリ、Boost。 AtCoderでも使用が認められているライブラリです。 様々な便利機能が搭載されています。 競技プログラミングで使えるBoostライブラリ 初級編 から一部抜粋すると、 boost...

QooQ