Androidアプリで使用するカスタムDialogFragmentクラス(1行入力ダイアログ, メッセージ表示ダイアログ, 確認ダイアログ)の作成方法を解説します。
本来ならEditTextウィジェットを使うケースでも入力が1行のみの場合はカスタム入力ダイアログクラスを作成します。 初回登録時にはそれほど問題ないのですが、入力した値の変更管理が面倒になるという理由からです。※登録後に修正する一番可能性がある
入力項目 | 入力方法 | レイアウトファイル |
---|---|---|
体温 | EditDialogFragement | edit_body_temper.xml |
歩数 | EditDialogFragement | edit_walking_count.xml |
天候 | EditDialogFragement | edit_weather_cond.xml |
表示タイミング | ダイアログクラス | リスナー |
---|---|---|
必須入力チェック時 | MessageOkDialogFragment | 無し |
エラーレスポンス受信時 | MessageOkDialogFragment | 無し |
登録(更新)ボタン押下時 | ConfirmDialogFragment |
ApiDemosアプリのAleart Dialogsと FragmentAlertDialogクラス からの実装を流用します。
【利用するソース】ApiDemosアプリ(Android Googlesource リポジトリから取得)
src/com/example/android/apis/app/ AlertDialogSamples.java -> EditDialogFragement FragmentAlertDialog.java -> EditDialogFragement, MessageOkDialogFragment, ConfirmDialogFragment res/layout/ alert_dialog_text_entry.xml
自作のクラスは ApiDemosのコードと上記公式ドキュメントを参考にアプリの要件に合わせて作りました。
<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>
<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>
<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>
<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();
}
}
[ソース] app/src/main/java/com/example/android/healthcare/ui/main/AppTopFragment.java
// 数値系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");
};
// 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時の後続処理
// メールアドレス必須ダイアログ
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");
}