Androidで電卓アプリ作ってみた

# 概要
シンプルな電卓アプリです。
Androidのお勉強用に作りました。

Android Studio 3.6.2
TableLayout
![caluculation.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/566570/f84b198c-cb91-b52c-3ca6-80b0526cab06.png)

# 工夫・苦労したこと
・TableLayoutで列をまたいだボタンを作る
Table rowの中にLinearLayoutを入れ子にしました。

・特定条件下のみ画像を表示
imageViewにsetImageResourceで画像をセットするようにしました。

・掛け算割り算を優先する処理
計算式をArrayListに格納　→　×÷を検索して優先計算する　→　計算済みの部分をリストから削除　→　計算結果をリストに加える　→　×÷がなくなるまで繰り返す　→　その後に+-計算をする
という方法で実装しました。

・doubleの計算誤差を失くす
「1.0d - 0.9d = 0.099999999...」になってしまう問題。
BigDecimalクラスを利用して解決しました。

・割り算で割り切れない時エラーが出てしまう
BigDecimalは、割り算で割り切れない数字(循環小数）が出るとArithmeticExceptionが発生する。
このエラーを回避するために小数点第11位の数字を四捨五入するようにしました。
でも、そうすると今度は割り切れる数字の時も小数点第10位まで表示されてしまう問題が発生。
こんな風になる→　4÷2=2.0000000000
これを避けるために割り算をtry-catchで囲んでArithmeticException発生時のみ小数点第11位の数字を四捨五入するようにしました。

・バグ修正
小数点を複数打てないようにする。
＋－×÷＝を意図しない所で打てないようにする。
＝ボタンクリック後の挙動いろいろ。
他にも色々ありました・・・。

## TableLayoutで列をまたいだボタンを作る方法
Table rowの中にLinearLayoutを入れ子にします。

```activity_main.xml
<TableRow
        android:id="@+id/row2"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="2">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <Button
                android:id="@+id/button4"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:text="@string/button4"
                android:textSize="25sp"
                android:onClick="btnCurrent_onClick"/>

            <Button
                android:id="@+id/button1"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:text="@string/button1"
                android:textSize="25sp"
                android:onClick="btnCurrent_onClick"/>

        </LinearLayout>

        <!--行をまたぐボタン-->
        <Button
            android:id="@+id/buttonPlus"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_margin="5dp"
            android:background="#FFBF00"
            android:text="@string/buttonPlus"
            android:textSize="25sp"
            android:onClick="btnCurrent_onClick"/>
</TableRow>

        
```

# 感想・反省
作ってみないとわからないことが結構あって勉強になりました。
今後も本を読む　→　作ってみる、というサイクルで学習していきます。

反省点はif文だらけになってしまったこと。
もっときれいなコードを書きたい。

# コード全文
```MainActivity.java
package to.msn.wings.caluculator;

import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Bundle;
import android.text.Editable;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {

    List<String> stringList = new ArrayList<>();//入力文字を格納するリスト
    int decimalPointCount = 0; // 小数点ボタンの連続クリック防止用変数。
    int ClickedFormulaOnce = 0; // +-/*が計算式に1個でも含まれるかの判定用。+-*/が1個も無いと計算できないので。
    int formulaContinuousClick = 0; // +-/*ボタンの連続クリック防止用変数。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    // 計算用メソッド
    public void calculation(TextView txt) {
        // TextViewの文字を全て取得して、配列に格納する。
        String txtAll = txt.getText().toString();

        // 区切り文字自身も配列に含める正規表現
        //正規表現((?<=[+×÷-])|(?=[+×÷-]))の意味は+-×÷の直前と直後で区切るという意味。
        String[] stringArray = txtAll.split("((?<=[+×÷-])|(?=[+×÷-]))", 0);

        // 配列に格納後、TextViewの文字を全て消去する。
        txt.setText(null);
        // 配列を全てArrayListに格納する。
        for(String s:stringArray) {
            stringList.add(s);
        }
        //BigDecimalはdoubleと違い誤差が出ない。
        BigDecimal bigDecimalResultValue = new BigDecimal("0.0");
        // 掛け算割り算の処理
        for(int i = 1; i < stringList.size(); i += 2) {
            if(stringList.get(i).equals("×")) {
                bigDecimalResultValue = new BigDecimal(stringList.get(i-1)).multiply(new BigDecimal(stringList.get(i+1)));
                // 計算済みの数字と式をstringListから削除する。　例：「2*3+1」の「2*3」の部分を削除する。
                stringList.remove(i-1);
                stringList.remove(i-1);
                stringList.remove(i-1);
                // 計算結果をstringListに加える。　例：「2*3+1」を「6+1」にする。
                stringList.add(i-1, String.valueOf(bigDecimalResultValue));
                // stringListを3つ削除して1つ足したので、iから2を引く。
                i -= 2;
            } else if (stringList.get(i).equals("÷")) {
                // Bigdecimalの割り算は割り切れない数の時ArithmeticExceptionエラーが出る。そのためtrycathで囲む。
                // これをやっとくと割り切れる時は小数点以下に無駄に00000がつかない。
                // 割り切れない時だけ小数点第11位を四捨五入する。
                try {
                    bigDecimalResultValue = new BigDecimal(stringList.get(i-1)).divide(new BigDecimal(stringList.get(i+1)));
                } catch (ArithmeticException e) {
                    bigDecimalResultValue = new BigDecimal(stringList.get(i-1)).divide(new BigDecimal(stringList.get(i+1)), 10, RoundingMode.HALF_UP);//四捨五入。小数点第10位まで表示。
                }
                stringList.remove(i-1);
                stringList.remove(i-1);
                stringList.remove(i-1);
                stringList.add(i-1, String.valueOf(bigDecimalResultValue));
                i -= 2;
            }
        }
        // 足し算引き算の処理
        // 掛け算割り算はすでに処理済みなので、単純に前から順に足し算引き算をしていくだけ。
        while(stringList.size() > 1) {
            if(stringList.get(1).equals("+")) {
                bigDecimalResultValue = new BigDecimal(stringList.get(0)).add(new BigDecimal(stringList.get(2)));
                stringList.remove(0);
                stringList.remove(0);
                stringList.remove(0);
                stringList.add(0, String.valueOf(bigDecimalResultValue));
            } else if (stringList.get(1).equals("-")) {
                bigDecimalResultValue = new BigDecimal(stringList.get(0)).subtract(new BigDecimal(stringList.get(2)));
                stringList.remove(0);
                stringList.remove(0);
                stringList.remove(0);
                stringList.add(0, String.valueOf(bigDecimalResultValue));
            }
        }
        if(String.valueOf(bigDecimalResultValue).equals("3")) {
            // 結果が3の時だけ画像を表示する処理
            // IDを元にImageViewオブジェクトを取得
            @SuppressLint("ResourceType")
            ImageView iv = this.findViewById(R.id.imageView);
            // drawableフォルダにあるイメージを設定
            iv.setImageResource(R.drawable.nabeatsu);
            txt.setText("世界のナベ〇ツ");
        } else {
            txt.setText(String.valueOf(bigDecimalResultValue));
        }
        // 結果表示後にリストをクリア。
        stringList.clear();
    }
    public void btnCurrent_onClick(View view) {
        // テキストビューを取得
        TextView txt = findViewById(R.id.textView);
        // スイッチ文でクリックされたボタンを判定。テキストに表示する。
        switch (view.getId()) {
            case R.id.button0:
                txt.append("0");
                formulaContinuousClick = 0;
                break;
            case R.id.button1:
                txt.append("1");
                formulaContinuousClick = 0;
                break;
            case R.id.button2:
                txt.append("2");
                formulaContinuousClick = 0;
                break;
            case R.id.button3:
                txt.append("3");
                formulaContinuousClick = 0;
                break;
            case R.id.button4:
                txt.append("4");
                formulaContinuousClick = 0;
                break;
            case R.id.button5:
                txt.append("5");
                formulaContinuousClick = 0;
                break;
            case R.id.button6:
                txt.append("6");
                formulaContinuousClick = 0;
                break;
            case R.id.button7:
                txt.append("7");
                formulaContinuousClick = 0;
                break;
            case R.id.button8:
                txt.append("8");
                formulaContinuousClick = 0;
                break;
            case R.id.button9:
                txt.append("9");
                formulaContinuousClick = 0;
                break;

            case R.id.buttonBS:
                formulaContinuousClick = 0;
                // Editableインスタンス取得
                Editable editable = Editable.Factory.getInstance().newEditable(txt.getText());

                // ボタンを押すごとに最後尾1文字を削除する処理
                if(editable.length() > 0){
                    //deleteの第1引数が削除したい文字のstart,第2引数が削除したい文字のend
                    editable.delete(editable.length()-1, editable.length());
                }
                // TextViewにセットする
                txt.setText(editable, TextView.BufferType.EDITABLE);

                break;

            case R.id.buttonClear:
                decimalPointCount = 0;
                ClickedFormulaOnce = 0;
                formulaContinuousClick = 0;
                stringList.clear();

                //画像のクリア用
                @SuppressLint("ResourceType")
                ImageView iv = this.findViewById(R.id.imageView);
                iv.setImageDrawable(null);
                txt.setText(null);
                break;

            case R.id.buttonPoint:
                // 小数点を連打できないようにする判定。
                if (decimalPointCount == 0) {
                    txt.append(".");
                    decimalPointCount = 1;
                }
                break;

            // ここからは+-/*をクリックしたときの処理。
            // formulaContinuousClickは+-*/=の連続クリック防止用。
            // ClickedFormulaOnceは+-*/を1度でもクリックしたかの判定用。
            case R.id.buttonPlus:
                formulaContinuousClick++;
                // +-/*を押した時の処理。連続タップの場合は無視する。
                // なおかつTextViewに何もない場合も無視する。
                if(formulaContinuousClick == 1 && !(txt.getText().toString().equals(""))) {
                    ClickedFormulaOnce = 1;
                    txt.append("+");
                }
                decimalPointCount = 0; //小数点のクリックカウントを0に戻す。
                break;

            case R.id.buttonMinus:
                formulaContinuousClick++;
                if(formulaContinuousClick == 1 && !(txt.getText().toString().equals(""))) {
                    ClickedFormulaOnce = 1;
                    txt.append("-");
                }
                decimalPointCount = 0;
                break;

            case R.id.buttonMulti:
                formulaContinuousClick++;
                ClickedFormulaOnce = 1;
                if(formulaContinuousClick == 1 && !(txt.getText().toString().equals(""))) {
                    ClickedFormulaOnce = 1;
                    txt.append("×");
                }
                decimalPointCount = 0;
                break;

            case R.id.buttonDiv:
                formulaContinuousClick++;
                ClickedFormulaOnce = 1;
                if(formulaContinuousClick == 1 && !(txt.getText().toString().equals(""))) {
                    ClickedFormulaOnce = 1;
                    txt.append("÷");
                }
                decimalPointCount = 0;
                break;

            case R.id.buttonEqual:
                if(ClickedFormulaOnce == 1) {
                    formulaContinuousClick++;
                }
                if (ClickedFormulaOnce == 1 && formulaContinuousClick == 1) {
                    formulaContinuousClick = 0;
                    ClickedFormulaOnce = 0;
                    // 計算結果が小数点付きの時に小数点をさらに付けられないようにする。　例「3.3+0.1＝3.4」→小数点クリック→「3.4.」など
                    decimalPointCount = 1;
                    if(txt.getText().toString().length() > 56) {
                        Context context = getApplicationContext();
                        Toast.makeText(context, "文字数が上限の56文字を超えています。", Toast.LENGTH_LONG).show();
                    } else {
                        calculation(txt);
                    }
                }
                break;
        }
    }
}
```

```activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:shrinkColumns="0,1,2,3,4">


    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="2"
        android:orientation="vertical">

        <TextView
            android:id="@+id/textView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|end"
            android:textSize="50sp" />

        <ImageView
            android:id="@+id/imageView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal" />
    </LinearLayout>


    <TableRow
        android:id="@+id/row1"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="2">

        <Button
            android:id="@+id/button7"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:onClick="btnCurrent_onClick"
            android:text="@string/button7"
            android:textSize="25sp" />

        <Button
            android:id="@+id/button8"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:onClick="btnCurrent_onClick"
            android:text="@string/button8"
            android:textSize="25sp" />

        <Button
            android:id="@+id/button9"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:onClick="btnCurrent_onClick"
            android:text="@string/button9"
            android:textSize="25sp" />

        <Button
            android:id="@+id/buttonDiv"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_margin="5dp"
            android:background="#FFBF00"
            android:onClick="btnCurrent_onClick"
            android:text="@string/buttonDiv"
            android:textSize="25sp" />

        <Button
            android:id="@+id/buttonClear"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_margin="5dp"
            android:background="#FF4000"
            android:onClick="btnCurrent_onClick"
            android:text="@string/buttonClear"
            android:textSize="25sp" />
    </TableRow>

    <TableRow
        android:id="@+id/row2"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="2">

        <Button
            android:id="@+id/button4"
            android:layout_width="match_parent"
            android:layout_height="match_parent"

            android:onClick="btnCurrent_onClick"
            android:text="@string/button4"
            android:textSize="25sp" />

        <Button
            android:id="@+id/button5"
            android:layout_width="match_parent"
            android:layout_height="match_parent"

            android:onClick="btnCurrent_onClick"
            android:text="@string/button5"
            android:textSize="25sp" />


        <Button
            android:id="@+id/button6"
            android:layout_width="match_parent"
            android:layout_height="match_parent"

            android:onClick="btnCurrent_onClick"
            android:text="@string/button6"
            android:textSize="25sp" />

        <Button
            android:id="@+id/buttonMulti"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_margin="5dp"

            android:background="#FFBF00"
            android:onClick="btnCurrent_onClick"
            android:text="@string/buttonMulti"
            android:textSize="25sp" />

        <Button
            android:id="@+id/buttonBS"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_margin="5dp"

            android:background="#00ffff"
            android:onClick="btnCurrent_onClick"
            android:text="@string/buttonBS"
            android:textSize="25sp" />
    </TableRow>

    <TableRow
        android:id="@+id/row3"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="2">

        <Button
            android:id="@+id/button1"
            android:layout_width="match_parent"
            android:layout_height="match_parent"

            android:onClick="btnCurrent_onClick"
            android:text="@string/button1"
            android:textSize="25sp" />

        <Button
            android:id="@+id/button2"
            android:layout_width="match_parent"
            android:layout_height="match_parent"

            android:onClick="btnCurrent_onClick"
            android:text="@string/button2"
            android:textSize="25sp" />


        <Button
            android:id="@+id/button3"
            android:layout_width="match_parent"
            android:layout_height="match_parent"

            android:onClick="btnCurrent_onClick"
            android:text="@string/button3"
            android:textSize="25sp" />

        <Button
            android:id="@+id/buttonPlus"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_margin="5dp"
            android:background="#FFBF00"
            android:onClick="btnCurrent_onClick"
            android:text="@string/buttonPlus"
            android:textSize="25sp" />


        <Button
            android:id="@+id/buttonMinus"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_margin="5dp"

            android:background="#FFBF00"
            android:onClick="btnCurrent_onClick"
            android:text="@string/buttonMinus"
            android:textSize="40sp" />
    </TableRow>


    <TableRow
        android:id="@+id/row4"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1">

        <Button
            android:id="@+id/button0"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_span="2"
            android:onClick="btnCurrent_onClick"
            android:text="@string/button0"
            android:textSize="25sp" />

        <Button
            android:id="@+id/buttonPoint"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:onClick="btnCurrent_onClick"
            android:text="@string/buttonPoint"
            android:textSize="40sp" />

        <Button
            android:id="@+id/buttonEqual"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_margin="5dp"
            android:layout_span="2"
            android:background="#2EFE64"
            android:onClick="btnCurrent_onClick"
            android:text="@string/buttonEqual"
            android:textSize="25sp" />
    </TableRow>
```


```string.xml
<resources>
    <string name="app_name">Caluculator</string>
    <string name="button0">0</string>
    <string name="button1">1</string>
    <string name="button2">2</string>
    <string name="button3">3</string>
    <string name="button4">4</string>
    <string name="button5">5</string>
    <string name="button6">6</string>
    <string name="button7">7</string>
    <string name="button8">8</string>
    <string name="button9">9</string>
    <string name="buttonPlus">+</string>
    <string name="buttonMinus">-</string>
    <string name="buttonMulti">×</string>
    <string name="buttonDiv">÷</string>
    <string name="buttonPoint">.</string>
    <string name="buttonEqual">=</string>
    <string name="buttonClear">C</string>
    <string name="buttonBS">BS</string>
</resources>
```