NUmeroのソース置き場

見習いプログラマー

2015/10

Androidアプリ"生活電卓"の使い方を説明します。

モードの切り替え

image


上のタブを選択することで計算モードを切り替えることができます。
右上から設定画面を表示することができます。

電卓

image


普通の電卓同様、数値ボタンと演算ボタンで計算できます。

image


入力を間違えた場合、"DEL"ボタンでひとつ前に戻ることができます。

割引/+税計算


image



"+税"ボタンで8%分を上乗せして計算します。

image



"○%OFF"ボタンまたは"○割引"を押し、次に数値を入力するとその値で割引します。



金種計算

image


"+"ボタンや"-"ボタンで各金種の枚数を増減し、"="ボタンで計算、結果を表示します。
image


枚数の部分をタップすると直接枚数を入力できます。

設定

image


・値の共有
 有効にすると入力値や計算結果をそのまま他のモードで利用できます。
 例えば、電卓モードで計算した後に割引/+税計算モードで税込みを計算することができます。
・バイブレーション
 有効にすると計算結果表示時に振動でお知らせします。
・起動画面
 アプリを起動したときの最初の画面を設定できます。
・税率
 割引/+税計算モードでの税率を設定します。

今後のアップデート(できれば)


・テーマの変更

開発時にTextViewが反映されないという問題が発生し、(多少強引ではあるが)解決したのでその方法のメモ。

発生した問題


ボタンを押したときにTextViewにsetTextで文字列を表示する処理を書いていたが、ボタンを押してもTextViewが反映されなかった。TextViewに対してinvalidateをしても表示されず。

原因


レイアウトの多重層が問題のようで、問題のTextViewもかなり深い層に存在していた。
この場合、TextViewより上層のViewが更新されなければ下層の位置にあるTextViewが反映されない。

解決法


今回の場合は多少強引ではあるが、TextViewのsetTextの後、最上層のViewに対してinvalidateすることで下層のViewすべてを再描画することで解決。
実際にしてはいないがレイアウトの深さを解消することでも解決すると思われる。




※不思議なことに今回の問題が発生したのはXperia Z4のみで手持ちのNexus7(2012)、Nexus7(2013)、L01では同じ現象が見られなかった。

EditTextでソフトウェアキーボードのEnterボタンを押したときのフォーカス先と入力後にキーボードを閉じる処理のメモ

フォーカスの移動


<EditText
    android:id="@+id/input"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:nextFocusDown="@+id/input_next"/>

nextFocusDownの引数に次のEditTextのidを指定することで、Enterキーで指定したEditTextにフォーカスされます。

入力後キーボードを閉じる


<EditText
    android:id="@+id/input"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:imeOptions="actionDone"/>


imeOptionsで引数を指定することで、入力後のEnterキーでキーボードを閉じることができます。
引数に関しては公式ドキュメントを参考にしてください。

OnClickListenerの実装について、様々な書き方があるのでまとめてみます。

レイアウトは共通のこちらを使います。
activity_main.xml
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    tools:context=".MainActivity">
    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="BUTTON"/>
</RelativeLayout>


Activityにインターフェイスを実装


MainActivity.java
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

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

        Button button = (Button)findViewById(R.id.button);
        button.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.button:
                Toast.makeText(getApplicationContext(),"buttonが押されました。",Toast.LENGTH_SHORT).show();
                break;
        }
    }
}


無名クラスに実装


MainActivity.java
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

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

        View.OnClickListener listener = new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(getApplicationContext(),"buttonが押されました。",Toast.LENGTH_SHORT).show();
            }
        };

        Button button = (Button)findViewById(R.id.button);
        button.setOnClickListener(listener);
    }
}


直接setOnClickListenerに実装する場合は…
MainActivity.java
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

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

        Button button = (Button)findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(getApplicationContext(),"buttonが押されました。",Toast.LENGTH_SHORT).show();
            }
        });
    }
}


別クラスに実装


MainActivity.java
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

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

        Button button = (Button)findViewById(R.id.button);
        button.setOnClickListener(new CustomListener());
    }

    public class CustomListener implements View.OnClickListener{
        @Override
        public void onClick(View v) {
            Toast.makeText(getApplicationContext(),"buttonが押されました。",Toast.LENGTH_SHORT).show();
        }
    }
}


レイアウトファイルのonClick属性を用いる


レイアウトファイルのButtonを次のようにします。
<Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="BUTTON"
        android:onClick="click"/>

MainActivity
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

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

    public void button_click(View v){
        Toast.makeText(getApplicationContext(),"buttonが押されました。",Toast.LENGTH_SHORT).show();
    }
}


このほかにもAndroidAnnotationやButter Knifeを用いた書き方もあります。
結論を言うと可読性と好みの問題であるのでどれを利用すべきというのはありませんが、どの方法でも読めるようにしておいて損はないと思います。

最近ちょっとした電卓アプリでも出そうかと考え、作成するときに利用したアルゴリズムです。
逆ポーランド記法(以下RPN)のアルゴリズムを用いれば、計算式で掛け算や割り算を含む場合そちらを優先的に計算できます。

