カスタムDialogFragmentクラスの作成


【最終更新日】2023-04-07

Androidアプリで使用するカスタムDialogFragmentクラス(1行入力ダイアログ, メッセージ表示ダイアログ, 確認ダイアログ)の作成方法を解説します。

1.カスタムダイアログの利用

本来ならEditTextウィジェットを使うケースでも入力が1行のみの場合はカスタム入力ダイアログクラスを作成します。 初回登録時にはそれほど問題ないのですが、入力した値の変更管理が面倒になるという理由からです。※登録後に修正する一番可能性がある

(1) 1行入力ダイアログ
入力項目入力方法レイアウトファイル
体温EditDialogFragementedit_body_temper.xml
歩数EditDialogFragementedit_walking_count.xml
天候EditDialogFragementedit_weather_cond.xml
【データ登録画面と1行入力ダイアログ】
(2) メッセージ・確認ダイアログ
表示タイミングダイアログクラスリスナー
必須入力チェック時MessageOkDialogFragment無し
エラーレスポンス受信時MessageOkDialogFragment無し
登録(更新)ボタン押下時ConfirmDialogFragment
2.ApiDemosアプリのコードを再利用

ApiDemosアプリのAleart Dialogsと FragmentAlertDialogクラス からの実装を流用します。

【ApiDemosアプリの Alert Dialogs サンプル】

【利用するソース】ApiDemosアプリ(Android Googlesource リポジトリから取得)

src/com/example/android/apis/app/
    AlertDialogSamples.java  -> EditDialogFragement
    FragmentAlertDialog.java -> EditDialogFragement, MessageOkDialogFragment, ConfirmDialogFragment
res/layout/
    alert_dialog_text_entry.xml
【公式ドキュメント】
ダイアログ
https://developer.android.com/guide/topics/ui/dialogs?hl=ja
  • 「カスタム レイアウトを作成する」
  • 「全画面で、または埋め込みフラグメントとしてダイアログを表示する」
3.カスタム入力ダイアログクラス

自作のクラスは ApiDemosのコードと上記公式ドキュメントを参考にアプリの要件に合わせて作りました。

【ソース】android-health-care-example/app/src/main/
java/com/example/android/healthcare/dialogs/CustomDialogs.java
【EditText用スタイル】res/values/styles.xml ※入力型として数値(右寄せ)属性を設定
<resources>
    <style name="ViewSizeWrapContent">
        <item name="android:layout_width">wrap_content</item>
        <item name="android:layout_height">wrap_content</item>
    </style>
    <style name="StyleEditMarginStart" parent="ViewSizeWrapContent">
        <item name="android:layout_marginStart">@dimen/text_margin_start</item>
        <!-- 入力ボックスの右端の余白 -->
        <item name="android:paddingEnd">@dimen/edit_padding_end</item>
    </style>
    <style name="StyleEditSingle" parent="StyleEditMarginStart">
        <item name="android:maxLines">1</item>
    </style>
    <style name="StyleEditNumbered" parent="StyleEditSingle">
        <item name="android:gravity">end</item>
        <item name="android:inputType">number</item>
    </style>
    <style name="StyleEditNumberDecimal" parent="StyleEditNumbered">
        <item name="android:inputType">number|numberDecimal</item>
    </style>
    <!-- 以下省略 -->
</resources>
【EditDialogFragement用レイアウト】res/layout/
  • [体温] edit_body_temper.xml ※実数値入力スタイルを設定
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingStart="@dimen/alertdialog_padding_start_end"
        android:paddingEnd="@dimen/alertdialog_padding_start_end"
        >
    
        <!-- 体温は正浮動小数点数 -->
        <EditText
            android:id="@+id/editBodyTemper"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:ems="@integer/ems_body_temper"
            android:maxLength="@integer/max_body_temper"
            style="@style/StyleEditNumberDecimal"
            android:singleLine="true"
            android:textAppearance="?android:textAppearanceMedium"
            />
    </LinearLayout>
  • [歩数] edit_walking_count.xml ※整数値入力スタイルを設定
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingStart="@dimen/alertdialog_padding_start_end"
        android:paddingEnd="@dimen/alertdialog_padding_start_end"
        >
    
        <!-- 歩数は正の整数 -->
        <EditText
            android:id="@+id/editWalkingCount"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:ems="@integer/ems_walking_count"
            android:maxLength="@integer/max_walking_count"
            style="@style/StyleEditNumbered"
            android:singleLine="true"
            android:textAppearance="?android:textAppearanceMedium"
            />
    </LinearLayout>
  • [天候] edit_weather_cond.xml ※EditTextデフォルト
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingStart="@dimen/alertdialog_padding_start_end"
        android:paddingEnd="@dimen/alertdialog_padding_start_end"
        >
    
        <EditText
            android:id="@+id/editWeatherCond"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:maxLength="@integer/max_walking_count"
            android:singleLine="true"
            android:textAppearance="?android:textAppearanceMedium"
            android:autofillHints="" />
    </LinearLayout>

