「Android家計簿アプリ(Java)」開発入門

今回は、「Android家計簿アプリ」の作り方をお話していきたいと思います。

アプリに実装する機能は、

→「プログラミング初心者のための「シンプル家計簿アプリ」開発入門」

と同様の機能を実装していきます。

「Java」である程度アプリを作れる方を想定してご説明していきますので、まだ「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)」は、下記のようになります。

<?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」を記した下図をご参照ください。

メイン画面ウィジェット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);        //「日付」のウィジェットを取得
    }
}
                    

「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クラス」の内容は下記のようになります。

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)」の内容は、下記のようになります。

<?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」は下図のようになります。

データ登録画面(ウィジェットID)

そして、「データ登録画面」の「アクティビティ(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クラス」の内容は、下記のようになります。

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クラス」は下記のようになります。

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)」の内容は、下記のようになります。

<?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」は下図のようになります。

データ編集画面(ウィジェットID)

そして、「データ編集画面」の「アクティビティ(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言語」への変換方法

についてご説明していきたいと思います。

→「Android家計簿アプリ(Java)」開発入門~アプリ実行編~

HOMEへ