・例

通常
  5 + 4 - 2
RPN
  5 4 2 - +

通常
  5 + 4 × 2 - 1
RPN
  5 4 2 × 1 - +

通常
  5 + 4 × 2 - 1 ÷ 3
RPN
  5 4 2 × 1 3 ÷ - +

このように演算子を数値の後ろに置き、掛け算割り算を優先的に計算できます。


・演算子の優先度

÷ = × > + = -

・式の変換

計算式を読み込み、RPNの並び順にするときの配列を用意しておきます。
演算子を読み込んだ場合は別の配列(スタック)に一時的に格納します。

1.数値の場合はそのまま配列に格納
2.演算子を読みこんだ場合はスタック先頭の演算子と優先度を比較。スタックの先頭の優先度が低ければスタックの演算子を配列に格納。その後、読みこんだ演算子をスタックの先頭から格納。
3.計算式の最後に到達すればスタックをすべて配列に格納。


とざっくり言葉で説明してもわからないと思うので、例を用いて説明します。
先ほどの例の一番最後の式で説明します。

計算式 : 5 + 4 × 2 - 1 ÷ 3
配列 :
スタック :


・数値を読み込み配列に格納
計算式 : + 4 × 2 - 1 ÷ 3
配列 : 5
スタック :

・演算子を読み込むがスタックに演算子がないためそのまま格納
計算式 : 4 × 2 - 1 ÷ 3
配列 : 5
スタック : +

・数値を読み込み配列に格納
計算式 : × 2 - 1 ÷ 3
配列 : 5 4
スタック : +

・演算子を読み込みスタックの先頭と比較、スタック先頭の演算子よりも優先度が高いためスタックの先頭に格納
計算式 : 2 - 1 ÷ 3
配列 : 5 4
スタック : × +

・数値を読み込み配列に格納
計算式 : - 1 ÷ 3
配列 : 5 4 2
スタック : × +

・演算子を読み込みスタックの先頭と比較、スタック先頭の演算子よりも優先度が低いためスタックの先頭の演算子を配列に格納、読み込んだ演算子をスタックの先頭に格納
計算式 : 1 ÷ 3
配列 : 5 4 2 ×
スタック : - +

・数値を読み込み配列に格納
計算式 : ÷ 3
配列 : 5 4 2 × 1
スタック : - +

・演算子を読み込みスタックの先頭と比較、スタック先頭の演算子よりも優先度が高いためスタックの先頭に格納
計算式 : 3
配列 : 5 4 2 × 1
スタック : ÷ - +

・数値を読み込み配列に格納
計算式 :
配列 : 5 4 2 × 1 3
スタック : ÷ - +

・計算式の最後に到達したのでスタックをすべて配列に格納
計算式 :
配列 : 5 4 2 × 1 3 ÷ - +
スタック :

これでRPNの変換ができました。

・計算

実際にRPNを用いて計算してみます。
解法は演算子を見つけたらその前の2つの値を計算するだけです。

5 4 2 × 1 3 ÷ - +

5 8 1 3 ÷ - +

5 8 0.3333… - +

5 7.6666… +

12.6666…


Android 5.0(Lollipop)から追加されたActionBarの代替となるコンポーネントで、以前のスタイルからではなくレイアウトに書きます。

・準備


themeのToolBarを非表示にする。
styles.xml
<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
    </style>

</resources>



・実装


activity_main.xml
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_height="wrap_content"
        android:layout_width="match_parent"
        android:minHeight="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        android:elevation="4dp" />
    <!-- contents -->
</RelativeLayout>


xml内でViewのパーツとして扱います。

MainActivity.java
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;

public class MainActivity extends AppCompatActivity {

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

        Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        //アイコンの設定
        getSupportActionBar().setIcon(R.mipmap.ic_launcher);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();
        if (id == R.id.action_settings) {
            //メニューのクリック処理
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

}

image



※他のサイトではActionBarActivityを継承しているのですが、非推奨になっているのでAppCompatActivityのほうがいいのかな…?


AndroidでFloatingActionButtonを実装してみようと思います。

image



・準備


FABはDesign Libraryに含まれているのでライブラリをgradleに追記します。
※ここではv22.2.1のライブラリを使用しています。

build.gradle(Module: app)
dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    compile 'com.android.support:appcompat-v7:22.2.1'
    compile 'com.android.support:design:22.2.1'
}


・実装


activity_main.xml
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <android.support.design.widget.FloatingActionButton
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:id="@+id/fab"
        android:layout_gravity="right"
        android:layout_marginBottom="16dp"
        android:layout_marginRight="16dp"
        android:layout_alignParentRight="true"
        android:layout_alignParentBottom="true" />
    <!-- contents -->
</RelativeLayout>

FAB内部の画像はdrawable以下に画像を入れるなどして
android:src=""
で指定します。

MainActivity.java
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

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

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.fab:
                Toast.makeText(getApplicationContext(),"FABが押されました",Toast.LENGTH_SHORT).show();
                break;
        }
    }
}


image




FABの実装は基本的にボタンと同じようにするだけなので難しくはありません。

このページのトップヘ