(1) EditDialogFragementクラス ※説明用にソースにないコメントを追加してます

public class CustomDialogs {
    //...一部省略...

    public static class EditDialogFragement extends DialogFragment {
        // 入力型
        public enum EditInputType {
            BODY_TEMPER/*体温(numberDecimal)*/, WALKING_COUNT/*歩数(nuberSigned)*/, NONE
        }
        // OK時に編集値を受け取るリスナー
        public interface EditOkCancelListener {
            void onOk(String editValue);
            void onCancel();
        }

        private final EditInputType mEditInputType;
        private final EditOkCancelListener mListener;
        public EditDialogFragement(EditInputType type,
                                   EditOkCancelListener listener) {
            mEditInputType = type; // 呼び出し元から入力型を受け取る
            mListener = listener;  // 呼び出し元からリスナーインスタンスを受け取る
        }

        public static EditDialogFragement newInstance(String title, String editValue,
                                 EditInputType type, EditOkCancelListener listener) {
            EditDialogFragement frag = new EditDialogFragement(type, listener);
            Bundle args = new Bundle();
            // タイトルは文字列
            args.putString("title", title);
            // メッセージは文字列指定
            args.putString("editValue", editValue);
            frag.setArguments(args);
            return frag;
        }

        @NonNull
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            assert getArguments() != null;
            String title = getArguments().getString("title");
            String editValue = getArguments().getString("editValue");
            LayoutInflater factory = getLayoutInflater();
            View entryView;
            EditText mEditText;
            // 入力型に応じて生成するレイアウトを切り替える
            if (mEditInputType == EditInputType.BODY_TEMPER) {
                // 体温入力用レイアウト
                entryView = factory.inflate(R.layout.edit_body_temper, null);
                mEditText = entryView.findViewById(R.id.editBodyTemper);
            } else if (mEditInputType == EditInputType.WALKING_COUNT){
                // 歩数入力用レイアウト
                entryView = factory.inflate(R.layout.edit_walking_count, null);
                mEditText = entryView.findViewById(R.id.editWalkingCount);
            } else {
                // 天候入力用レイアウト
                entryView = factory.inflate(R.layout.edit_weather_cond, null);
                mEditText = entryView.findViewById(R.id.editWeatherCond);
            }
            if (!TextUtils.isEmpty(editValue)) {
                // 入力値があれば全テキストを選択状態にする
                mEditText.setSelectAllOnFocus(true);
                mEditText.setText(editValue);
            } else {
                mEditText.setText("");
            }
            // OKボタン押下時にリスナーに入力値を設定する
            return new AlertDialog.Builder(getActivity())
                    .setTitle(title)
                    .setView(entryView)
                    .setPositiveButton(R.string.alert_dialog_ok,
                            (dialog, whichButton) -> mListener.onOk(mEditText.getText().toString()))
                    .setNegativeButton(R.string.alert_dialog_cancel,
                            (dialog, whichButton) -> mListener.onCancel())
                    .create();
        }
    }
}

(2) MessageOkDialogFragmentクラス ※ほぼApiDemosからの流用なので説明用のコメントはありません

public static class MessageOkDialogFragment extends DialogFragment {
    public static MessageOkDialogFragment newInstance(String title, String message) {
        MessageOkDialogFragment frag = new MessageOkDialogFragment();
        Bundle args = new Bundle();
        // タイトルは任意
        if (!TextUtils.isEmpty(title)) {
            args.putString("title", title);
        }
        // メッセージは文字列指定
        args.putString("message", message);
        frag.setArguments(args);
        return frag;
    }

    @NonNull
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        assert getArguments() != null;
        String title = getArguments().getString("title");
        String message = getArguments().getString("message");
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        if (!TextUtils.isEmpty(title)) {
            builder.setTitle(title);
        }
        return builder
                .setMessage(message)
                .setPositiveButton(R.string.alert_dialog_ok,
                        (dialog, whichButton) -> {
                        })
                .create();
    }
}

(3) ConfirmDialogFragmentクラス ※説明用にソースにないコメントを追加してます

public static class ConfirmDialogFragment extends DialogFragment {
    // 呼び出し元にボタン押下の結果を通知するリスナーインターフェイス
    public interface ConfirmOkCancelListener {
        // OKボタン押下されたことがわかれば良いので呼び出し元に渡す値は無し
        void onOk();
        void onCancel();
    }

    private final ConfirmOkCancelListener mListener;
    public ConfirmDialogFragment(ConfirmOkCancelListener listener) {
        mListener = listener;
    }

    public static ConfirmDialogFragment newInstance(String title, String message,
                                                        ConfirmOkCancelListener listener) {
        ConfirmDialogFragment frag = new ConfirmDialogFragment(listener);
        Bundle args = new Bundle();
        // タイトルは任意
        if (!TextUtils.isEmpty(title)) {
            args.putString("title", title);
        }
        // メッセージは文字列指定
        args.putString("message", message);
        frag.setArguments(args);
        return frag;
    }

