Android UI編——DialogFragmentシリーズ一のソースコード分析
14186 ワード
『DialogFragmentシリーズ一のソースコード分析』『DialogFragmentシリーズ二のDialogパッケージ』『DialogFragmentシリーズ三のAlertDialog実現』『DialogFragmentシリーズ四のStatusDialog(Progress、Success、Error)実現』『DialogFragmentシリーズ五のItemDialog(eg:BottomDialog)実現』『DialogFragmentシリーズ六のよくある問題』
Androidの日常開発では、アプリごとに弾枠を使ってインタラクティブになる需要があり、最もよく使われるのはDialog、PopUpWindow、Toast、SnackBarで、インタラクティブな操作が最もよく使われるのはDialogで、Android APIはすでに開発者にAlertDialogを提供しており、多くの読者に比べて使用されているが、AlertDialogにはActivity/fragmentライフサイクルの管理を受けず、Crashが発生しやすいという大きな欠点がある.また、画面切り替え後も自動的にステータスを保持できないなど、Androidは別のDialogFragmentを提供しており、GoogleやアリAndroid開発マニュアルではDialogFragmentの使用を推奨しており、AlertDialogやその他のカスタムAlertDialogの使用は推奨されていません.
次はDialogFragmentの長所と使用から展開しますが、やはり直接ソース分析から直感的で説得力があると思います.
ソースコードは次のとおりです.
DialogFragmentって何?
我々はDialogFragmentが直接Fragmentから継承されているのを見たが、DialogInterfaceを実現した.OnCancelListener, DialogInterface.OnDismissListener、この2つのインタフェースはよく知っていますか?さらに中のonCreateDialogという方法を見ると、DialogFragmentはDialogとFragmentの合体であることがわかります.
どうしてDialogFragmentはDialogが状態を保存できない問題を解決できるのですか?
ステータスデータの保存:
状態保存データ取り出し:
ステータスデータを取り出してメンバー変数に直接コピーすることで、グローバルに使用すれば関連属性を設定できるようになり、ステータス保存の目的を達成することができます.実はFragmentステータス保存メカニズム(savedInstancesState)を使用しています.
なぜDialogFragmentのライフサイクルが制御できるのでしょうか?
ソースコードから分かるように、Dialogの表示、非表示、破棄はそれぞれFragmentのonStart()、onStop()、onDestroyView()で行われているが、DialogFragmentのshow方法はFragmentTransactionパラメータに入力する必要がある.つまりDialogFragmentはFragmentManagerの管理であるライフサイクルが普通のFragmentに完全に管理されているため、DialogはFragmentに「制御」されている.もう二度と放浪することはありません!
DialogFragmentはどのように使いますか?
ソースコードからDialogを提供する方法が見つかったので、この方法を書き換えることでDialogFragmentをカスタマイズすることができます.例えば、AlertDialogを直接書くことができます.実はこれはDialogFragmentのDialog属性を体現しています.では、Fragmentである以上、では、onCreateDialogを書き直さないとデフォルトになるため、Fragmentの1つのレイアウトでカスタマイズできます.つまり、onCreateView()を書き換えることで実現できます.これがDialogFragmentをカスタマイズする主な2つの方法です.
では、DialogFragmentの分析と使用はここまでにしましょう.普通のDialogを使うなら、早くDialogFragmentに変えましょう.安心して手間を省きましょう.
Androidの日常開発では、アプリごとに弾枠を使ってインタラクティブになる需要があり、最もよく使われるのはDialog、PopUpWindow、Toast、SnackBarで、インタラクティブな操作が最もよく使われるのはDialogで、Android APIはすでに開発者にAlertDialogを提供しており、多くの読者に比べて使用されているが、AlertDialogにはActivity/fragmentライフサイクルの管理を受けず、Crashが発生しやすいという大きな欠点がある.また、画面切り替え後も自動的にステータスを保持できないなど、Androidは別のDialogFragmentを提供しており、GoogleやアリAndroid開発マニュアルではDialogFragmentの使用を推奨しており、AlertDialogやその他のカスタムAlertDialogの使用は推奨されていません.
次はDialogFragmentの長所と使用から展開しますが、やはり直接ソース分析から直感的で説得力があると思います.
ソースコードは次のとおりです.
public class DialogFragment extends Fragment
implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener {
/** @hide */
@RestrictTo(LIBRARY_GROUP)
@IntDef({STYLE_NORMAL, STYLE_NO_TITLE, STYLE_NO_FRAME, STYLE_NO_INPUT})
@Retention(RetentionPolicy.SOURCE)
private @interface DialogStyle {}
public static final int STYLE_NORMAL = 0;
public static final int STYLE_NO_TITLE = 1;
public static final int STYLE_NO_FRAME = 2;
public static final int STYLE_NO_INPUT = 3;
private static final String SAVED_DIALOG_STATE_TAG = "android:savedDialogState";
private static final String SAVED_STYLE = "android:style";
private static final String SAVED_THEME = "android:theme";
private static final String SAVED_CANCELABLE = "android:cancelable";
private static final String SAVED_SHOWS_DIALOG = "android:showsDialog";
private static final String SAVED_BACK_STACK_ID = "android:backStackId";
int mStyle = STYLE_NORMAL;
int mTheme = 0;
boolean mCancelable = true;
boolean mShowsDialog = true;
int mBackStackId = -1;
Dialog mDialog;
boolean mViewDestroyed;
boolean mDismissed;
boolean mShownByMe;
public DialogFragment() {
}
public void setStyle(@DialogStyle int style, @StyleRes int theme) {
mStyle = style;
if (mStyle == STYLE_NO_FRAME || mStyle == STYLE_NO_INPUT) {
mTheme = android.R.style.Theme_Panel;
}
if (theme != 0) {
mTheme = theme;
}
}
public void show(FragmentManager manager, String tag) {
mDismissed = false;
mShownByMe = true;
FragmentTransaction ft = manager.beginTransaction();
ft.add(this, tag);
ft.commit();
}
public int show(FragmentTransaction transaction, String tag) {
mDismissed = false;
mShownByMe = true;
transaction.add(this, tag);
mViewDestroyed = false;
mBackStackId = transaction.commit();
return mBackStackId;
}
public void showNow(FragmentManager manager, String tag) {
mDismissed = false;
mShownByMe = true;
FragmentTransaction ft = manager.beginTransaction();
ft.add(this, tag);
ft.commitNow();
}
public void dismiss() {
dismissInternal(false);
}
public void dismissAllowingStateLoss() {
dismissInternal(true);
}
void dismissInternal(boolean allowStateLoss) {
if (mDismissed) {
return;
}
mDismissed = true;
mShownByMe = false;
if (mDialog != null) {
mDialog.dismiss();
}
mViewDestroyed = true;
if (mBackStackId >= 0) {
getFragmentManager().popBackStack(mBackStackId,
FragmentManager.POP_BACK_STACK_INCLUSIVE);
mBackStackId = -1;
} else {
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.remove(this);
if (allowStateLoss) {
ft.commitAllowingStateLoss();
} else {
ft.commit();
}
}
}
public Dialog getDialog() {
return mDialog;
}
@StyleRes
public int getTheme() {
return mTheme;
}
public void setCancelable(boolean cancelable) {
mCancelable = cancelable;
if (mDialog != null) mDialog.setCancelable(cancelable);
}
/**
* Return the current value of {@link #setCancelable(boolean)}.
*/
public boolean isCancelable() {
return mCancelable;
}
public void setShowsDialog(boolean showsDialog) {
mShowsDialog = showsDialog;
}
/**
* Return the current value of {@link #setShowsDialog(boolean)}.
*/
public boolean getShowsDialog() {
return mShowsDialog;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (!mShownByMe) {
// If not explicitly shown through our API, take this as an
// indication that the dialog is no longer dismissed.
mDismissed = false;
}
}
@Override
public void onDetach() {
super.onDetach();
if (!mShownByMe && !mDismissed) {
// The fragment was not shown by a direct call here, it is not
// dismissed, and now it is being detached... well, okay, thou
// art now dismissed. Have fun.
mDismissed = true;
}
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mShowsDialog = mContainerId == 0;
if (savedInstanceState != null) {
mStyle = savedInstanceState.getInt(SAVED_STYLE, STYLE_NORMAL);
mTheme = savedInstanceState.getInt(SAVED_THEME, 0);
mCancelable = savedInstanceState.getBoolean(SAVED_CANCELABLE, true);
mShowsDialog = savedInstanceState.getBoolean(SAVED_SHOWS_DIALOG, mShowsDialog);
mBackStackId = savedInstanceState.getInt(SAVED_BACK_STACK_ID, -1);
}
}
@Override
@NonNull
public LayoutInflater onGetLayoutInflater(@Nullable Bundle savedInstanceState) {
if (!mShowsDialog) {
return super.onGetLayoutInflater(savedInstanceState);
}
mDialog = onCreateDialog(savedInstanceState);
if (mDialog != null) {
setupDialog(mDialog, mStyle);
return (LayoutInflater) mDialog.getContext().getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
}
return (LayoutInflater) mHost.getContext().getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
}
/** @hide */
@RestrictTo(LIBRARY_GROUP)
public void setupDialog(Dialog dialog, int style) {
switch (style) {
case STYLE_NO_INPUT:
dialog.getWindow().addFlags(
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
// fall through...
case STYLE_NO_FRAME:
case STYLE_NO_TITLE:
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
}
}
@NonNull
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
return new Dialog(getActivity(), getTheme());
}
@Override
public void onCancel(DialogInterface dialog) {
}
@Override
public void onDismiss(DialogInterface dialog) {
if (!mViewDestroyed) {
// Note: we need to use allowStateLoss, because the dialog
// dispatches this asynchronously so we can receive the call
// after the activity is paused. Worst case, when the user comes
// back to the activity they see the dialog again.
dismissInternal(true);
}
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (!mShowsDialog) {
return;
}
View view = getView();
if (view != null) {
if (view.getParent() != null) {
throw new IllegalStateException(
"DialogFragment can not be attached to a container view");
}
mDialog.setContentView(view);
}
final Activity activity = getActivity();
if (activity != null) {
mDialog.setOwnerActivity(activity);
}
mDialog.setCancelable(mCancelable);
mDialog.setOnCancelListener(this);
mDialog.setOnDismissListener(this);
if (savedInstanceState != null) {
Bundle dialogState = savedInstanceState.getBundle(SAVED_DIALOG_STATE_TAG);
if (dialogState != null) {
mDialog.onRestoreInstanceState(dialogState);
}
}
}
@Override
public void onStart() {
super.onStart();
if (mDialog != null) {
mViewDestroyed = false;
mDialog.show();
}
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
if (mDialog != null) {
Bundle dialogState = mDialog.onSaveInstanceState();
if (dialogState != null) {
outState.putBundle(SAVED_DIALOG_STATE_TAG, dialogState);
}
}
if (mStyle != STYLE_NORMAL) {
outState.putInt(SAVED_STYLE, mStyle);
}
if (mTheme != 0) {
outState.putInt(SAVED_THEME, mTheme);
}
if (!mCancelable) {
outState.putBoolean(SAVED_CANCELABLE, mCancelable);
}
if (!mShowsDialog) {
outState.putBoolean(SAVED_SHOWS_DIALOG, mShowsDialog);
}
if (mBackStackId != -1) {
outState.putInt(SAVED_BACK_STACK_ID, mBackStackId);
}
}
@Override
public void onStop() {
super.onStop();
if (mDialog != null) {
mDialog.hide();
}
}
/**
* Remove dialog.
*/
@Override
public void onDestroyView() {
super.onDestroyView();
if (mDialog != null) {
mViewDestroyed = true;
mDialog.dismiss();
mDialog = null;
}
}
}
DialogFragmentって何?
我々はDialogFragmentが直接Fragmentから継承されているのを見たが、DialogInterfaceを実現した.OnCancelListener, DialogInterface.OnDismissListener、この2つのインタフェースはよく知っていますか?さらに中のonCreateDialogという方法を見ると、DialogFragmentはDialogとFragmentの合体であることがわかります.
どうしてDialogFragmentはDialogが状態を保存できない問題を解決できるのですか?
ステータスデータの保存:
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
if (mDialog != null) {
Bundle dialogState = mDialog.onSaveInstanceState();
if (dialogState != null) {
outState.putBundle(SAVED_DIALOG_STATE_TAG, dialogState);
}
}
if (mStyle != STYLE_NORMAL) {
outState.putInt(SAVED_STYLE, mStyle);
}
if (mTheme != 0) {
outState.putInt(SAVED_THEME, mTheme);
}
if (!mCancelable) {
outState.putBoolean(SAVED_CANCELABLE, mCancelable);
}
if (!mShowsDialog) {
outState.putBoolean(SAVED_SHOWS_DIALOG, mShowsDialog);
}
if (mBackStackId != -1) {
outState.putInt(SAVED_BACK_STACK_ID, mBackStackId);
}
}
状態保存データ取り出し:
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mShowsDialog = mContainerId == 0;
if (savedInstanceState != null) {
mStyle = savedInstanceState.getInt(SAVED_STYLE, STYLE_NORMAL);
mTheme = savedInstanceState.getInt(SAVED_THEME, 0);
mCancelable = savedInstanceState.getBoolean(SAVED_CANCELABLE, true);
mShowsDialog = savedInstanceState.getBoolean(SAVED_SHOWS_DIALOG, mShowsDialog);
mBackStackId = savedInstanceState.getInt(SAVED_BACK_STACK_ID, -1);
}
}
ステータスデータを取り出してメンバー変数に直接コピーすることで、グローバルに使用すれば関連属性を設定できるようになり、ステータス保存の目的を達成することができます.実はFragmentステータス保存メカニズム(savedInstancesState)を使用しています.
なぜDialogFragmentのライフサイクルが制御できるのでしょうか?
@Override
public void onStart() {
super.onStart();
if (mDialog != null) {
mViewDestroyed = false;
mDialog.show();
}
}
@Override
public void onStop() {
super.onStop();
if (mDialog != null) {
mDialog.hide();
}
}
/**
* Remove dialog.
*/
@Override
public void onDestroyView() {
super.onDestroyView();
if (mDialog != null) {
// Set removed here because this dismissal is just to hide
// the dialog -- we don't want this to cause the fragment to
// actually be removed.
mViewDestroyed = true;
mDialog.dismiss();
mDialog = null;
}
}
public int show(FragmentTransaction transaction, String tag) {
mDismissed = false;
mShownByMe = true;
transaction.add(this, tag);
mViewDestroyed = false;
mBackStackId = transaction.commit();
return mBackStackId;
}
ソースコードから分かるように、Dialogの表示、非表示、破棄はそれぞれFragmentのonStart()、onStop()、onDestroyView()で行われているが、DialogFragmentのshow方法はFragmentTransactionパラメータに入力する必要がある.つまりDialogFragmentはFragmentManagerの管理であるライフサイクルが普通のFragmentに完全に管理されているため、DialogはFragmentに「制御」されている.もう二度と放浪することはありません!
DialogFragmentはどのように使いますか?
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
return new Dialog(getActivity(), getTheme());
}
ソースコードからDialogを提供する方法が見つかったので、この方法を書き換えることでDialogFragmentをカスタマイズすることができます.例えば、AlertDialogを直接書くことができます.実はこれはDialogFragmentのDialog属性を体現しています.では、Fragmentである以上、では、onCreateDialogを書き直さないとデフォルトになるため、Fragmentの1つのレイアウトでカスタマイズできます.つまり、onCreateView()を書き換えることで実現できます.これがDialogFragmentをカスタマイズする主な2つの方法です.
では、DialogFragmentの分析と使用はここまでにしましょう.普通のDialogを使うなら、早くDialogFragmentに変えましょう.安心して手間を省きましょう.