前言
最近項目中用到了DialogFragment阱冶,用起來很方便捌年,但是坑比較多飒筑,于是自己研究了下源碼离例,理清楚DialogFragment中Dialog和Fragment的關(guān)系咏雌,以及DialogFragment的原理凡怎。
DialogFragment的使用方法
1、重寫onCreateDialog方法創(chuàng)建AlertDialog
1.1 簡單的AlertDialog
public class FireMissilesDialogFragment extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// Use the Builder class for convenient dialog construction
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setMessage(R.string.dialog_fire_missiles)
.setPositiveButton(R.string.fire, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// FIRE ZE MISSILES!
}
})
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// User cancelled the dialog
}
});
// Create the AlertDialog object and return it
return builder.create();
}
}
1.2 自定義布局的AlertDialog
如果想讓對話框具有自定義布局赊抖,請創(chuàng)建一個布局统倒,然后通過調(diào)用 AlertDialog.Builder 對象上的 setView() 將其添加到 AlertDialog。
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
// Get the layout inflater
LayoutInflater inflater = getActivity().getLayoutInflater();
// Inflate and set the layout for the dialog
// Pass null as the parent view because its going in the dialog layout
builder.setView(inflater.inflate(R.layout.dialog_signin, null))//R.layout.dialog_sign 自定義布局
// Add action buttons
.setPositiveButton(R.string.signin, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
// sign in the user ...
}
})
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
LoginDialogFragment.this.getDialog().cancel();
}
});
return builder.create();
}
1.3 DialogFragment與所在的Acitivty交互
當用戶觸摸對話框的某個操作按鈕或從列表中選擇某一項時氛雪,DialogFragment 可能會執(zhí)行必要的操作房匆,如果想將事件傳遞給打開該對話框的 Activity 或Fragment。 可以為每種點擊事件定義一種方法报亩。
public class NoticeDialogFragment extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// Build the dialog and set up the button click handlers
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setMessage(R.string.dialog_fire_missiles)
.setPositiveButton(R.string.fire, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
//將點擊Positive事件傳遞給所在的Activity
mListener.onDialogPositiveClick(NoticeDialogFragment.this);
}
})
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
//將點擊Negative 事件傳遞給所在的Activity
mListener.onDialogNegativeClick(NoticeDialogFragment.this);
}
});
return builder.create();
}
//定義一個監(jiān)聽的接口浴鸿,DialogFragment所在的Activity實現(xiàn)這個接口
public interface NoticeDialogListener {
public void onDialogPositiveClick(DialogFragment dialog);
public void onDialogNegativeClick(DialogFragment dialog);
}
// Use this instance of the interface to deliver action events
NoticeDialogListener mListener;
// Override the Fragment.onAttach() method to instantiate the NoticeDialogListener
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (NoticeDialogListener) activity;
//獲取DialogFragment所在的Activity,執(zhí)行mListener方法時會自動調(diào)用Actvity中相應(yīng)的方法
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement NoticeDialogListener");
}
}
...
}
DialgFragment所在的Acitivity如下
public class MainActivity extends FragmentActivity
implements NoticeDialogFragment.NoticeDialogListener{
...
public void showNoticeDialog() {
// 創(chuàng)建DialogFragment的實例來顯示
DialogFragment dialog = new NoticeDialogFragment();
dialog.show(getSupportFragmentManager(), "NoticeDialogFragment");
}
//當DialogFragment中發(fā)生相應(yīng)的點擊事件時會自動調(diào)用到這里面的兩個方法。
@Override
public void onDialogPositiveClick(DialogFragment dialog) {
// 用戶點擊DialogFragment中的positive按鈕
...
}
@Override
public void onDialogNegativeClick(DialogFragment dialog) {
// 用戶點擊DialogFragment中的 negative 按鈕
...
}
2.重寫onCreateView
有時候需要彈出框弦追,但是不需要AlertDialog里面的功能岳链,就可以重寫onCreateView實現(xiàn)自己的布局
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.message_share_websit_dialog, container);
initView(view);
return view;
}
3 以彈出框方式顯示對話框和全屏Fragment方式顯示對話框
有時候在大尺寸的手機或者pad上可以將DialogFragment作為彈出框形式展示,在小屏幕的手機上作為一個普通Fragment的形式展示劲件。
public class CustomDialogFragment extends DialogFragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout to use as dialog or embedded fragment
return inflater.inflate(R.layout.purchase_items, container, false);
}
/** The system calls this only when creating the layout in a dialog. */
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// The only reason you might override this method when using onCreateView() is
// to modify any dialog characteristics. For example, the dialog includes a
// title by default, but your custom layout might not need it. So here you can
// remove the dialog title, but you must call the superclass to get the Dialog.
Dialog dialog = super.onCreateDialog(savedInstanceState);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
return dialog;
}
}
以下代碼可根據(jù)屏幕尺寸決定將片段顯示為對話框還是全屏 UI:
public void showDialog() {
FragmentManager fragmentManager = getSupportFragmentManager();
CustomDialogFragment newFragment = new CustomDialogFragment();
if (mIsLargeLayout) {
// 如果時大屏幕的設(shè)備掸哑,顯示為彈出框方式
newFragment.show(fragmentManager, "dialog");
} else {
// 如果是小屏幕的手機,顯示為全屏的Fragment
FragmentTransaction transaction = fragmentManager.beginTransaction();
// 設(shè)置動畫效果
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
//獲取android.R.id.content布局零远,并將newFragment加入到布局中
transaction.add(android.R.id.content, newFragment)
.addToBackStack(null).commit();
}
}
DialogFragment中源碼分析
DialogFragment的繼承結(jié)構(gòu)
public class DialogFragment extends Fragment
implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener
可以看出DialogFragment繼承了Fragment是在Fragment基礎(chǔ)之上封裝的苗分,因此DialogFragment既可以作為Dialog來使用也可以作為單獨的Fragment來使用。
問題1:DialogFragment既然繼承了Fragment為什么會顯示成一個Dialog的形式牵辣?
在DialogFragment內(nèi)部定義了一個Dialog mDialog;
當我們重寫了onCreateDialog()方法時摔癣,mDialog就是在onCreateDialog()中返回的Dialog,否則就會默認返回一個Dialog。如果我們重寫了onCreateView方法就將該布局加入到Dialog中。這個方法可以在onActivityCreated中找到
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (!mShowsDialog) {
return;//如果不顯示為Dialog形式的話不做任何處理直接返回供填。
}
View view = getView();
//獲取dialogFragment的布局拐云,這個布局就是我們在onCreateView中找到的布局。
if (view != null) {
if (view.getParent() != null) {
throw new IllegalStateException(
"DialogFragment can not be attached to a container view");
}
mDialog.setContentView(view);//如果布局不為null的話近她,將我們定義的布局加入到mDialog中叉瘩。
}
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);
}
}
}
對應(yīng)的流程圖:
onGetLayoutInflater重寫了Fragment中獲取顯示布局的過程,這個過程也決定了最后DialogFragment最后顯示的效果粘捎。
@Override
public LayoutInflater onGetLayoutInflater(Bundle savedInstanceState) {
if (!mShowsDialog) {
//如果不顯示Dialog的話直接調(diào)用Fragment中的onGetLayoutInflater方法
return super.onGetLayoutInflater(savedInstanceState);
}
mDialog = onCreateDialog(savedInstanceState);//否則新建dialog
if (mDialog != null) {
setupDialog(mDialog, mStyle);//設(shè)置Dialog的樣式
return (LayoutInflater) mDialog.getContext().getSystemService(
Context.LAYOUT_INFLATER_SERVICE);//返回dialog的布局
}
return (LayoutInflater) mHost.getContext().getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
}
創(chuàng)建Dialog薇缅,如果重寫了該方法就返回我們定義的Dialog,否則返回默認的Dialog.
@NonNull
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new Dialog(getActivity(), getTheme());
}
流程圖如下:
重點:是展示普通的Fragment還是以Dialog形式展示,由mShowsDialog來控制攒磨,mShowsDialog也可以由我們來賦值泳桦。
問題2 DialogFragment展示
DialogFragment的展示也有不同的方式,實際兩種方式本質(zhì)上是一樣的娩缰,都是需要得到FragmentTransaction來對DialogFragment的進行管理灸撰。
public void show(FragmentManager manager, String tag) {
mDismissed = false;
mShownByMe = true;
FragmentTransaction ft = manager.beginTransaction();
//獲取FragmentTransaction并將
//當前DialogFragment的實例加入到FragmentTransaction中
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;
}
問題3 DialogFragment消失
DialogFragment的消失函數(shù)有onDismiss和dismiss兩個拼坎,都調(diào)用了dismissInternal(boolean allowStateLoss)
方法浮毯,但是傳入的參數(shù)不同;allowStateLoss代表是否允許不保存DialogFragment的狀態(tài)。
傳入自定的Dialog,調(diào)用 dismissInternal(true);不保存DialogFragment的狀態(tài)泰鸡。
public void onDismiss(DialogInterface dialog) {
if (!mViewDestroyed) {
dismissInternal(true);
}
}
dismissInternal(false);保存DialogFragment的狀態(tài)
@Override
public void dismiss() {
dismissInternal(false);
}
具體的消失邏輯在dismissInternal(boolean allowStateLoss)實現(xiàn)下面具體分析源碼
void dismissInternal(boolean allowStateLoss) {
if (mDismissed) {
return;//如果當前的DialogFragment的已經(jīng)消失了债蓝,就直接返回
}
mDismissed = true;
mShownByMe = false;
if (mDialog != null) {
mDialog.dismiss();//如果mDialog不為null那么想將mDialog消失
}
mViewDestroyed = true;
if (mBackStackId >= 0) {//如果mBackStackId >0則將BackStack中所有的mBackStackId之前的DialogFragment都彈出棧
getFragmentManager().popBackStack(mBackStackId,
FragmentManager.POP_BACK_STACK_INCLUSIVE);
mBackStackId = -1;
} else {
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.remove(this);//從FragmentTransaction中移除當前的DialogFragment
if (allowStateLoss) {
ft.commitAllowingStateLoss();//如果允許不保存狀態(tài)執(zhí)行
} else {
ft.commit();//提交
}
}
}
由上可知,DialogFragment的消失其實有兩步盛龄,首先看自帶的mDialog是不是null如果不是null則先將mDialog消失饰迹,然后再移除DialogFragment。