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

2020年5月31日日曜日

Flutter

t f B! P L

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



今回は「計算機能の実装」を行います。

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


Calculatorクラスの作成

calculation.dartを新規作成

計算を行うクラスとして Calculatorクラス を作成します。

前回までと同様main.dartに書くと、コードが長くなり、ゴチャゴチャしそうです。
なので、calculation.dartというファイルを作成し、そこにCalculatorクラスを作ることにします。


以下のGIFのように新しいファイルを作成します。

calculation.dartを次のようにします。
numberは数字、opは演算子のリスト(可変長配列)です。

class Calculator{
	var number = new List();
	var op = new List();

   static void GetKey(String letter){
        // TODO キー入力 -> 配列に格納
    }

    static String Execute(){
        int result;
        // TODO 計算処理
        return result.toString();
    }
}

main.dartとの連携

まず、main.dartにcalculation.dartをimportします。

import 'dart:async';
import 'package:flutter/material.dart';
import 'calculation.dart';	//追加

続いて、_TextFiledStateの内容を変更します。
_expressionの初期値がレイアウト作成の時のままの'1+1'となっていたので '' に直しておきます。
また、_UpdateText()のif文分岐内容も変更します。

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

    void _UpdateText(String letter){
        setState(() {
            if(letter == 'C')
                _expression = '';
            else if (letter == '='){
                _expression='';
                var ans = Calculator.Execute();
                controller.sink.add(ans);
            }else if (letter == 'e'){
                _expression = 'Error';
            }
            else
                _expression += letter;
        });
    }

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

最後に_TextFiledStateのinitState()の内容を変更します。
まず、controllerのインスタンス化のところ、最後にbroadcast()を追加します。
そして、controllerにCalculator.GetKey()を追加で登録します。

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

Calculatorのメソッド

GetKey()メソッドの作成


次のような処理を行うメソッドを作ろうと思います。

例.)
入力 : 10+25×3-1
->
number:[10, 25, 3, 1]
op:[+, ×, -]


このメソッドは
・数字
・四則演算子
・C
・=
のボタンが押された時に呼び出されます。


なのでそれぞれ場合分けして処理をします。

const c_op = ['+', '-', '×', '÷'];

class Calculator{
    static var _number = new List();
    static var _op = new List();

    static String _buffer;
    static void GetKey(String letter){
        // 四則演算子
        if(c_op.contains(letter)){
            _op.add(letter);
            _number.add(double.parse(_buffer));
            _buffer = '';
        } // C
        else if(letter == 'C'){
            _number.clear();
            _op.clear();
            _buffer = '';
        } // =
        else if(letter == '='){
            return null;
        }// 数字
        else{
            _buffer += letter;
        }
    }
// 〜〜〜省略〜〜〜
各メソッドの説明

double.parse()を使用して、文字列をdoubleに解析できます

it-swarm.dev

contains(Object element) → bool
Returns true if the collection contains an element equal to element. [...]

List class(公式)

add(E value) → void
Adds value to the end of this list, extending the length by one. [...]

List class(公式)

clear() → void
Removes all objects from this list; the length of the list becomes zero. [...]

List class(公式)

Execute()メソッドの作成

計算処理を書いていきます。
今回は四則演算の順序を守らず、ー符号の入力もできない物を作ります。
(ただ面倒だっただけです。ごめんなさい。)

// 〜〜〜省略〜〜〜
    static double _result;
    static String Execute() {
        _number.add(double.parse(_buffer));

        if (_number.length == 0)
            return '0';

        _result = _number[0];
        for (int i = 0; i < _op.length; i++) {
            if (_op[i] == '+')
                _result += _number[i + 1];
            else if (_op[i] == '-')
                _result -= _number[i + 1];
            else if (_op[i] == '×')
                _result *= _number[i + 1];
            else if (_op[i] == '÷' && _number[i + 1] != 0)
                _result /= _number[i + 1];
            else
                return 'e';
        }

        _number.clear();
        _op.clear();
        _buffer = '';

        var resultStr = _result.toString().split('.');
        return resultStr[1] == '0' ? resultStr[0] : _result.toString();
    }
}

最後の2行

var resultStr = _result.toString().split('.');
return resultStr[1] == '0' ? resultStr[0] : _result.toString();

doubleをtoString()すると、2.0などと小数点以下0の文字列が返ってしまうので、それを直す処理を書いています。


完成

(プログラム全体はこのページの最後に載せてます。)

色々雑だったけど、ひとまず完成しました!

実機で動かしてみる(iOS)

現在開いているシュミレーターを終了し、接続した実機を選択

[PIC 2]


AndroidStudioの下のタブの「ターミナル」をクリックして統合シェルを起動して次のコマンドを入力します。

    fluter install

Installing com.example.calculatorapp to 〜〜〜のiPhone7...
Could not find application bundle at build/ios/iphoneos/Runner.app; have you run "flutter build ios"?
Install failed

返ってきたので、書かれている通りに

    flutter build ios

と入力します。


