Kojittoアプリ プライバシーポリシー
プライバシーポリシー
2016年12月22日 改訂
開発者:Kojitto
mail:kentaro198477@gmail.com
Kojittoの提供するAndroidアプリ「タイマークラシック」、「Digital Timer - free」(以下、当アプリ)では、
ユーザー様の個人情報(年齢・性別・位置情報等)の一切を取得しておりません。
よって、第三者への提供、開示等行われることもありません。
当アプリのダウンロード時に必要となる権限「カメラ機能の利用」については、
当アプリ内でのタイマー終了時におけるユーザー様への通知の手段として、端末のフラッシュライトを点灯させることがあり、
これは端末のカメラ機能の一部を利用することで実現しています。撮影・録画等を行うプログラムは一切含まれておりません。
プライバシーポリシーの変更を行った場合は、当ブログにて通知いたします。
アクティビティをスタックに残さなくする
例えばタイマーアプリを作っていて、
タイマー終了時に透明なアクティビティを開始して、
アラートダイアログを表示し、
OKが押されたり、dismissされたりしたときに
そのアクティビティをフィニッシュするとします。
この透明アクティビティがスタックに残っていると、
端末の”□”をタップしたときに表示され、ユーザーがそれをタップすると
またアラートダイアログが表示されるので、タイマーの挙動としては不自然です。
透明アクティビティがスタックに残らないようにするには、マニフェストに以下のように記述します。
<activity android:name=".MyActivity" android:excludeFromRecents="true" android:theme="@android:style/Theme.Translucent.NoTitleBar" >
android:excludeFromRecentsをtrueにするだけです。
また、テーマを@android:style/Theme.Translucent.NoTitleBarにした場合、
アラートダイアログのUIデザインが昔っぽいものになってしまうので、
これを避けるには、独自にスタイルを定義する必要があります。
【電卓アプリを作るvol.7】アプリ終了時の状態を保存
前回まで・・・
計算結果に端数が出た場合に、
それを丸めて表示する静的メソッドを実装しました。
今回は、
計算中に電話がかかってきて、
戻ったら全てクリアーされていたという、
悲しいことが起こらないように、
終了時の状態を保存し、
再開したときにロードする機能を実装していきます。
何を保存するべきか
電卓を開いたときに、
どのような情報があれば、
同じ状態を再現できるでしょうか?
最後に見ていたものと、再開直後が違っていたら、
不安になってACを押してしまうでしょう。
なので、
①ディスプレイに表示されている文字列
は欠かせません。
見たままを保存します。
単純に、StringBuilder.toString()で得られる文字列を保存します。
電卓には状態があると前途しました。
状態によって呼び分けられるメソッドが変わってきます。
②現在の状態
を保存します。
現在の状態を知るためのメソッド、
int getStateValue()をすでに用意していますので、
それによって得られた定数も保存します。
状態が右辺入力中なら、
③左辺の情報が無いと計算できません。
④演算子も同様です。
定数計算のために、
⑤右辺の値も必要です。
今回はメモリー機能やグランドトータル機能は実装していませんので、
これら5つの情報を保存すれば、
どの状況も再現が可能です。
再現するには
アプリのオンポーズ時にこれらを保存し、
オンレジューム時に、
Calcクラスのインスタンスのフィールドに、
上記の情報を格納します。
stateに状態に応じたクラスのインスタンス、
value1に左辺の値をdoubleで、
value2に右辺の値を同じくdouble、
enzanshiは、演算子を示す定数、
stringBuilderに、最後に見ていたのと同じ文字列を入れます。
Calc内に、これら5情報を保持するクラスを作ります
コンストラクターも用意します。
public class CalcHolder{ public int state; public String dispString; public double leftValu,rightValue; public int enzanshi; public CalcHolder(int state,String dispString, double leftValue, double rightValue, int enzanshi) { this.state = state; this.dispString = dispString; this.leftValu = leftValue; this.rightValue = rightValue; this.enzanshi = enzanshi; } }
同じくCalc内に、セーブ用とロード用のパブリックなメソッドを定義します。
セーブ用のgetHolderでは、CalcHolderのインスタンスを返し、
ロード用のsetHolderは、CalcHolderのインスタンスを渡すことによって、
前回の状態に復帰します。
public CalcHolder getHolder(){ return new CalcHolder(state.getStateValue(this),stringBuilder.toString(), value1,value2,enzanshi); } public void setHolder(CalcHolder holder){ switch (holder.state){ case State.STATE_A: changeState(StateA.getInstance()); break; case State.STATE_B: changeState(StateB.getInstance()); break; case State.STATE_C: changeState(StateC.getInstance()); break; case State.STATE_D: changeState(StateD.getInstance()); break; } this.value1 = holder.leftValu; this.value2 = holder.rightValue; this.enzanshi = holder.enzanshi; set(holder.dispString); callback.onTextChanged(stringBuilder.toString()); }
メインアクティビティのonPauseとonResumeに保存・復帰の処理を記述します。
Gsonを利用しますので、Gsonライブラリーを読み込んで置きます。
(Gsonについての関連記事)
@Override protected void onPause() { super.onPause(); Calc.CalcHolder holder = calc.getHolder(); Gson gson = new Gson(); String js = gson.toJson(holder, Calc.CalcHolder.class); getSharedPreferences("pref",MODE_PRIVATE).edit().putString("holder",js).apply(); } @Override protected void onResume() { super.onResume(); final String NULL = "null"; Gson gson = new Gson(); String js = getSharedPreferences("pref",MODE_PRIVATE).getString("holder",NULL); if (js.equals(NULL)){ calc.allClear(); // 初回起動 } else { calc.setHolder(gson.fromJson(js, Calc.CalcHolder.class)); } }
正しく動作するか確かめてみてください。
マニフェストでアプリをポートレイトにしていなければ、
端末の向きを変えてみて、数字がリセットされないことでも確認できます。
ちなみに、onResume内の「初回起動」とコメントアウトした部分は、
そのまま、ダウンロード後の第一回目の起動なので、
サンキューメッセージ等を表示するのもいいかもしれません。
【電卓アプリを作るvol.6】計算結果を成型する
前回まで・・・
とりあえず、必要最低限の電卓ができました。
今回は、計算結果を丸めて表示する機能を実装します。
現状の問題点
今の段階では、1+1=の結果が、
"2.0" (シンプルに "2" でいいんじゃない?)
また、10÷6のように割り切れない計算をすると
桁数が非常に多くなってしまううえ、
"1.6666666666666667"
というふうに、最後が謎の7になっています。
これはコンピューター上の仕様のようですが、
一般的には、「6が永遠と続く」という認識なので、
ユーザーからしたら不具合かと思われかねません。
そこで、一般的な電卓に習い、12桁までの表示にするとか、
小数点以下n位までの表示にする、
といった機能を実装していきます。
一般電卓の多くは、「小数点セレクター」「ラウンドセレクター」
という機能があり、
「小数点セレクター」は、小数点以下第何位まで表示するか、
「ラウンドセレクター」は、小数点セレクターで設定した位置に丸める際、
切り上げか、切捨てか、四捨五入するかなどを設定できる機能です。
カシオ計算機のサイトなどで使い方が詳しく載っています。
今回の実装ではこれらの機能をお手本にしているので、
理解度を高めるためにも、一度ご覧になることをオススメします。
自作のRoundSelectorクラス
Calcから送られてきた文字列を、成型した文字列に変換するクラスです。
引数に、計算結果のdouble、小数点の位置、丸めの方法を定数で指定します。
テキストビューにセットする前にフィルターを挟むように使います。
静的メソッドとして定義しました。
public class RoundSelector { public static final int POINT_5 = 5,POINT_4 = 4,POINT_3 = 3,POINT_2 = 2,POINT_1 = 1,POINT_0 = 0; public static final int ROUND_F = 10,ROUND_CUT = 11,ROUND_UP = 12,ROUND_5_4 = 14; public static String conversion(double value, int point, int round){ String string; BigDecimal bd = new BigDecimal(value); int mode = 0; switch (round){ case ROUND_F: return bd.toString(); case ROUND_CUT: mode = BigDecimal.ROUND_DOWN; break; case ROUND_UP: mode = BigDecimal.ROUND_UP; break; case ROUND_5_4: mode = BigDecimal.ROUND_HALF_UP; break; } BigDecimal bigDecimal = bd.setScale(point,mode); string = bigDecimal.toString(); Log.d("hogehoge","bd =" + bigDecimal.toString() ); return string; } public static String conversion(String value,int point,int round){ return conversion(Double.parseDouble(value),point,round); } public static String formatBasic(String string){ DecimalFormat df1 = new DecimalFormat("#,##0.############"); return df1.format(Double.valueOf(string)); } }
メインアクティビティの、Calcからのコールバックで送られてくる文字列に対して、
RoundSelectorを適用します。
calc = new Calc(); calc.setListener(new Calc.Callback() { @Override public void onTextChanged(String string) { displayView.setText(string); Log.d("hoge", RoundSelector.conversion(string,0,RoundSelector.ROUND_CUT)); Log.d("hoge", RoundSelector.conversion(string,3,RoundSelector.ROUND_CUT)); Log.d("hoge", RoundSelector.conversion(string,5,RoundSelector.ROUND_5_4)); Log.d("hoge", RoundSelector.conversion(string,3,RoundSelector.ROUND_F)); } });
10÷6は、以下のような結果となります。
D/hoge: 1
D/hoge: 1.666
D/hoge: 1.66667
D/hoge: 1.6666666666666667406815349750104360282421112060546875
ただし、現段階ではコールバックで送られてくるテキスト全てに適用しているので、
ユーザーが少数を入力した際も丸められてしまいます。
テキストが、入力中なのか、計算の答えとして送られてきたものなのか、
判別する処理が必要です。
私の場合は、State.getStateValue()で得られる定数がSTATE_D(答えを表示している状態)
であれば数値の文字列を成型するように修正しましたが、
もっといいやり方もあると思うので、
独自の実装をしてみてください。
端数処理方法の設定として、
・小数点以下の桁数
・丸める方法
の定数を、SharedPreferencesに保存しておくと、
ユーザーの使用環境に合わせた、
利便性の高い電卓にすることができます。
【電卓アプリを作るvol.5】UIをレイアウトする
前回まで・・・
汎用性の高いCalcクラスを作りました
今回は、最低限のUIと電卓として機能するまでを行います。
UIをレイアウトします。
お好みのようにデザインしてください。
基本的には、電卓の液晶画面にあたる部分をTextViewで、
キーはButtonで配置しましたが、
キャンバスで描画する芸術的な電卓もありだと思います。
今後、押したら画像が変わる9パッチのボタン画像や、
レイアウトのサイズに合わせて文字列を自動で拡大・縮小する
ビューもご紹介しようと思いますが、
今回は最低限の作業を行っていきます。
基本的なレイアウトを作りました。
Calcのインスタンス化と液晶表示
Calcクラス型のフィールドを定義します。
calcとしました。
メインアクティビティのオンクリエイト等で、
Calcをインスタンス化します。
Calcで定義したコールバックを受け取れるようにします。
calc = new Calc(); calc.setListener(new Calc.Callback() { @Override public void onTextChanged(String string) { textView.setText(string); } });
onTextChangedの引数に入っている文字列が、
電卓の液晶画面に表示すべき文字列です。
まだ、doubleがそのまま文字列になっただけなので、
割り切れない数は、かなり桁数が多くなりますが、
今後、フォーマットして表示できるように改良します。
ボタン操作
ボタン全てにリスナーを付けます。
ボタンが押されたら、それに対応した定数を
calc.input()の引数に渡します。
[=]キーだったら、calc.input(Calc.KEY_EQUAL);という具合です。
数字キーは、ボタンテキストをそのままint型にして
calc.input(5);のようにします。
もし、和風な電卓で、表示が「壱、弐、参」などにしていたら、
それぞれにも定数を用意する必要があります。
Integer.parseInt("壱");ってできるのかな?(笑)
全てのボタンに適用したら、
何かボタンが押されるたびに、コールバックで文字列が送られてくるので、
それを表示すれば、電卓としては目的達成です!
電卓って、意外とボタンが多いことに気づきます(笑)
以下のように、IDで判断して処理を分けてもいいですが、
文字列で判定すると、ローカライズの際に面倒になる可能性があります。
public void onKeyClick(View v) { switch (v.getId()) { case R.id.b_00: calc.input(Calc.KEY_00); break; case R.id.b_dot: calc.input(Calc.KEY_DOT); break; case R.id.b_equal: calc.input(Calc.KEY_EQUAL); break; case R.id.b_tasu: calc.input(Calc.KEY_TASU); break; ・・・
次回は、”計算結果を丸め”て、会社の端数処理基準等に合わせた表示を可能にする、
小数点セレクター・ラウンドセレクターを実装していきます。
【電卓アプリを作るvol.4】ソースコード2/2
Calc
メインの電卓クラス
アクティビティからnewして使います。
public class Calc implements Context { // ディスプレイ表示が変わるたびに、文字列をコールバックします public interface Callback{ void onTextChanged(String string); } Callback callback; // 最大表示可能桁数 private final int MAX_DIGIT = 12; static public final int KEY_00 = 10, KEY_DOT = 20, KEY_EQUAL = 21, KEY_TASU = 22, KEY_HIKU = 23, KEY_KAKERU = 24, KEY_WARU = 25, KEY_PERCENT = 26, KEY_SIGN_CHANGE = 27, KEY_C = 31, KEY_AC = 32, KEY_BACK_SPACE = 32; private StringBuilder stringBuilder; private double value1,value2; // 左辺、右辺保持 private int enzanshi; private State state; // コンストラクター。ACで初期化しています public Calc() { allClear(); } public void setListener(Callback callback){ this.callback = callback; } // 現在の表示をdoubleで取得 private double getValue(){ return Double.valueOf(stringBuilder.toString()); } // 文字列を表示用変数にセット private void set(String s){ stringBuilder = new StringBuilder(s); } // doubleを文字列に変換して、表示用変数にセット private void set(double d){ stringBuilder = new StringBuilder(String.valueOf(d)); } private boolean isENZAN_Key(int key) { if (key == KEY_TASU || key == KEY_HIKU || key == KEY_KAKERU || key == KEY_WARU){ return true; } return false; } private boolean isNumber_key(int key) { if (0 <= key && key <= 9 || key == KEY_DOT){ return true; } return false; } // 引数のキーを入力 public void input(int inputKey) { if (isNumber_key(inputKey)) state.inputNumber(this, inputKey); else if (inputKey == KEY_00) { state.inputNumber(this, 0); state.inputNumber(this, 0); } else if (isENZAN_Key(inputKey)) state.inputEnzanshi(this, inputKey); else if (inputKey == KEY_EQUAL) state.inputEqual(this); else if (inputKey == KEY_C) state.inputClear(this); else if (inputKey == KEY_AC) allClear(); else if (inputKey == KEY_BACK_SPACE) state.inputBackSpace(this); else if (inputKey == KEY_SIGN_CHANGE) signChange(); else if (inputKey == KEY_PERCENT) state.inputPercent(this); callback.onTextChanged(stringBuilder.toString()); } // 四則演算します private double calculation(double d1,int ope,double d2){ try { double ans = 0; switch (ope) { case KEY_TASU: ans = d1 + d2; break; case KEY_HIKU: ans = d1 - d2; break; case KEY_KAKERU: ans = d1 * d2; break; case KEY_WARU: if (d2 == 0){ // エラー return 0; } else { ans = d1 / d2; } break; } return ans; } catch (java.lang.IllegalArgumentException a){ // エラー } return 0; } // +/-の変換 private void signChange() { if (state.getStateValue(this) == State.STATE_B){ setLeftDouble(); set("-0"); changeState(StateC.getInstance()); } else { if (stringBuilder.indexOf("-") == -1){ stringBuilder.insert(0,"-"); } else { stringBuilder.deleteCharAt(0); } } } // 末尾に数字を追加 @Override public void addNum(int key) { if (stringBuilder.length() >= MAX_DIGIT)return; if (key == KEY_DOT) { if (stringBuilder.indexOf(".") == -1) stringBuilder.append("."); } else if (stringBuilder.length() == 1 && stringBuilder.charAt(0) == '0') { stringBuilder.replace(0, 1, "" + key); } else if (stringBuilder.toString().equals("-0")) { stringBuilder.replace(1, 2, "" + key); } else { stringBuilder.append("" + key); } } // 表示されている数値を左辺として保持 @Override public void setLeftDouble() { value1 = getValue(); } // 表示されている数値を右辺として保持 @Override public void setRightValue() { value2 = getValue(); } // 押された演算子を保持 @Override public void setEnzanshi(int key) { enzanshi = key; } @Override public void keisan() { double ans = calculation(value1,enzanshi,getValue()); set(ans); } // 表示の数値と、保持していた右辺・演算子で計算 @Override public void teisuKeisan() { double ans = calculation(getValue(),enzanshi,value2); set(ans); } // 演算子が+、-のときは左辺のxパーセント // ×、÷のときは右辺の100分の1をセット @Override public void percent() { if (enzanshi == KEY_TASU || enzanshi == KEY_HIKU){ set((getValue() * value1) / 100d); } else { set(getValue() / 100d); } } @Override public void dividedBy100() { set(getValue() / 100d); } // 1文字消す @Override public void backSpace() { if (stringBuilder.length() == 1){ set("0"); } else if (stringBuilder.length() == 2 && stringBuilder.indexOf("-") == 0){ set("0"); } else { stringBuilder.deleteCharAt(stringBuilder.length() - 1); set(stringBuilder.toString()); } } @Override public void clear() { if (stringBuilder.toString().equals("0")) { allClear(); } else { set("0"); } } @Override public void allClear() { set("0"); enzanshi = -1; value1 = 0; value2 = 0; changeState(StateA.getInstance()); } @Override public void dispZero() { set("0"); } // 状態を明示的に変更 @Override public void changeState(State instance) { this.state = instance; } }
【電卓アプリを作るvol.3】ソースコード1/2
前回まで・・・
電卓のアルゴリズムについて考えました
今回は、ソースコードを掲載します。
参考書のような詳細な解説は割愛しますが、
状態遷移表とコードを何度も見比べれば、
理解できると思います。
アンドロイドスタジオのエクスプローラービューは↓こうなってます
Context
各状態クラスから、Calc(電卓のメインの処理)クラスに知らせるためのインターフェイス
public interface Context { void addNum(int key); void setLeftDouble(); void setEnzanshi(int key); void changeState(State instance); void clear(); void backSpace(); void percent(); void dispZero(); void keisan(); void allClear(); void teisuKeisan(); void setRightValue(); void dividedBy100(); }
State
各状態クラスに継承させるインターフェイス
public interface State { static final int STATE_A = 1,STATE_B = 2,STATE_C = 3,STATE_D = 4; int getStateValue(Context context); void inputNumber(Context context, int key); void inputEnzanshi(Context context, int key); void inputClear(Context context); void inputEqual(Context context); void inputBackSpace(Context context); void inputPercent(Context context); }
StateA
左辺入力中の状態
以下、各ステートは複数のインスタンスは必要ないので、
シングルトンパターンを採用しています。
シングルトンについては、詳しく解説されたサイトが多数あるので
そちらをご参考ください。
public class StateA implements State{ private static StateA ourInstance = new StateA(); public static StateA getInstance() { return ourInstance; } private StateA() { } @Override public int getStateValue(Context context) { return STATE_A; } @Override public void inputNumber(Context context, int key) { context.addNum(key); } @Override public void inputEnzanshi(Context context, int key) { context.setLeftDouble(); context.setEnzanshi(key); context.changeState(StateB.getInstance()); } @Override public void inputClear(Context context) { context.clear(); } @Override public void inputEqual(Context context) { } @Override public void inputBackSpace(Context context) { context.backSpace(); } @Override public void inputPercent(Context context) { context.dividedBy100(); } }
StateB
演算子入力中の状態(抽象メソッド以外は省略)
@Override public int getStateValue(Context context) { return STATE_B; } @Override public void inputNumber(Context context, int key) { context.dispZero(); context.addNum(key); context.changeState(StateC.getInstance()); } @Override public void inputEnzanshi(Context context, int key) { context.setEnzanshi(key); } @Override public void inputClear(Context context) { context.clear(); context.changeState(StateC.getInstance()); } @Override public void inputEqual(Context context) { context.setRightValue(); context.keisan(); context.changeState(StateD.getInstance()); } @Override public void inputBackSpace(Context context) { } @Override public void inputPercent(Context context) { context.percent(); }
StateC
右辺入力中の状態(抽象メソッド以外は省略)
@Override public int getStateValue(Context context) { return STATE_C; } @Override public void inputNumber(Context context, int key) { context.addNum(key); } @Override public void inputEnzanshi(Context context, int key) { context.setEnzanshi(key); context.keisan(); context.setLeftDouble(); context.setRightValue(); context.changeState(StateB.getInstance()); } @Override public void inputClear(Context context) { context.clear(); } @Override public void inputEqual(Context context) { context.setRightValue(); context.keisan(); context.changeState(StateD.getInstance()); } @Override public void inputBackSpace(Context context) { context.backSpace(); } @Override public void inputPercent(Context context) { context.percent(); }
StateD
イコールが押された直後の状態(抽象メソッド以外は省略)
@Override public void inputNumber(Context context, int key) { context.allClear(); context.addNum(key); context.changeState(StateA.getInstance()); } @Override public void inputEnzanshi(Context context, int key) { context.setLeftDouble(); context.setEnzanshi(key); context.changeState(StateB.getInstance()); } @Override public void inputClear(Context context) { context.clear(); } @Override public void inputEqual(Context context) { context.teisuKeisan(); } @Override public void inputBackSpace(Context context) { } @Override public void inputPercent(Context context) { context.dividedBy100(); }