前回はTimePickerDialogをFragmentで実装しました。しかし、GingerBreadまでの動作は正しいことを確認しましたが、Jelly Beanでは以下の点で動作が想定外でした。(見た目には影響ありませんが...)
・縦横切替でonTimeSetが呼び出される
・バックキーを押すとonTimeSetが呼び出される
・完了を押すとonTimeSetが2回呼び出される
また、DialogFragmentはボタンの連打等で2つ以上のダイアログが重なる場合があります。そのため、以下の要件を満たすTimePickerFragmentを作成しました。
・完了時に1回だけonTimeSetが呼び出される
・連打されても1つしかダイアログが出ない
上記を満たしたコードは以下のとおりです。
public abstract class TimePickerFragment extends ExclusiveDialogFragment implements TimePickerDialog.OnTimeSetListener { /** キャンセルフラグ */ private boolean mIsCancel; /** 時間設定コールバック通過フラグ */ private boolean mIsTimeChange; /** フラグメント破棄フラグ */ private boolean mIsDestory; /** 排他的にダイアログが消去されたフラグ */ private boolean mIsExclusiveDialogDismiss; /** 時間設定コールバックで一時的に保存する時間 */ private int mTmpHour; /** 時間設定コールバックで一時的に保存する分 */ private int mTmpMinute; /** * {@inherited} */ @Override public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); mIsCancel = false; mIsTimeChange = false; mIsDestory = false; mIsExclusiveDialogDismiss = false; mTmpHour = 0; mTmpMinute = 0; } /** * {@inheritDoc} */ @Override public Dialog onCreateDialog(final Bundle savedInstanceState) { // プリファレンス読み込み final Activity activity = getActivity(); int theme; // GingerBread以前 if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) { theme = android.R.style.Theme_Panel; } else { // HoneyComb以降 theme = android.R.style.Theme_Holo_Panel; } // 24時間設定にしている場合はそちらに合わせる return new TimePickerDialog(activity, theme, this, getHour(), getMinute(), DateFormat.is24HourFormat(activity)); } /** * {@inheritDoc} */ @Override public void onTimeSet(final TimePicker view, final int hourOfDay, final int minute) { // すでに時間を変更している場合は変更しない if (mIsTimeChange) { return; } // JBで完了時にコールバックが2回来る、キャンセル時にコールバックが来る、 // という2つの事象があったため一時的に値を保存するように修正 mTmpHour = hourOfDay; mTmpMinute = minute; mIsTimeChange = true; } /** * {@inherited} */ @Override public void onCancel(final DialogInterface dialog) { super.onCancel(dialog); mIsCancel = true; } /** * {@inherited} */ @Override public void onDismiss(final DialogInterface dialog) { super.onDismiss(dialog); // 時間の更新がない場合は変更しない if (mIsTimeChange) { return; } // キャンセル時は処理なし if (mIsCancel) { return; } // バックグラウンドで破棄された場合は処理なし if (mIsDestory) { return; } // 排他的にダイアログが消去された場合は処理なし if (mIsExclusiveDialogDismiss) { return; } onUpdateTime(mTmpHour, mTmpMinute); } /** * {@inherited} */ @Override public void onDestroyView() { super.onDestroyView(); mIsDestory = true; } /** * {@inherited} */ @Override protected void onDismissExclusiveDialog() { super.onDismissExclusiveDialog(); mIsExclusiveDialogDismiss = true; } /** * TimePickerで扱う時間を取得します。 * * @return 時間 */ protected abstract int getHour(); /** * TimePickerで扱う分を取得します。 * * @return 分 */ protected abstract int getMinute(); /** * 時間設定を通知します。 * * @param hour * 時間 * @param minute * 分 */ protected abstract void onUpdateTime(final int hour, final int minute); }長々と書いてはいますが、このコードではダイアログが完了ボタンを押して消えるときに、一度だけonUpdateTimeというメソッドを呼び出します。
/** キャンセルフラグ */ private boolean mIsCancel; /** 時間設定コールバック通過フラグ */ private boolean mIsTimeChange; /** フラグメント破棄フラグ */ private boolean mIsDestory; /** 排他的にダイアログが消去されたフラグ */ private boolean mIsExclusiveDialogDismiss; ..... public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); mIsCancel = false; mIsTimeChange = false; mIsDestory = false; mIsExclusiveDialogDismiss = false; mTmpHour = 0; mTmpMinute = 0; }フィールドで、完了ボタン以外で消える条件をフラグとして持っています。 ・バックキーなどでのキャンセル(mIsCancel) ・OSのメモリ管理により消された(mIsDestory) ・ダイアログが複数出ようとして消された(mIsExclusiveDialogDismiss) もう一つは、onTimeSetが2回実行されないようにする実行済フラグです。 (※通常、フラグメント内で永続化したい変数はフィールドに持たせませんが、ここではライフサイクル上で一度しか使わないため、フィールドとして定義しています。) あとは、それぞれのフラグを適切なタイミングで立てています。 バックキーなどでのキャンセル(mIsCancel)
public void onCancel(final DialogInterface dialog) { super.onCancel(dialog); mIsCancel = true; }OSのメモリ管理により消された
public void onDestroyView() { super.onDestroyView(); mIsDestory = true; }ダイアログが複数出ようとして消された
protected void onDismissExclusiveDialog() { super.onDismissExclusiveDialog(); mIsExclusiveDialogDismiss = true; }onTimeSetが実行された
public void onTimeSet(final TimePicker view, final int hourOfDay, final int minute) { ..... mIsTimeChange = true; }onTimeSet内では2回目が呼び出されないようにガードして、時間の更新をします。
public void onTimeSet(final TimePicker view, final int hourOfDay, final int minute) { if (mIsTimeChange) { return; } mTmpHour = hourOfDay; mTmpMinute = minute; mIsTimeChange = true; }最後にonDismissでフラグでガードしつつonUpdateTimeを呼び出します。
public void onDismiss(final DialogInterface dialog) { super.onDismiss(dialog); // 時間の更新がない場合は変更しない if (mIsTimeChange) { return; } // キャンセル時は処理なし if (mIsCancel) { return; } // バックグラウンドで破棄された場合は処理なし if (mIsDestory) { return; } // 排他的にダイアログが消去された場合は処理なし if (mIsExclusiveDialogDismiss) { return; } onUpdateTime(mTmpHour, mTmpMinute); }あとは、このTimePickerFragmentを継承するクラスを作成してください。
今回はExclusiveDialogFragmentを継承しましたが、複数ダイアログの表示を考慮しない場合はDialogFragmentを継承して構築してください。その場合Exclusive系のフラグやメソッドは削除します。
コードに不備がありましたので、修正しました。(2012/08/10)
・Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1の条件文
・onDismissメソッドでのmIsTimeChangeのチェック(2.3系のキャンセルボタン押下時にガード)
0 件のコメント:
コメントを投稿