Building com.example.calculatorapp for device (ios-release)...
════════════════════════════════════════════════════════════════════════════════
No valid code signing certificates were found
You can connect to your Apple Developer account by signing in with your Apple ID
in Xcode and create an iOS Development Certificate as well as a Provisioning
Profile for your project by:
  1- Open the Flutter project's Xcode target with
       open ios/Runner.xcworkspace
  2- Select the 'Runner' project in the navigator then the 'Runner' target
     in the project settings
  3- Make sure a 'Development Team' is selected.
     - For Xcode 10, look under General > Signing > Team.
     - For Xcode 11 and newer, look under Signing & Capabilities > Team.
     You may need to:
         - Log in with your Apple ID in Xcode first
         - Ensure you have a valid unique Bundle ID
         - Register your device with your Apple Developer Account
         - Let Xcode automatically provision a profile for your app
  4- Build or run your project again
  5- Trust your newly created Development Certificate on your iOS device
     via Settings > General > Device Management > [your new certificate] > Trust

    For more information, please visit:
    https://developer.apple.com/library/content/documentation/IDEs/Conceptual/
    AppDistributionGuide/MaintainingCertificates/MaintainingCertificates.html

    Or run on an iOS simulator without code signing
    ════════════════════════════════════════════════════════════════════════════════
    No development certificates available to code sign app for device deployment

と返ってきた。。。
1つずつ順にやっていきます。


1-

1- Open the Flutter project's Xcode target with
     open ios/Runner.xcworkspace

AndroidStudioの統合シェルに

    open ios/Runner.xcworkspace

と入力すると、Xcodeか開きます。


2-

2- Select the 'Runner' project in the navigator then the 'Runner' target
   in the project settings

先ほど開いたXcodeで作業をします。
Runnerをクリックして、チームを選択します。

[PIC 4]

チームをちゃんと選択するにはもう少し作業が必要なようなので、1つずつ確認して作業していきます。

3-

3- Make sure a 'Development Team' is selected.
   - For Xcode 10, look under General > Signing > Team.
   - For Xcode 11 and newer, look under Signing & Capabilities > Team.
   You may need to:
       - Log in with your Apple ID in Xcode first
       - Ensure you have a valid unique Bundle ID
       - Register your device with your Apple Developer Account
       - Let Xcode automatically provision a profile for your app

- Log in with your Apple ID in Xcode first
ログインしてある。

- Ensure you have a valid unique Bundle ID
この警告と同じこといってるのかな?と思うので、

[PIC 5]

こんな感じに変更しました。

[PIC 6]

- Register your device with your Apple Developer Account
これかな?
Preference... > Accounts

[PIC 7]

- Let Xcode automatically provision a profile for your app
よくわからない。。無視。


4-

4- Build or run your project again

ではもう一回

    flutter build ios

すると、パスワード求められるから答える。
「許可」でもいいんだけど、何回も問われて面倒だから「常に許可」に。

[PIC 8]

終わったら、

    flutter install

を入力。


iPhoneにアプリが入った。

[PIC 9]

開こうとするとこうなる。

[PIC 10]

Untrusted だと。


5-

5- Trust your newly created Development Certificate on your iOS device
   via Settings > General > Device Management > [your new certificate] > Trust

書いてある通り、実機の
「設定」>「一般」>「プロファイルとデバイス管理」より「信頼」っと。



実機で動かすことができました。

[PIC 12]

以上でFlutter電卓作成は終了です。

参考

概要知り合い「Flutterはいいぞ」ぼく「はい」Flutterのインストールから実機での動作確認がゴール。環境VersionmacOSMojave 10.14.6Flutterv...

プログラム全体

[main.dart]
import 'dart:async';
import 'package:flutter/material.dart';
import 'calculation.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 == 'C')
                _expression = '';
            else if (letter == '='){
                _expression='';
                var ans = Calculator.Execute();
                controller.sink.add(ans);
            }else if (letter == 'e'){
                _expression = 'Error';
            }
            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>.broadcast();
    @override
    void initState() {
        controller.stream.listen((event) => _UpdateText(event));
        controller.stream.listen((event) => Calculator.GetKey(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);
                },
            )
        );
    }
}

[calculation.dart]
const c_op = ['+', '-', '×', '÷'];

class Calculator{
    static var _number = new List();
    static var _op = new List();

    static String _buffer;
    static void GetKey(String letter){
        // 四則演算子
        if(c_op.contains(letter)){
            _op.add(letter);
            _number.add(double.parse(_buffer));
            _buffer = '';
        } // C
        else if(letter == 'C'){
            _number.clear();
            _op.clear();
            _buffer = '';
        } // =
        else if(letter == '='){
            return null;
        }// 数字
        else{
            _buffer += letter;
        }
    }

    static double _result;
    static String Execute() {
        _number.add(double.parse(_buffer));

        if (_number.length == 0)
            return '0';

        _result = _number[0];
        for (int i = 0; i < _op.length; i++) {
            if (_op[i] == '+')
                _result += _number[i + 1];
            else if (_op[i] == '-')
                _result -= _number[i + 1];
            else if (_op[i] == '×')
                _result *= _number[i + 1];
            else if (_op[i] == '÷' && _number[i + 1] != 0)
                _result /= _number[i + 1];
            else
                return 'e';
        }

        _number.clear();
        _op.clear();
        _buffer = '';

        var resultStr = _result.toString().split('.');
        return resultStr[1] == '0' ? resultStr[0] : _result.toString();
    }
}

自己紹介

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

人気の投稿

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

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

QooQ