「Android家計簿アプリ(Java)」開発入門
今回は、「Android家計簿アプリ」の作り方をお話していきたいと思います。
アプリに実装する機能は、
→「プログラミング初心者のための「シンプル家計簿アプリ」開発入門」
と同様の機能を実装していきます。
「Java」である程度アプリを作れる方を想定してご説明していきますので、まだ「Androidアプリ」の開発に取り組んだことが無い方は、先に「Androidアプリの作り方」を学んでから読んでみてください。
「Android家計簿アプリ」の動作
「Android家計簿アプリ」の主な機能は、
- データ登録
- データ編集
- データ削除
- 指定期間データの表示
になります。
実際のアプリの動作は下の動画をご覧ください。
画面構成
「Android家計簿アプリ」の画面は、
- データ一覧画面(メイン)
- データ登録画面
- データ編集画面
の3つになります。
ファイル構成
「Androidアプリ」を構成するファイルには、
- プログラムファイル
- レイアウトファイル
- スタイルファイル
- ストリングファイル
- 素材ファイル(画像など)
- マニフェストファイル
などがあります。
プログラムファイルの種類と継承関係
プログラムファイルは、13個のクラスで構成されていて「継承関係」を持つクラスもあります。
「クラスファイル一覧」は下記の様になります。
- AccountDao.java
- DAOインターフェース
- AccountData.java
- 家計簿データクラス
- AccountDataUtilities.java
- データ登録・編集用ユーティリティクラス
- AccountItemDecoration.java
- 家計簿データ表示装飾用クラス
- AccountRecyclerAdapter.java
- リサイクラーアダプタークラス
- AccountUtilities.java
- 全体共用ユーティリティクラス
- AccountViewHolder.java
- ビューホルダークラス
- AppDatabase.java
- データベースクラス
- DatabaseHelper.java
- データベースヘルパークラス
- DatePickerFragment.java
- 日付選択用フラグメント用クラス
- EditAccountDataActivity.java
- データ編集用アクティビィ
- MainActivity.java
- メイン画面用アクティビティ
- RegistAccountDataActivity.java
- データ登録用アクティビティ
これらのファイルの内容については後ほどご説明をしていきたいと思います。
クラス継承の関係は下図のようになります。
「全体共用ユーティリティクラス」は、「DatabaseHelperクラス」など、上図に記載されていない他のクラスでも継承しています。
そして、「Androidが用意しているクラス」を継承しているクラスもあります。
データの保存方法
「家計簿データ」の保存には、Android OSに搭載されている「SQLiteデータベース」を利用します。
「SQLiteデータベース」には2種類あり、「android.database.sqliteパッケージ」内のクラスを利用する方法と「Room」と呼ばれる「抽象化レイヤー」を利用する方法があります。
それぞれの利用のイメージは下図のようになりますが、Googleは「Room」を利用することを推奨しているため、今回は「Room」を利用してデータベースを操作していきます。
「SQLiteデータベース」は、「SQL言語」を利用して操作していきますが、まだ「データベース・SQL」を学んだことが無い方は、先に学習に取り組んでおいてください。
→「「データベース」って何?データベースを理解する!データベースの基本と「SQL」入門 | ~プログラミングライフスタイル~」
といっても、「Room」は極力SQL文を書かない作りとなっているので、今回作成したアプリでは「SQL文」を書くことはあまりありません。
テーブルの作成とカラムの定義
データベースを利用するためには、「テーブルの作成・カラムの定義」を行っていく必要がありますが、今回作成する「Android家計簿アプリ」では、「AccountDataクラス」でその内容を定義しています。
import androidx.room.Entity; import androidx.room.PrimaryKey; /** * 家計簿データクラス(Room) */ @Entity(tableName = "account_data") //テーブル名を定義 public class AccountData { @PrimaryKey(autoGenerate = true) public int id; //「id」カラムを定義 public String content; //「内容」カラムを定義 public int price; //「金額」カラムを定義 public long date; //「日付」カラムを定義 /** * コンストラクタ * @param content 内容 * @param price 金額 * @param date 日付 */ public AccountData(String content, int price, long date) { this.content = content; //「内容」を設定 this.price = price; //「金額」を設定 this.date = date; //「日付」を設定 } /** * 「id」を取得(Getter) * @return id */ public int getId() { return id; } /** * 「内容」を取得(Getter) * @return 内容 */ public String getContent() { return content; } /** * 「価格」を取得(Getter) * @return */ public int getPrice() { return price; } /** * 「日付」を取得(Getter) * @return */ public long getDate() { return date; } /** * 「家計簿データ」を更新 * @param content 更新する「内容」 * @param price 更新する「金額」 * @param date 更新する「日付」 * @return 更新した「家計簿データ」 */ public AccountData update(String content, int price, long date){ this.content = content; //「内容」を設定 this.price = price; //「金額」を設定 this.date = date; //「日付」を設定 return this; } }
今回作成するプログラムでは、1つずつの処理にコメントを付記していますので、「処理の内容」についてはコメントを読んでみてください。
次ページで、「デバッグ方法」についても説明していきますので、デバッグを行いながら、処理の流れを追いつつ「処理の内容」を掴んでいってください。
処理の内容を大別すると、
- テーブル名を「@Entityアノテーション」で定義
- クラスフィールドで「カラムの内容」を定義
- フィールドの「Getter・Setter」の定義
- 「データ更新用メソッド」の定義
などを行っています。
データベース操作クラス
データ操作に必要なクラス・インターフェースについて見ていきたいと思います。
「DAO(Data Access Object)」インターフェースには、データの「読み込み・追加・変更・削除」に必要なメソッドが定義されています。
import java.util.List; import androidx.room.Dao; import androidx.room.Delete; import androidx.room.Insert; import androidx.room.Query; import androidx.room.Update; /** * Room(SQLite)用データベース操作インターフェース */ @Dao public interface AccountDao { /** * 指定した期間のデータを取得 * @param startDate 表示開始日 * @param lastDate 表示終了日 * @return 家計簿データリスト */ @Query("SELECT * FROM account_data WHERE date > :startDate AND date < :lastDate ORDER BY date ASC") List<AccountData> getData(long startDate, long lastDate); /** * データを追加 * @param ad 追加データ */ @Insert void insert(AccountData ad); /** * データを更新 * @param ad 更新データ */ @Update void update(AccountData ad); /** * データを削除 * @param ad 削除データ */ @Delete void delete(AccountData ad); }
データ読み込みに必要な「独自実装」以外の部分については、SQL文を書かなくても「AccountDataクラスのインスタンス」をメソッドの引数に渡すと、「インスタンスのidフィールド」が一致するデータの「追加・更新・削除」を行ってくれます。
もし「android.database.sqliteパッケージ」のクラスを利用した場合は、これらの処理の一つ一つに対してSQL文を作成しなければならないため、「Room」を利用することで、「SQL文の記述量」を減らすことができます。
「データベースの処理を実行するクラス」は、「DatabaseHelperクラス」ですが、まだ説明をしていない内容なども含まれているため、現在のところはどのような処理になっているのかを俯瞰しておいてください。
import android.os.Handler; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import androidx.room.Room; /** * データベースヘルパー */ public class DatabaseHelper extends AccountUtilities{ private AppDatabase mDb; //DB private AccountDao mDao; //DAO private AccountData mAd; //家計簿データ private List<AccountData> mLad = null; //家計簿データリスト private final String DB_NAME = "account-database"; //DB名 final Handler mHandler = new Handler(); //ハンドラー /** * コンストラクタ */ public DatabaseHelper() { //Roomインスタンスを取得 mDb = Room.databaseBuilder(getMainActivity(), AppDatabase.class, DB_NAME).build(); //DAOを取得 mDao = mDb.accountDao(); } /** * DBからデータ取得&表示 */ public void getData() { //ExecutorServiceを取得 ExecutorService executor = Executors.newSingleThreadExecutor(); //ExecutorServiceでタスクを実行(非同期処理) executor.execute(new Runnable() { @Override public void run() { try { long startDate = getMainActivity().getDisplayStartDate(); //「表示開始日」の取得 long lastDate = getMainActivity().getDisplayLastDate(); //「表示終了日」の取得 mLad = mDao.getData(startDate, lastDate); //DBからデータを取得 } catch (Exception e) { //データ取得エラー時の処理 displayMessage(AccountUtilities.getStr(R.string.canNotGetData)); } //データ取得成功時の処理 mHandler.post(new Runnable() { @Override public void run() { //リサイクラービューのデータを更新 getMainActivity().updateDataView(mLad); //合計金額の表示 getMainActivity().displaySumPrice(mLad); } }); } }); } /** * DBへデータ追加 * @param ad 追加する家計簿データ */ public void insertData(AccountData ad){ mAd = ad; //家計簿データ //ExecutorServiceを取得 ExecutorService executor = Executors.newSingleThreadExecutor(); //ExecutorServiceでタスクを実行(非同期処理) executor.execute(new Runnable() { @Override public void run() { try { //会計簿データをDBへ追加 mDao.insert(mAd); } catch (Exception e) { //データ追加エラー時の処理 displayMessage(AccountUtilities.getStr(R.string.canNotAddData)); } //データ追加成功時の処理 mHandler.post(new Runnable() { @Override public void run() { displayMessage(AccountUtilities.getStr(R.string.successRegistData)); getData(); //DBからデータを取得 getMainActivity().displaySumPrice(mLad); //合計金額の表示 } }); } }); } /** * DBのデータを更新 * @param position 更新データの位置 * @param content 内容 * @param price 価格 * @param date 日付 */ public void updateData(int position, String content, int price, long date){ //ExecutorServiceの処理に渡す値の定数 final String pContent = content; //内容 final int pPrice = price; //価格 final long pDate = date; //日付 final int pPosition = position; //データの位置 //ExecutorServiceを取得 ExecutorService executor = Executors.newSingleThreadExecutor(); //ExecutorServiceでタスクを実行(非同期処理) executor.execute(new Runnable() { @Override public void run() { try { AccountData updateAd = null; //更新する家計簿データ try { //アダプター内の家計簿データを更新&更新データの取得 updateAd = getAdapter().getAccountData(pPosition).update(pContent, pPrice, pDate); } catch (Exception e){ //データ更新エラー時の処理 displayMessage(AccountUtilities.getStr(R.string.canNotUpdateData)); } //DBのデータを更新 mDao.update(updateAd); } catch (Exception e) { //データ更新エラー時の処理 displayMessage(AccountUtilities.getStr(R.string.canNotUpdateData)); } //データ更新成功時の処理 mHandler.post(new Runnable() { @Override public void run() { displayMessage(AccountUtilities.getStr(R.string.successUpdateData)); getMainActivity().displaySumPrice(mLad); //合計金額の表示 } }); } }); } /** * DBのデータを削除 * @param ad 削除する家計簿データ * @param position データの位置 */ public void deleteData(AccountData ad, final int position){ //ExecutorServiceの処理に渡す値の定数 mAd = ad; //ExecutorServiceを取得 ExecutorService executor = Executors.newSingleThreadExecutor(); //ExecutorServiceでタスクを実行(非同期処理) executor.execute(new Runnable() { @Override public void run() { try { //DBのデータを削除 mDao.delete(mAd); //アダプター内の家計簿データを削除 getAdapter().deleteAccountDataList(position); } catch (Exception e) { //データ削除エラー時の処理 displayMessage(AccountUtilities.getStr(R.string.canNotDeleteData)); } //データ削除成功時の処理 mHandler.post(new Runnable() { @Override public void run() { displayMessage(AccountUtilities.getStr(R.string.successDeleteData)); getMainActivity().displaySumPrice(mLad); //合計金額の表示 } }); } }); } }
「Room」では、データベースの処理に「非同期処理」を利用しないとエラーが発生しますので、Javaの「ExecutorService」を利用して非同期処理を実行しています。
最後に、「データベース」を表すクラスが「AppDatabase抽象クラス」です。
import androidx.room.Database; import androidx.room.RoomDatabase; /** * Room DataBaseクラス */ @Database(entities = {AccountData.class}, version = 1, exportSchema = false) public abstract class AppDatabase extends RoomDatabase { //DAO用メソッド public abstract AccountDao accountDao(); }
これは、「データベース」そのものを表すクラスで「RoomDatabaseクラス」を継承していますが「抽象クラス」のため、このままでは「インスタンス化」ができません。
そのため、「DatabaseHelperクラス」のコンストラクタ内で「Roomインスタンス(データベースインスタンス)」を格納するフィールドのデータ型として利用されています。
「データベースインスタンス」は「DatabaseHelperクラス」のコンストラクタの中で「Roomクラスのメソッド」から生成されるようになっています。
以上が、データベースに関するクラスの内容になりますが、「DatabaseHelperクラス」の処理が「データベースの処理の起点」になっています。
データ一覧画面(メイン画面)
「Android家計簿アプリ」を起動すると表示される「データ一覧画面(メイン画面)」ですが、起動直後の処理では、
- データベースからデータの取得
- リサイクラービューへの表示
を行っていますが、「リサイクラービュー」は、画面に「リスト形式」でデータを表示するための仕組みです。
リサイクラービューを表示するために、今回作成したアプリでは、
- リサイクラービュー
- MainActivityのレイアウトファイルの「ウィジェット」として作成
- リサイクラーアダプター
- 1行分の表示データ(View)を設定・作成する。「RecyclerView.Adapter」を継承して作成し、本アプリでは、「AccountRecyclerAdapterクラス」として作成。
- ビューホルダー
- 表示データの内容を設定する。「RecyclerView.ViewHolder」を継承して作成し、本アプリでは、「AccountViewHolderクラス」として作成。
- データ表示用レイアウトファイル
- データ表示の「レイアウト」を定義する「レイアウトファイル(XML)」。
- アイテムデコレーション
- 表示内容を装飾する。「RecyclerView.ItemDecoration」を継承して作成し、本アプリでは、「AccountItemDecorationクラス」として作成。
のようにそれぞれのクラスを作成しています。
リサイクラービューの詳細は、
→「RecyclerView でリストを作成する | Android デベロッパー | Android Developers」
を参照してみてください。
リサイクラービュー&レイアウトファイル(activity_main.xml)
「リサイクラービュー」の本体を表す「レイアウトファイル(activity_main.xml)」は、下記のようになります。
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <Button android:id="@+id/MaRegistDataBtn" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginEnd="5dp" android:layout_marginLeft="5dp" android:layout_marginRight="5dp" android:layout_marginStart="5dp" android:layout_marginTop="7dp" android:background="@drawable/button_style_1" android:text="@string/btnRegistData" android:textColor="#FFFFFF" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="1.0" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/MaBorder1" /> <Button android:id="@+id/MaSelectStartDateBtn" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginEnd="2dp" android:layout_marginLeft="5dp" android:layout_marginRight="2dp" android:layout_marginStart="5dp" android:background="@drawable/button_style_2" android:text="@string/tvDisplayStartDate" android:textColor="#FFFFFF" app:layout_constraintBaseline_toBaselineOf="@+id/MaSelectEndDateBtn" app:layout_constraintEnd_toStartOf="@+id/MaSelectEndDateBtn" app:layout_constraintStart_toStartOf="parent" /> <Button android:id="@+id/MaSelectEndDateBtn" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginEnd="5dp" android:layout_marginLeft="2dp" android:layout_marginRight="5dp" android:layout_marginStart="2dp" android:layout_marginTop="30dp" android:background="@drawable/button_style_2" android:text="@string/tvDisplayEndDate" android:textColor="#FFFFFF" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toEndOf="@+id/MaSelectStartDateBtn" app:layout_constraintTop_toTopOf="@+id/MaEndDateTv" /> <ImageView android:id="@+id/MaImageView" android:layout_width="0dp" android:layout_height="0dp" android:scaleType="fitXY" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toTopOf="@+id/MaHguideline2" app:srcCompat="@drawable/header_xxxhdpi_1280" /> <ImageView android:id="@+id/MaBorder4" android:layout_width="0dp" android:layout_height="wrap_content" android:scaleType="fitXY" app:layout_constraintBottom_toTopOf="@+id/MaSumPriceTv" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:srcCompat="@drawable/separate_border" /> <ImageView android:id="@+id/MaBorder2" android:layout_width="0dp" android:layout_height="wrap_content" android:scaleType="fitXY" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/MaHguideline1" app:srcCompat="@drawable/separate_border" /> <ImageView android:id="@+id/MaBorder3" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="5dp" android:scaleType="fitXY" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/MaSelectStartDateBtn" app:srcCompat="@drawable/separate_border" /> <ImageView android:id="@+id/MaBorder1" android:layout_width="0dp" android:layout_height="wrap_content" android:scaleType="fitXY" app:layout_constraintBottom_toTopOf="@+id/MaRegistDataBtn" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/MaImageView" app:layout_constraintVertical_bias="1.0" app:srcCompat="@drawable/separate_border" /> <TextView android:id="@+id/MaStartDateTv" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintBaseline_toBaselineOf="@+id/MaToTv" app:layout_constraintEnd_toStartOf="@+id/MaToTv" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/MaBorder2" /> <TextView android:id="@+id/MaToTv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/tvTo" app:layout_constraintEnd_toStartOf="@+id/MaEndDateTv" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toEndOf="@+id/MaStartDateTv" app:layout_constraintTop_toBottomOf="@+id/MaErrorDateTv" /> <TextView android:id="@+id/MaEndDateTv" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintBaseline_toBaselineOf="@+id/MaToTv" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toEndOf="@+id/MaToTv" tools:layout_editor_absoluteY="163dp" /> <TextView android:id="@+id/MaErrorDateTv" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginEnd="20dp" android:layout_marginLeft="20dp" android:layout_marginRight="20dp" android:layout_marginStart="20dp" android:textColor="#FF0000" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/MaBorder2" /> <TextView android:id="@+id/MaSumPriceTv" android:layout_width="0dp" android:layout_height="wrap_content" android:autoSizeMaxTextSize="25sp" android:autoSizeMinTextSize="15sp" android:autoSizeTextType="uniform" android:gravity="right|center_vertical" android:textSize="20sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/MaHguideline1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_begin="135dp" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/MaHguideline2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_begin="75dp" /> <androidx.recyclerview.widget.RecyclerView android:id="@+id/MaAccountRecyclerView" android:layout_width="409dp" android:layout_height="0dp" app:layout_constraintBottom_toTopOf="@+id/MaBorder4" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/MaBorder3" app:layout_constraintVertical_bias="1.0" /> </androidx.constraintlayout.widget.ConstraintLayout>
コードだけではわかりづらいと思いますので、主要な「ウィジェットのID」を記した下図をご参照ください。
たくさんウィジェットがありますが、「MaAccountRecyclerView」のIDが付いている「ウィジェット」が「リサイクラービュー」です。
リサイクラーアダプター
次に「AccountRecyclerAdapterクラス」について見ていきたいと思います。
このクラスは、「1行分の表示データ」を作成するためのクラスですが、「表示データを保持する」という「データ管理」の役割も持つため、「データ操作」に関する、
- データ部分をタップした時の処理
- データ更新
- データ削除
に関する処理なども含まれています。
import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import java.util.ArrayList; import java.util.Calendar; /** * リサイクラーアダプタークラス */ public class AccountRecyclerAdapter extends RecyclerView.Adapter<AccountViewHolder>{ private ArrayList<AccountData> mLad = null; //家計簿データリスト private Calendar mCalendar = AccountUtilities.getCalendar(); //カレンダー private MainActivity mActivity = AccountUtilities.getMainActivity(); //メインアクティビティ /** * コンストラクタ * @param lad 家計簿データリスト */ public AccountRecyclerAdapter(ArrayList<AccountData> lad) { mLad = lad; } /** * ビューホルダー作成時に実行する処理 * @param parent ビューホルダーのビューグループ * @param viewType getItemViewTypeメソッドの返り値 * @return ビューホルダー */ @Override public AccountViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { //家計簿データ表示用のViewを取得 View inflater = LayoutInflater.from(parent.getContext()).inflate(R.layout.account_list_item, parent,false); //ビューホルダーの取得 final AccountViewHolder viewHolder = new AccountViewHolder(inflater); //「家計簿データ」タッチ時の処理を設定 viewHolder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //タッチされた「家計簿データ」の位置を取得 final int position = viewHolder.getAdapterPosition(); //アラートダイアログの生成 AlertDialog.Builder alertDialog = new AlertDialog.Builder(AccountUtilities.getMainActivity()); alertDialog.setTitle(R.string.selectTask); //アラートダイアログのタイトル文字列を設定 alertDialog.setMessage(R.string.askingTaskType); //アラートダイアログの表示メッセージを設定 //「編集ボタン」タッチ時の処理 alertDialog.setPositiveButton(R.string.edit, new DialogInterface.OnClickListener(){ public void onClick(DialogInterface dialog, int which) { //インテントの生成 Intent intent = new Intent(AccountUtilities.getMainActivity().getApplicationContext(), EditAccountDataActivity.class); intent.putExtra("id", mLad.get(position).getId()); //インテントにIDを設定 intent.putExtra("position", position); //インテントにタッチされたアイムのポジションを設定 AccountUtilities.getMainActivity().startActivity(intent); //「編集画面」を表示 }}); //「削除ボタン」タッチ時の処理 alertDialog.setNegativeButton(R.string.delete, new DialogInterface.OnClickListener(){ public void onClick(DialogInterface dialog, int which) { //DBのデータを削除 AccountUtilities.getDB().deleteData(mLad.get(position),position); }}); alertDialog.show(); } }); return viewHolder; } /** * 表示データをビューホルダーに設定 * @param holder アカウントビューホルダー * @param position 位置 */ @Override public void onBindViewHolder(@NonNull AccountViewHolder holder, int position) { holder.mContent.setText(mLad.get(position).getContent()); //「内容」を設定 holder.mPrice.setText(String.valueOf(mLad.get(position).getPrice())+mActivity.getString(R.string.priceUnit)); //「金額」を設定 mCalendar.setTimeInMillis(mLad.get(position).getDate()); //カレンダーにタイムスタンプを設定 //「日付」を設定 holder.mDate.setText(mCalendar.get(Calendar.YEAR) + mActivity.getString(R.string.year) + (mCalendar.get(Calendar.MONTH) + 1) + mActivity.getString(R.string.month) + mCalendar.get(Calendar.DATE) + mActivity.getString(R.string.day)); } /** * データ数の取得 * @return データ数 */ @Override public int getItemCount() { return mLad.size(); } /** * 家計簿データリストから指定位置のデータを削除 * @param index 削除するデータの位置 */ public void deleteAccountDataList(int index){ mLad.remove(index); //家計簿データリストから指定位置のデータを削除 notifyItemRemoved(index); //「削除」後の家計簿データを表示に反映 } /** * 家計簿データの取得(Getter) * @param position 取得データの位置 * @return 家計簿データ */ public AccountData getAccountData(int position){ return mLad.get(position); } /** * 家計簿データの更新 * @param position 更新データの位置 * @param content 更新する「内容」 * @param price 更新する「金額」 * @param date 更新する「日付(タイムスタンプ)」 */ public void updateAccountData(int position,String content, int price, long date ){ AccountData ad = mLad.get(position); //更新するデータを取得 ad.update(content, price, date); //データを更新 notifyItemChanged(position, ad); //「更新」後の内容を表示に反映 } }
「画面に表示するデータ」は「ArrayList」の「mLadフィールド」で保持しています。
ビューホルダー
「AccountViewHolder」クラスが「ビューホルダー」になりますが、このクラスで、「表示内容のウィジェット」などを取得しています。
import android.view.View; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; /** * RecyclerViewのViewHolder */ public class AccountViewHolder extends RecyclerView.ViewHolder { public TextView mContent; //「内容」 public TextView mPrice; //「価格」 public TextView mDate; //「日付」 //「内容」を取得(getter) public TextView getContent() { return mContent; } //「価格」を取得(getter) public TextView getPrice() { return mPrice; } //「日付」を取得(getter) public TextView getDate() { return mDate; } /** * constructor * @param itemView 表示項目のView */ public AccountViewHolder(@NonNull View itemView) { super(itemView); mContent = (TextView)itemView.findViewById(R.id.tvContent); //「内容」のウィジェットを取得 mPrice = (TextView)itemView.findViewById(R.id.tvPrice); //「価格」のウィジェットを取得 mDate = (TextView)itemView.findViewById(R.id.tvDate); //「日付」のウィジェットを取得 } }
データ表示用レイアウトファイル(account_list_item.xml)
「1データ分の表示内容」を定義しているレイアウトファイルは「account_list_item.xml」ですが、「レイアウトの内容」は下記のようになります。
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="44dp"> <TextView android:id="@+id/tvContent" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="3dp" android:paddingLeft="10dp" android:text="@string/tvContent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="@+id/guideline" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/tvPrice" android:layout_width="0dp" android:layout_height="wrap_content" android:gravity="right|center_vertical" android:text="@string/tvPrice" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="@+id/guideline" app:layout_constraintTop_toBottomOf="@+id/tvContent" /> <TextView android:id="@+id/tvDate" android:layout_width="0dp" android:layout_height="0dp" android:gravity="right|center_vertical" android:paddingLeft="5dp" android:text="@string/tvDate" app:layout_constraintBottom_toBottomOf="@+id/tvPrice" app:layout_constraintEnd_toStartOf="@+id/guideline" app:layout_constraintStart_toStartOf="@+id/MaListMark" app:layout_constraintTop_toTopOf="parent" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="24dp" android:layout_marginStart="24dp" android:orientation="vertical" app:layout_constraintGuide_begin="130dp" app:layout_constraintStart_toStartOf="parent" /> <ImageView android:id="@+id/MaListMark" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="5dp" android:layout_marginStart="5dp" android:layout_marginTop="5dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:srcCompat="@drawable/list_mark" /> </androidx.constraintlayout.widget.ConstraintLayout>
アイテムデコレーション
今回は「表示データの区切り線」を表示するために「アイテムデコレーション」用の「AccountItemDecorationクラス」を作成しています。
import android.content.Context; import android.graphics.Canvas; import android.graphics.drawable.Drawable; import android.view.View; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; /** * 家計簿データの表示内容装飾用クラス */ public class AccountItemDecoration extends RecyclerView.ItemDecoration { private Drawable mDivider; //ディバイダー /** * コンストラクタ * @param context コンテキスト */ public AccountItemDecoration(Context context) { //ディバイダーの取得 mDivider = context.getResources().getDrawable(R.drawable.under_border);; } /** * 装飾内容の描画 * @param c キャンバス * @param parent リサイクラービュー * @param state 現在のリサイクラービューの状態 */ @Override public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) { super.onDraw(c, parent, state); //リサイクラービューのデータ数を取得 int childCount = parent.getChildCount(); int left = parent.getPaddingLeft()+10; //表示用ビューの左の余白を算出 int right = parent.getWidth() - parent.getPaddingRight()-10; //表示用ビューの右の余白を算出 for (int i = 0; i < childCount; i++) { View child = parent.getChildAt(i); //表示用ビューの取得 //レイアウトパラメータの取得 RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); int top = child.getBottom() + params.bottomMargin; //表示用ビューの上の余白を算出 int bottom = top + mDivider.getIntrinsicHeight(); //表示用ビューの下の余白を算出 mDivider.setBounds(left,top,right,bottom); //余白の算出値を設定 mDivider.draw(c); //描画 } } }
以上が、「リサイクラービュー」に関連するファイルですが、アプリの初回起動時には「MainActivityクラス」や「データベース操作クラス」からこれらのクラスを用いて「リサイクラービュー」にデータを表示しています。
メイン画面(MainActivityクラス)
「MainActivityクラス」の内容は下記のようになります。
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import java.util.ArrayList; import java.util.Calendar; import java.util.List; /** * メインアクティビティ */ public class MainActivity extends AccountUtilities { private RecyclerView mAccountRecyclerView; //リサイクラービュー private RecyclerView.Adapter mRecyclerAdapter; //アダプター private RecyclerView.LayoutManager mRecyclerLayoutManager; //レイアウトマネージャー private long mDisplayStartDate = 0; //表示開始時刻(タイムスタンプ) private long mDisplayLastDate = 0; //表示終了時刻(タイムスタンプ) @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //AccountUtilitiesクラスへインスタンスを設定 setContext(getApplicationContext()); //コンテキストを設定 setMainActivity(this); //メインアクティビティを設定 setDatabaseHelper(new DatabaseHelper()); //データベースヘルパーを設定 //リサイクラービュー関連 mAccountRecyclerView = (RecyclerView) findViewById(R.id.MaAccountRecyclerView); //リサイクラービューの取得 //アダプターがリサイクラービューのサイズに影響を与えない場合は true を設定 mAccountRecyclerView.setHasFixedSize(true); mAccountRecyclerView.addItemDecoration(new AccountItemDecoration(this)); //AccountItemDecoration(表示内容の装飾)を設定 mRecyclerLayoutManager = new LinearLayoutManager(this); //レイアウトマネージャーの生成 mAccountRecyclerView.setLayoutManager(mRecyclerLayoutManager); //リサイクラービューにレイアウトマネージャーを設定 setInitDisplayDate(); //初回表示時の表示期間を設定 displayPeriod(); //「表示開始日」と「表示終了日」を表示 getDB().getData(); // DBからデータを取得 //「データ登録ボタン」のイベントリスナー getBtn(R.id.MaRegistDataBtn).setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { //「データ登録ボタン」がクリックされたら「データ登録」用アクティビティを表示 startActivity(new Intent(getApplication(), RegistAccountDataActivity.class)); } } ); Calendar calendar = getCalendar(); //カレンダークラスのインスタンスを取得 calendar.setTimeInMillis(mDisplayStartDate); //カレンダーに「表示開始日(タイムスタンプ)」を設定 int year = calendar.get(Calendar.YEAR); //「年」の取得 int month = calendar.get(Calendar.MONTH); //「月」の取得 int day = calendar.get(Calendar.DAY_OF_MONTH); //「日」の取得 //「表示開始日」設定ボタンのイベントリスナー設定 setDateEventListener(this, R.id.MaSelectStartDateBtn, R.id.MaStartDateTv, R.id.MaErrorDateTv, year, month, day, CheckDateType.MAIN_START); //「表示終了日」設定ボタンのイベントリスナー設定 setDateEventListener(this, R.id.MaSelectEndDateBtn, R.id.MaEndDateTv, R.id.MaErrorDateTv, year, month, day, CheckDateType.MAIN_END); } /** * リサイクラービューを更新 * @param lad 家計簿データセット */ public void updateDataView(List<AccountData> lad){ ArrayList<AccountData> alad= (ArrayList<AccountData>)lad; //ArrayListへ変換 mRecyclerAdapter = new AccountRecyclerAdapter(alad); //リサイクラーアダプターにデータを設定 setAccountRecyclerAdapter((AccountRecyclerAdapter) mRecyclerAdapter); //AccountUtilitiesクラスへアダプターを設定 mAccountRecyclerView.setAdapter(mRecyclerAdapter); //リサイクラービューにアダプターを設定 mRecyclerAdapter.notifyDataSetChanged(); //リサイクラービューの表示を更新 } /** * 初回表示時の表示期間を設定 */ private void setInitDisplayDate(){ Calendar calendar = getCalendar(); //カレンダーを取得 mDisplayLastDate = calendar.getTimeInMillis(); //現在時刻のタイムスタンプを取得し、「表示終了日」に設定 calendar.set(Calendar.DAY_OF_MONTH,1); //「表示開始日」の「日」を設定(その月の「1日」を設定) calendar.set(Calendar.HOUR_OF_DAY,0); //「表示開始日」の「時」を設定(「0時」を設定) calendar.set(Calendar.MINUTE,0); //「表示開始日」の「分」を設定(「0分」を設定) mDisplayStartDate = calendar.getTimeInMillis(); //「表示開始日」を設定 } /** * 表示データの合計金額を算出 * @param lad 家計簿データセット */ public void displaySumPrice(List<AccountData> lad){ long sum = 0; for( AccountData ad : lad){ sum += ad.getPrice(); } getTv(R.id.MaSumPriceTv).setText(getString(R.string.priceSum) + String.valueOf(sum) + getString(R.string.priceUnit)); } /** * 「表示期間」の「開始日」と「終了日」を表示 */ private void displayPeriod(){ displayDate(R.id.MaStartDateTv, mDisplayStartDate); displayDate(R.id.MaEndDateTv, mDisplayLastDate); } /** * 指定IDのテキストビューへ時刻を表示 * @param id テキストビューID * @param timestamp タイムスタンプ */ private void displayDate(int id, long timestamp){ Calendar calendar = getCalendar(); //カレンダーを取得 calendar.setTimeInMillis(timestamp); //「タイムスタンプ」をカレンダーに設定 int year = calendar.get(Calendar.YEAR); //「年」を取得 int month = calendar.get(Calendar.MONTH); //「月」を取得 int day = calendar.get(Calendar.DAY_OF_MONTH); //「日」を取得 getTv(id).setText(year + getString(R.string.year) + (month + 1) + getString(R.string.month) + day + getString(R.string.day)); //テキストビューへ表示 } /** * 「日付設定」ボタンのイベントリスナー設定 * @param activity リスナーを設定するアクティビティ * @param setDateBtnId 「日付設定」ボタンID * @param setDateBtnId イベントリスナーを設定するボタンID * @param errorInputDateId 「日付」のエラー表示用テキストビューID * @param year 「年」 * @param month 「月」 * @param day 「日」 * @param dateType 「設定する日付」の種類 */ protected void setDateEventListener(Activity activity, int setDateBtnId, int inputDateId, int errorInputDateId, int year, int month, int day, AccountUtilities.CheckDateType dateType){ //匿名クラス(View.OnClickListener)に渡す値の定数 final Activity pActivity = activity; final int pInputDateId = inputDateId; final int pErrorInputDateId = errorInputDateId; final int pYear = year; final int pMonth = month; final int pDay = day; final AccountUtilities.CheckDateType pDateType = dateType; //「日付設定ボタン」のイベントリスナー getBtn(setDateBtnId).setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { //DatePickerFragmentクラスのインスタンスを生成 DatePickerFragment dpFragment = new DatePickerFragment(pActivity, pInputDateId, pErrorInputDateId, pYear, pMonth, pDay, pDateType); //DatePickerFragment(日付設定画面)を表示 dpFragment.show(getSupportFragmentManager(), " datePicker"); } } ); } /** * 「表示開始日」のタイムスタンプを取得(Getter) * @return 「表示開始日」のタイムスタンプ */ public long getDisplayStartDate() { return mDisplayStartDate; } /** * 「表示開始日」のタイムスタンプを設定(Setter) * @param displayStartDate */ public void setDisplayStartDate(long displayStartDate) { mDisplayStartDate=displayStartDate; } /** * 「表示終了日」のタイムスタンプを取得(Getter) * @return 「表示終了日」のタイムスタンプ */ public long getDisplayLastDate() { return mDisplayLastDate; } /** * 「表示終了日」のタイムスタンプを取得(Setter) * @param displayLastDate 「表示終了日」のタイムスタンプ */ public void setDisplayLastDate(long displayLastDate) { mDisplayLastDate=displayLastDate; } }
「MainActivityクラス」が継承している「AccountUtilitiesクラス」は、さまざまなクラスと「データ・メソッドなどを共用する」ためのクラスです。
「MainActivity」のコンストラクタでは、
- アプリケーションコンテキスト
- メインアクティビティ
- データベースヘルパー
のインスタンスを「AccountUtilitiesクラス」に設定しています。
「MainActivityクラス」は、「AccountUtilitiesクラス」を継承していますので、「AccountUtilitiesクラス」の「private」では無いフィールド・メソッドを利用することができます。
「AccountUtilitiesクラス」は下記のようになります。
import android.content.Context; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; import java.util.Calendar; import java.util.TimeZone; /** * ユーティリティクラス */ public class AccountUtilities extends AppCompatActivity { protected int mYear = 0; //年 protected int mMonth = 0; //月 protected int mDay = 0; //日 private static Context mContext = null; //コンテキスト private static MainActivity mMainActivity = null; //メインアクティビティ private static DatabaseHelper mDbh = null; //データベースヘルパー private static AccountRecyclerAdapter mAdapter = null; //アダプター private static String TIME_ZONE = "Asia/Tokyo"; //カレンダーのタイムゾーン /** * 日付選択の種別 */ protected enum CheckDateType{ MAIN_START, //スタート画面(メインアクティビティ)の「表示開始日」選択 MAIN_END, //スタート画面(メインアクティビティ)の「表示終了日」選択 INPUT_EDIT //データ追加・編集画面の「登録日付」選択 } /** * 「テキストビュー」の取得 * @param id テキストビューID * @return テキストビュー */ protected TextView getTv(int id){ return ((TextView)findViewById(id)); } /** * 「エディットテキスト」の取得 * @param id エディットテキストID * @return エディットテキスト */ protected EditText getEt(int id){ return ((EditText)findViewById(id)); } /** * 「ボタン」の取得 * @param id ボタンID * @return ボタン */ protected Button getBtn(int id) { return ((Button)findViewById(id)); } /** * 追加・編集画面の「日付設定ボタン」タップ時の処理 * @param year 年 * @param month 月 * @param day 日 * @param inputDateId 「日付選択」ボタンのID * @param errorInputDateId エラー表示用テキストビューのID * @param checkDateType 選択ボタンの種別 */ protected void setDate(int year, int month, int day, int inputDateId, int errorInputDateId, CheckDateType checkDateType ) { boolean result = false; //チェック結果 //現在の日付より未来の日付が設定されていないか? if (isSelectedDateInTheFuture(year, month, day, errorInputDateId)) { if (checkDateType == CheckDateType.INPUT_EDIT) { //データ追加・編集画面の「登録日付」選択 result = true; } else { getTv(errorInputDateId).setText(""); //エラー表示をクリア if (checkDateType == CheckDateType.MAIN_START) { //「表示開始日」ボタンを選択 Calendar calendar = getCalendar(); //カレンダーの取得 calendar.set(year, month, day,0,0,1); //選択した「年・月・日」をカレンダーに設定 long startTs = calendar.getTimeInMillis(); //「表示開始日」のタイムスタンプを取得 //表示開始日が表示終了日より未来の日付に設定されていないか? if( isStartDateGreaterThanEndDate(startTs) ){ getTv(errorInputDateId).setText(R.string.selectFutureDate); } else { getMainActivity().setDisplayStartDate(startTs); //「表示開始日」のタイムスタンプをメインアクティビティに設定 getDB().getData(); //DBからデータを取得&表示 result = true; } } else if(checkDateType == CheckDateType.MAIN_END) { //「表示終了日」ボタンを選択 Calendar calendar = getCalendar(); //カレンダーの取得 calendar.set(year, month, day, 12, 59, 59); //選択した「年・月・日」をカレンダーに設定 long endTs = calendar.getTimeInMillis(); //「表示終了日」のタイムスタンプを取得 //表示終了日が表示開始日より過去の日付に設定されていないか? if (isEndDateLessThanStartDate(endTs)) { getTv(errorInputDateId).setText(R.string.selectPastDate); } else { getMainActivity().setDisplayLastDate(endTs); //「表示終了日」のタイムスタンプをメインアクティビティに設定 getDB().getData(); //DBからデータを取得&表示 result = true; } } } } if( result ) { //「日付」が正しい場合 mYear = year; //「年」を設定 mMonth = month; //「月」を設定 mDay = day; //「日」を設定 //メインアクティビティの「表示開始日」または「表示終了日」を表示 getTv(inputDateId).setText(mYear + AccountUtilities.getStr(R.string.year) + (mMonth + 1) + AccountUtilities.getStr(R.string.month) + mDay + AccountUtilities.getStr(R.string.day)); } } /** * 設定した「日付」が設定時より未来の日付かどうかをチェック * @param year 年 * @param month 月 * @param day 日 * @param errorInputDateId エラー表示用テキストビューID * @return チェック結果 true:正、false:誤 */ protected boolean isSelectedDateInTheFuture(int year, int month, int day, int errorInputDateId){ getTv(errorInputDateId).setText(""); //エラー表示をクリア Calendar cl = getCalendar(); //カレンダーを取得 cl.set(year, month, day); //指定した日付を設定 Calendar nowCl = getCalendar(); //現在の日付を設定 //未来の日付に設定されていないか? if (nowCl.getTimeInMillis() < cl.getTimeInMillis()) { getTv(errorInputDateId).setText(R.string.selectFutureDate); return false; } return true; } /** * 表示開始日が表示終了日より未来の日付に設定されているかをチェック * @param startTs 表示開始日のタイムスタンプ * @return チェック結果 true:正、false:誤 */ private boolean isStartDateGreaterThanEndDate(long startTs){ //表示開始日が表示終了日より未来の日付に設定されていないか? if( startTs > getMainActivity().getDisplayLastDate()){ return true; } return false; } /** * 表示終了日が表示開始日より過去の日付に設定されているかをチェック * @param endTs 表示終了日のライムスタンプ * @return チェック結果 true:正、false:誤 */ private boolean isEndDateLessThanStartDate(long endTs){ //表示終了日が表示開始日より過去の日付に設定されていないか? if( endTs < getMainActivity().getDisplayStartDate()){ return true; } return false; } /** * コンテキストを設定(Setter) * @param context コンテキスト */ public static void setContext(Context context){ if ( mContext == null ) { mContext = context; } } /** * メインアクティビティのインスタンスを取得(Getter) * @return */ public static MainActivity getMainActivity(){ return mMainActivity; } /** * メインアクティビティのインスタンスを設定(Setter) * @param mainActivity メインアクティビティ */ public static void setMainActivity(MainActivity mainActivity){ if( mMainActivity == null) { mMainActivity = mainActivity; } } /** * データベースヘルパーを取得(Getter) * @return データベースヘルパーのインスタンス */ public static DatabaseHelper getDB(){ return mDbh; } /** * データベースヘルパーを設定(Setter) * @param dbh */ public static void setDatabaseHelper(DatabaseHelper dbh){ if ( mDbh == null ) { mDbh = dbh; } } /** * リサイクラーアダプターの取得(Getter) * @return リサイクラーアダプター */ public static AccountRecyclerAdapter getAdapter() { return mAdapter; } /** * リサイクルアダプターを設定(Setter) * @param adapter */ public static void setAccountRecyclerAdapter(AccountRecyclerAdapter adapter){ mAdapter = adapter; } /** * トーストにメッセージを表示 * @param message */ public static void displayMessage(String message){ Toast.makeText(mContext, message, Toast.LENGTH_LONG).show(); //トーストを表示 } /** * タイムゾーンを設定したカレンダークラスのインスタンスを取得 * @return */ public static Calendar getCalendar(){ Calendar calendar = Calendar.getInstance(); //カレンダーの取得 calendar.setTimeZone(TimeZone.getTimeZone(TIME_ZONE)); //タイムゾーンを設定 return calendar; } /** * 指定した「文字列ID」の文字列を取得 * @param id 文字列ID * @return 「文字列ID」の文字列 */ public static String getStr(int id){ return mContext.getResources().getString(id); } }
このクラスは、「データ登録・編集」用のクラス等でも利用していくため、さまざまなメソッドが定義されています。
データ登録画面
次に「データ登録画面」について見ていきたいと思います。
データ登録画面レイアウトファイル(regist_account_data.xml)
「データ登録画面」の「レイアウトファイル(regist_account_data.xml)」の内容は、下記のようになります。
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="visible"> <Button android:id="@+id/RaSetDateBtn" android:layout_width="200dp" android:layout_height="wrap_content" android:layout_marginTop="20dp" android:background="@drawable/button_style_2" android:text="@string/btnSetDate" android:textColor="#FFFFFF" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="@+id/RaVguideline1" app:layout_constraintTop_toBottomOf="@+id/RaDisplayDateTv" /> <Button android:id="@+id/RaInputScrRegistDataBtn" android:layout_width="200dp" android:layout_height="wrap_content" android:layout_marginTop="15dp" android:background="@drawable/button_style_1" android:text="@string/btnRegistData" android:textColor="#FFFFFF" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="@+id/RaVguideline1" app:layout_constraintTop_toBottomOf="@+id/RaSetDateBtn" /> <EditText android:id="@+id/RaInputContentEt" android:layout_width="0dp" android:layout_height="wrap_content" android:ems="10" android:inputType="textPersonName" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="@+id/RaVguideline1" app:layout_constraintTop_toBottomOf="@+id/RaErrorInputContentTv" /> <EditText android:id="@+id/RaInputPriceEt" android:layout_width="0dp" android:layout_height="wrap_content" android:ems="10" android:inputType="number" android:visibility="visible" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="@+id/RaVguideline1" app:layout_constraintTop_toBottomOf="@+id/RaErrorInputPriceTv" tools:visibility="visible" /> <ImageView android:id="@+id/MaImageView" android:layout_width="0dp" android:layout_height="0dp" android:scaleType="fitXY" app:layout_constraintBottom_toTopOf="@+id/RaHguideline4" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:srcCompat="@drawable/header_xxxhdpi_1280" /> <TextView android:id="@+id/RaInputPriceTv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/tvPrice" app:autoSizeMaxTextSize="100sp" app:autoSizeMinTextSize="12sp" app:autoSizeStepGranularity="2sp" app:autoSizeTextType="uniform" app:layout_constraintBaseline_toBaselineOf="@+id/RaInputPriceEt" app:layout_constraintEnd_toStartOf="@+id/RaInputPriceEt" /> <TextView android:id="@+id/RaInputContentTv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/tvContent" app:autoSizeMaxTextSize="100sp" app:autoSizeMinTextSize="12sp" app:autoSizeStepGranularity="2sp" app:autoSizeTextType="uniform" app:layout_constraintBaseline_toBaselineOf="@+id/RaInputContentEt" app:layout_constraintEnd_toStartOf="@+id/RaInputContentEt" /> <TextView android:id="@+id/RaInputDateTv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/tvDate" app:layout_constraintBaseline_toBaselineOf="@+id/RaDisplayDateTv" app:layout_constraintEnd_toStartOf="@+id/RaVguideline1" /> <TextView android:id="@+id/RaDisplayDateTv" android:layout_width="300dp" android:layout_height="wrap_content" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.476" app:layout_constraintStart_toStartOf="@+id/RaVguideline1" app:layout_constraintTop_toBottomOf="@+id/RaErrorInputDateTv" /> <TextView android:id="@+id/RaErrorInputContentTv" android:layout_width="0dp" android:layout_height="wrap_content" android:textColor="#FF0000" android:visibility="visible" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="@+id/RaVguideline1" app:layout_constraintTop_toTopOf="@+id/RaHguideline1" tools:visibility="visible" /> <TextView android:id="@+id/RaErrorInputPriceTv" android:layout_width="0dp" android:layout_height="wrap_content" android:textColor="#FF0000" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="@+id/RaVguideline1" app:layout_constraintTop_toTopOf="@+id/RaHguideline2" /> <TextView android:id="@+id/RaErrorInputDateTv" android:layout_width="0dp" android:layout_height="wrap_content" android:textColor="#FF0000" android:visibility="visible" app:layout_constraintBottom_toTopOf="@+id/RaDisplayDateTv" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="@+id/RaVguideline1" app:layout_constraintTop_toTopOf="@+id/RaHguideline3" app:layout_constraintVertical_bias="0.0" tools:visibility="visible" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/RaVguideline1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" app:layout_constraintGuide_begin="48dp" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/RaHguideline1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_begin="85dp" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/RaHguideline2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_begin="149dp" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/RaHguideline3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_begin="213dp" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/RaHguideline4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_begin="75dp" /> </androidx.constraintlayout.widget.ConstraintLayout>
主な「ウィジェットID」は下図のようになります。
データ登録画面アクティビティ(RegistAccountDataActivityクラス)
そして、「データ登録画面」の「アクティビティ(RegistAccountDataActivityクラス)」のプログラムは下記のようになります。
import android.os.Bundle; import android.view.View; import java.util.Calendar; /** * 「データ登録」画面のアクティビティ */ public class RegistAccountDataActivity extends AccountDataUtilities { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.regist_account_data); setCurrentTime(R.id.RaDisplayDateTv); //現在時刻をフィールドに保持 final Calendar calendar = getCalendar(); //カレンダーを取得 int year = calendar.get(Calendar.YEAR); //「年」を取得 int month = calendar.get(Calendar.MONTH); //「月」を取得 int day = calendar.get(Calendar.DAY_OF_MONTH); //「日」を取得 //「日付設定ボタン」のイベントリスナーを設定 setDateEventListener(this, R.id.RaSetDateBtn, R.id.RaDisplayDateTv, R.id.RaErrorInputDateTv, year, month, day); //「データ登録ボタン」のイベントリスナーを設定 getBtn(R.id.RaInputScrRegistDataBtn).setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { //エラー表示をクリア clearError(R.id.RaErrorInputContentTv, R.id.RaErrorInputPriceTv, R.id.RaErrorInputDateTv); //現在のタイムスタンプを取得 long date = getCurrentTimestamp(); //入力された「内容」「価格」のチェック boolean resultCheckContent = checkInputContent(R.id.RaInputContentEt,R.id.RaErrorInputContentTv); boolean resultCheckPrice = checkInputPrice(R.id.RaInputPriceEt,R.id.RaErrorInputPriceTv); if( resultCheckContent && resultCheckPrice ){ //「内容」の取得 String content = getEt(R.id.RaInputContentEt).getText().toString(); //「価格」の取得 int price = Integer.parseInt(getEt(R.id.RaInputPriceEt).getText().toString()); //追加データの生成 AccountData ad = new AccountData(content, price, date); //DBへデータを追加 mDbh.insertData(ad); //「データ登録画面」を非表示 finish(); } } } ); } }
このクラスは「データ登録画面」と「データ編集画面」の共用メソッドを持つ「AccountDataUtilitiesクラス」を継承しています。
クラス名が似ているため、混同しやすい部分ですが、「MainActivityクラス」が継承している「AccountUtilitiesクラス」とは「別のクラス」public class EditAccountDataActivity extendですので、下記の「クラス継承関係図(再掲)」をご参照ください。
AccountDataUtilitiesクラス
「AccountDataUtilitiesクラス」の内容は、下記のようになります。
import android.app.Activity; import android.view.View; import java.util.Calendar; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * データ追加・編集画面用ユーティリティクラス */ public class AccountDataUtilities extends AccountUtilities { protected DatabaseHelper mDbh = new DatabaseHelper(); //データベースヘルパー /** * コンストラクタ */ public AccountDataUtilities() { mDbh = AccountUtilities.getDB(); //データベースヘルパーを取得 } /** * 「内容」のチェック * @param inputContentId 「内容」のエディットテキストID * @param errorInputContentId 「内容」のエラー表示用テキストビューID * @return チェック結果 */ protected boolean checkInputContent(int inputContentId, int errorInputContentId){ //「内容」をチェック if( getEt(inputContentId).getText().toString().length() == 0 ){ getTv(errorInputContentId).setText(R.string.noticeInputName); return false; } return true; } /** * 「価格」のチェック * @param inputPriceId 「価格」のエディットテキストID * @param errorInputPriceId 「価格」のエラー表示用テキストビューID * @return チェック結果 */ protected boolean checkInputPrice(int inputPriceId, int errorInputPriceId){ String price = getEt(inputPriceId).getText().toString(); //「金額」を取得 Pattern pattern = Pattern.compile("^[0-9]+$"); //「金額」の正規表現パターンを生成 Matcher matcher = pattern.matcher(price); //マッチャーの生成 String errorMessage = getString(R.string.inputOnlyNumber); //エラーメッセージの設定 boolean result = matcher.matches(); //「金額」のチェック try{ //入力値を整数型(Integer)に変換 Integer.parseInt(price); }catch (NumberFormatException e){ result = false; errorMessage = getString(R.string.InputMaxPrice); //エラーメッセージの設定 } catch(Exception e){ result = false; } if ( !result ){ getTv(errorInputPriceId).setText(errorMessage); //エラーメッセージを表示 } return result; } /** * 「現在の日付」をテキストビューに表示 * @param tvDisplayDate 「日付」表示用テキストビューID */ protected void setCurrentTime(int tvDisplayDate){ Calendar cl = getCalendar(); //カレンダーを取得 mYear = cl.get(Calendar.YEAR); //「年」を取得 mMonth = cl.get(Calendar.MONTH); //「月」を取得 mDay = cl.get(Calendar.DATE); //「日」を取得 //「現在の日付」をテキストビューに表示 getTv(tvDisplayDate).setText(mYear + getString(R.string.year) + (mMonth + 1) + getString(R.string.month) + mDay + getString(R.string.day)); } /** * 現在のタイムスタンプを取得 * @return 現在のタイムスタンプ */ protected long getCurrentTimestamp(){ Calendar cl = getCalendar(); //カレンダーの取得 cl.set(mYear, mMonth, mDay); //カレンダーに「年・月・日」を設定 return cl.getTimeInMillis(); } /** * エラーメッセージをクリア * @param errorInputContentTvId 「内容」のエラー表示用テキストビューID * @param errorInputPriceTvId 「価格」のエラー表示用テキストビューID * @param errorInputDateTvId 「日付」のエラー表示用テキストビューID */ protected void clearError(int errorInputContentTvId, int errorInputPriceTvId, int errorInputDateTvId){ getTv(errorInputContentTvId).setText(""); //「内容」のエラー表示をクリア getTv(errorInputPriceTvId).setText(""); //「価格」のエラー表示をクリア getTv(errorInputDateTvId).setText(""); //「日付」のエラー表示をクリア } /** * 「日付設定ボタン」のイベントリスナー設定 * @param activity リスナーを設定するアクティビティ * @param setDateBtnId 「日付設定」ボタンID * @param errorInputDateId 「日付」のエラー表示用テキストビューID */ protected void setDateEventListener(Activity activity, int setDateBtnId, int inputDateId, int errorInputDateId, int year, int month, int day){ //「日付設定ボタン」のイベントリスナーに渡す値の定数 final Activity pActivity = activity; //リスナーを設定するアクティビティ final int pInputDateId = inputDateId; //「日付」表示用テキストビューID final int pErrorInputDateId = errorInputDateId; //「日付」のエラー表示用テキストビューID final int pYear = year; //「年」 final int pMonth = month; //「月」 final int pDay = day; //「日」 //「日付設定ボタン」のイベントリスナー getBtn(setDateBtnId).setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { //DatePickerFragmentを生成 DatePickerFragment dpFragment = new DatePickerFragment(pActivity, pInputDateId, pErrorInputDateId, pYear, pMonth, pDay, CheckDateType.INPUT_EDIT); //「日付設定」ダイアログの表示 dpFragment.show(getSupportFragmentManager(), "datePicker"); } } ); } }
DatePickerFragmentクラス
「日付設定ボタン」のイベントリスナーで利用している「日付選択ダイアログ」を表示する「DatePickerFragmentクラス」は下記のようになります。
import android.app.Activity; import android.app.DatePickerDialog; import android.app.Dialog; import android.os.Bundle; import androidx.fragment.app.DialogFragment; /** * 「日付選択画面」のフラグメント */ public class DatePickerFragment extends DialogFragment implements DatePickerDialog.OnDateSetListener{ private Activity mActivity; //アクティビティ private int mInputDateId = 0; //日付設定ボタンのID private int mErrorInputDateId = 0; //日付設定エラー表示用テキストビューのID private int mYear = 0; //「年」 private int mMonth = 0; //「月」 private int mDay = 0; //「日」 private AccountUtilities.CheckDateType mCheckType; //「日付設定ボタン」の種別 /** * コンストラクタ * @param activity 表示するアクティビティ * @param inputDateId 日付設定ボタンのID * @param errorInputDateId エラーを表示するテキストビューのID * @param year 年 * @param month 月 * @param day 日 * @param checkType 日付設定ボタンの種別 */ public DatePickerFragment(Activity activity, int inputDateId, int errorInputDateId, int year, int month, int day, AccountUtilities.CheckDateType checkType) { mActivity = activity; //アクティビティを設定 mInputDateId = inputDateId; //日付設定ボタンのIDを設定 mErrorInputDateId = errorInputDateId; //エラーを表示するテキストビューのIDを設定 mYear = year; //年を設定 mMonth = month; //月を設定 mDay = day; //日を設定 mCheckType = checkType; //日付設定ボタンの種別を設定 } /** * DatePickerFragmentのインスタンス生成時の処理 * @param savedInstanceState Bundleインスタンス * @return */ @Override public Dialog onCreateDialog(Bundle savedInstanceState) { // DatePickerDialogのインスタンスを生成 return new DatePickerDialog((AccountUtilities) mActivity, this, mYear, mMonth, mDay); } /** * 日付が選択された時の処理 * @param view //DatePickerのview * @param year //年 * @param monthOfYear //月 * @param dayOfMonth //日 */ @Override public void onDateSet(android.widget.DatePicker view, int year, int monthOfYear, int dayOfMonth) { //日付が選択された時の処理を設定 ((AccountUtilities) mActivity).setDate(year, monthOfYear, dayOfMonth, mInputDateId, mErrorInputDateId, mCheckType); } }
データ編集画面
次に「データ編集画面」について見ていきたいと思います。
データ編集画面レイアウトファイル(edit_account_data.xml)
「データ編集画面」の「レイアウトファイル(edit_account_data.xml)」の内容は、下記のようになります。
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:id="@+id/EaSetDateBtn" android:layout_width="200dp" android:layout_height="wrap_content" android:layout_marginTop="20dp" android:background="@drawable/button_style_2" android:text="@string/btnSetDate" android:textColor="#FFFFFF" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="@+id/EaVguideline1" app:layout_constraintTop_toBottomOf="@+id/EaDisplayDateTv" /> <Button android:id="@+id/EaInputScrRegistDataBtn" android:layout_width="200dp" android:layout_height="wrap_content" android:layout_marginTop="15dp" android:background="@drawable/button_style_1" android:text="@string/btnUpdateData" android:textColor="#FFFFFF" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="@+id/EaVguideline1" app:layout_constraintTop_toBottomOf="@+id/EaSetDateBtn" /> <EditText android:id="@+id/EaInputContentEt" android:layout_width="0dp" android:layout_height="wrap_content" android:ems="10" android:inputType="textPersonName" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="@+id/EaVguideline1" app:layout_constraintTop_toBottomOf="@+id/EaErrorInputContentTv" /> <EditText android:id="@+id/EaInputPriceEt" android:layout_width="0dp" android:layout_height="wrap_content" android:ems="10" android:inputType="number" android:visibility="visible" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="@+id/EaVguideline1" app:layout_constraintTop_toBottomOf="@+id/EaErrorInputPriceTv" tools:visibility="visible" /> <ImageView android:id="@+id/EaImageView" android:layout_width="0dp" android:layout_height="0dp" android:scaleType="fitXY" app:layout_constraintBottom_toTopOf="@+id/EaHguideline4" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:srcCompat="@drawable/header_xxxhdpi_1280" /> <TextView android:id="@+id/EaInputPriceTv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/tvPrice" app:autoSizeMaxTextSize="100sp" app:autoSizeMinTextSize="12sp" app:autoSizeStepGranularity="2sp" app:autoSizeTextType="uniform" app:layout_constraintBaseline_toBaselineOf="@+id/EaInputPriceEt" app:layout_constraintEnd_toStartOf="@+id/EaInputPriceEt" /> <TextView android:id="@+id/EaInputContentTv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/tvContent" app:autoSizeMaxTextSize="100sp" app:autoSizeMinTextSize="12sp" app:autoSizeStepGranularity="2sp" app:autoSizeTextType="uniform" app:layout_constraintBaseline_toBaselineOf="@+id/EaInputContentEt" app:layout_constraintEnd_toStartOf="@+id/EaInputContentEt" /> <TextView android:id="@+id/EaInputDateTv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/tvDate" app:layout_constraintBaseline_toBaselineOf="@+id/EaDisplayDateTv" app:layout_constraintEnd_toStartOf="@+id/EaVguideline1" /> <TextView android:id="@+id/EaDisplayDateTv" android:layout_width="300dp" android:layout_height="wrap_content" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.476" app:layout_constraintStart_toStartOf="@+id/EaVguideline1" app:layout_constraintTop_toBottomOf="@+id/EaErrorInputDateTv" /> <TextView android:id="@+id/EaErrorInputContentTv" android:layout_width="0dp" android:layout_height="wrap_content" android:textColor="#FF0000" android:visibility="visible" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="@+id/EaVguideline1" app:layout_constraintTop_toTopOf="@+id/EaHguideline1" tools:visibility="visible" /> <TextView android:id="@+id/EaErrorInputPriceTv" android:layout_width="0dp" android:layout_height="wrap_content" android:textColor="#FF0000" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="@+id/EaVguideline1" app:layout_constraintTop_toTopOf="@+id/EaHguideline2" /> <TextView android:id="@+id/EaErrorInputDateTv" android:layout_width="0dp" android:layout_height="wrap_content" android:textColor="#FF0000" android:visibility="visible" app:layout_constraintBottom_toTopOf="@+id/EaDisplayDateTv" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="@+id/EaVguideline1" app:layout_constraintTop_toTopOf="@+id/EaHguideline3" app:layout_constraintVertical_bias="0.0" tools:visibility="visible" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/EaVguideline1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" app:layout_constraintGuide_begin="48dp" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/EaHguideline1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_begin="85dp" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/EaHguideline2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_begin="149dp" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/EaHguideline3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_begin="213dp" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/EaHguideline4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_begin="75dp" /> </androidx.constraintlayout.widget.ConstraintLayout>
主要な「ウィジェットID」は下図のようになります。
データ編集画面」アクティビティ(EditAccountDataActivityクラス)
そして、「データ編集画面」の「アクティビティ(EditAccountDataActivityクラス)」のプログラムは下記のようになります。
import android.content.Intent; import android.os.Bundle; import android.view.View; import java.util.Calendar; /** * 「データ編集」画面のアクティビティ */ public class EditAccountDataActivity extends AccountDataUtilities { int mSelectId = 0; //「編集データ」のID int mSelectPosition = 0; //「編集データ」のアダプターが保持している「ArrayList」の「position(index)」 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.edit_account_data); //インテントの取得 Intent intent = getIntent(); if(intent != null){ //インテントからIDを取得 mSelectId = intent.getIntExtra("id", 0); //インテントからポジションを取得 mSelectPosition = intent.getIntExtra("position", 0); } // 「編集データ」を取得 AccountData ad = getAdapter().getAccountData(mSelectPosition); //「内容」を表示 getEt(R.id.EaInputContentEt).setText(ad.getContent()); //「価格」を表示 getEt(R.id.EaInputPriceEt).setText(String.valueOf(ad.getPrice())); Calendar calendar = getCalendar(); //カレンダーの取得 calendar.setTimeInMillis(ad.getDate()); //カレンダーに「編集データ」の日付を設定 int year = calendar.get(Calendar.YEAR); //「年」を取得 int month = calendar.get(Calendar.MONTH); //「月」を取得 int day = calendar.get(Calendar.DAY_OF_MONTH); //「日」を取得 //「表示日付」を設定 ((AccountDataUtilities)this).setDate(year, month, day, R.id.EaDisplayDateTv, R.id.EaErrorInputDateTv, CheckDateType.INPUT_EDIT); //「日付設定」ボタンのイベントリスナーを設定 setDateEventListener(this, R.id.EaSetDateBtn, R.id.EaDisplayDate, R.id.EaErrorInputDate, year, month, day ); //「日付設定ボタン」のイベントリスナー設定 //「データ更新ボタン」のイベントリスナーを設定 getBtn(R.id.EaInputScrRegistDataBtn).setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { //エラー表示をクリア clearError(R.id.EaErrorInputContentTv, R.id.EaErrorInputPriceTv, R.id.EaErrorInputDateTv); //更新するタイムスタンプを取得 long date = getCurrentTimestamp(); //入力された「内容」「価格」のチェック boolean resultCheckContent = checkInputContent(R.id.EaInputContentEt,R.id.EaErrorInputContentTv); boolean resultCheckPrice = checkInputPrice(R.id.EaInputPriceEt,R.id.EaErrorInputPriceTv); if(resultCheckContent && resultCheckPrice){ //「内容」を取得 String content = getEt(R.id.EaInputContentEt).getText().toString(); //「価格」を取得 int price = Integer.parseInt(getEt(R.id.EaInputPriceEt).getText().toString()); //DBのデータを更新 mDbh.updateData(mSelectPosition, content, price, date); //データの変更をリサイクラービューに通知 getAdapter().notifyItemChanged(mSelectPosition); //メッセージを表示 displayMessage(getString(R.string.successUpdateData)); //「データ編集画面」を非表示 finish(); } } } ); } }
その他のファイル
アプリを構成するその他のファイルには、
- スタイル
- ストリング
- マニフェスト
などのファイルがあります。
スタイル
アプリの「デザインテーマ」を設定しているファイルが、「styles.xml」です。
「styles.xml」の内容は下記のようになります。
<resources> <!-- Base application theme. --> <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <!-- Customize your theme here. --> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> <item name="windowNoTitle">true</item> <item name="windowActionBar">false</item> <item name="android:windowFullscreen">true</item> <item name="android:windowContentOverlay">@null</item> </style> </resources>
「データ一覧画面(メイン画面)」の「データ登録ボタン」には「button_style_1.xml」を適用し、「表示開始日設定ボタン」と「表示終了日設定ボタン」には、「button_style_2.xml」の「スタイル」を適用していて、その内容は下記のようになります。
「button_style_1.xml」の内容は、
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <gradient android:startColor="#ff0000A1" android:endColor="#ff0000FF" android:angle="45"/> <corners android:radius="12dp" /> </shape>
「button_style_2.xml」の内容は、
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <gradient android:startColor="#ff008B8B" android:endColor="#ff00B0B0" android:angle="45"/> <corners android:radius="12dp" /> </shape>
のようになります。
ストリング
アプリ内で利用している「文字列」を定義している「strings.xml」の内容は次のようになります。
<resources> <string name="app_name">account_book_app</string> <string name="tvContent">内容</string> <string name="tvPrice">金額</string> <string name="tvDate">日付</string> <string name="tvTo">~</string> <string name="tvDisplayStartDate">表示開始日指定</string> <string name="tvDisplayEndDate">表示終了日指定</string> <string name="btnRegistData">データ登録</string> <string name="btnUpdateData">データ更新</string> <string name="btnSetDate">日付設定</string> <string name="noticeInputName">※名前を入力してください。</string> <string name="inputOnlyNumber">※数値のみを入力してください。</string> <string name="InputMaxPrice">※金額は「2,147,483,647」円までの範囲で入力してください。</string> <string name="selectTask">処理選択</string> <string name="askingTaskType">どの処理を実行しますか?</string> <string name="edit">編集</string> <string name="year">年</string> <string name="month">月</string> <string name="day">日</string> <string name="delete">削除</string> <string name="priceUnit">円</string> <string name="priceSum">合計:</string> <string name="selectFutureDate">※表示開始日が表示終了日より未来の日付に設定されています。</string> <string name="selectPastDate">※表示終了日が表示開始日より過去の日付に設定されています。</string> <string name="canNotGetData">データが取得できません。</string> <string name="successRegistData">データを登録しました。</string> <string name="canNotAddData">データが登録できません。</string> <string name="successUpdateData">データを更新しました。</string> <string name="canNotUpdateData">データが更新できません。</string> <string name="successDeleteData">データを削除しました。</string> <string name="canNotDeleteData">データが削除できません。</string> </resources>
マニフェスト
アプリのさまざまな設定内容を定義している「AndroidManifest.xml」の内容は、下記のようになります。
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.account_book_app"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".RegistAccountDataActivity"> </activity> <activity android:name=".EditAccountDataActivity"> </activity> </application> </manifest>
次のページでは、
- アプリの実行方法
- 処理の流れの把握方法
- 「Kotlin言語」への変換方法
についてご説明していきたいと思います。