    @NonNull
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        assert getArguments() != null;
        String  title = getArguments().getString("title");
        String message = getArguments().getString("message");
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        if (!TextUtils.isEmpty(title)) {
            builder.setTitle(title);
        }
        return builder
                .setMessage(message)
                .setPositiveButton(R.string.alert_dialog_ok,
                        (dialog, whichButton) -> mListener.onOk())
                .setNegativeButton(R.string.alert_dialog_cancel,
                        (dialog, whichButton) -> mListener.onCancel())
                .create();
    }
}
4.アプリからカスタムダイアログを使用する

[ソース] app/src/main/java/com/example/android/healthcare/ui/main/AppTopFragment.java

(1) 体温入力, 歩数入力ダイアログ ※ダイアログの生成と値の取得部分のみ抜粋
// 数値系EditText入力ダイアログ起動イベントリスナー[体温入力(実数), 歩数入力(整数)]
private final View.OnClickListener mNumberInputClickListener = v -> {
    EditDialogFragement.EditOkCancelListener listener =
            new EditDialogFragement.EditOkCancelListener() {
                @Override
                public void onOk(String editValue) {
                    // ダイアログの入力値を受け取る
                    if (v.getId() == mInpBodyTemper.getId()) {
                        // 体温入力
                        if (!TextUtils.isEmpty(editValue)) {
                            // 実数値は小数点第1位で表示
                            String showValue = String.format(Locale.JAPANESE,
                                    AppTopUtil.FMT_BODY_TEMPER, Double.valueOf(editValue));
                            mInpBodyTemper.setText(showValue);
                        } else {
                            mInpBodyTemper.setText("");
                        }
                        // 入力ビューと入力値をonChangeリスナーに設定
                        mOnNumberChangedOnEditDialog.onChanged((TextView) v, editValue);
                    } else {
                        // 歩数入力なら加工無しで設定
                        mInpWalkingCount.setText(editValue);
                        mOnNumberChangedOnEditDialog.onChanged((TextView) v, editValue);
                    }
                }
                @Override
                public void onCancel() {
                }
            };
    // EditTextの引数
    EditDialogFragement.EditInputType editInputType;
    String title;
    String value;
    if (v.getId() == mInpBodyTemper.getId()) {
        // 体温入力
        editInputType = EditDialogFragement.EditInputType.BODY_TEMPER;
        // タイトル
        title = String.format(getString(R.string.format_input_title),
                getString(R.string.lbl_body_temper));
        // 前回入力値
        value = mInpBodyTemper.getText().toString();
    } else {
        // 歩数入力
        editInputType = EditDialogFragement.EditInputType.WALKING_COUNT;
        title = String.format(getString(R.string.format_input_title),
                getString(R.string.lbl_walking_count));
        value = mInpWalkingCount.getText().toString();
    }
    // ダイアログフラグメント生成
    DialogFragment fragment = EditDialogFragement.newInstance(title, value,
            editInputType, listener);
    // ダイアログフラグメント表示
    fragment.show(requireActivity().getSupportFragmentManager(), "EditDialogFragment");
};
(2) 入力チェックダイアログ表示
// OKボタンのみのダイアログ生成メソッド
    private void showMessageDialog(String title, String message, String tagName) {
        DialogFragment fragment = MessageOkDialogFragment.newInstance(title, message);
        fragment.show(requireActivity().getSupportFragmentManager(), tagName);
    }

    // 必須入力項目チェック処理のみ抜粋
    List warnings = checkRequiredInputs(false);
    if (!warnings.isEmpty()) {
        // メッセージダイアログ表示
        String warning = String.join("\n", warnings);
        showMessageDialog(getString(R.string.warning_required_dialog_title), warning,
                "RequiredDialogFragment");
        return;
   }

   // チェックOK時の後続処理
(3) メールアドレス必須ダイアログ ※ダイアログの生成とOKボタン押下時の処理、説明用コメントを追加しています
// メールアドレス必須ダイアログ
private void showConfirmDialogWithEmailAddress() {
    // ダイアログからボタン押下結果を受け取るリスナーのインスタンス生成
    ConfirmOkCancelListener listener = new ConfirmOkCancelListener() {
        @Override
        public void onOk() {
            // OKボタン押下ならメールアドレス設定画面に遷移する
            Intent settingsIntent = new Intent(requireActivity(), SettingsActivity.class);
            startActivity(settingsIntent);
        }

        @Override
        public void onCancel() {
            // No operation.
        }
    };
    ConfirmDialogFragment fragment = ConfirmDialogFragment.newInstance(
            getString(R.string.warning_required_dialog_title)/*タイトル*/,
            getString(R.string.warning_need_email_address)/*メッセージ*/,
            listener/*結果を受け取るリスナー*/);
    fragment.show(requireActivity().getSupportFragmentManager(), "ConfirmDialogFragment");
}
メニューページへ
戻る
Androidアプリのソースコードはこちら
https://github.com/pipito-yukio/personal_healthcare/tree/main/src/android-health-care-example