DialogFragment使用到原始碼完全解析
前言
最近專案中用到了DialogFragment,用起來很方便,但是坑比較多,於是自己研究了下原始碼,理清楚DialogFragment中Dialog和Fragment的關係,以及DialogFragment的原理。
DialogFragment的使用方法
1、重寫onCreateDialog方法建立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
如果想讓對話方塊具有自定義佈局,請建立一個佈局,然後通過呼叫 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 可能會執行必要的操作,如果想將事件傳遞給開啟該對話方塊的 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(); } //定義一個監聽的介面,DialogFragment所在的Activity實現這個介面 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,執行mListener方法時會自動呼叫Actvity中相應的方法 } catch (ClassCastException e) { throw new ClassCastException(activity.toString() + " must implement NoticeDialogListener"); } } ... }
DialgFragment所在的Acitivity如下
public class MainActivity extends FragmentActivity
implements NoticeDialogFragment.NoticeDialogListener{
...
public void showNoticeDialog() {
// 建立DialogFragment的例項來顯示
DialogFragment dialog = new NoticeDialogFragment();
dialog.show(getSupportFragmentManager(), "NoticeDialogFragment");
}
//當DialogFragment中發生相應的點選事件時會自動呼叫到這裡面的兩個方法。
@Override
public void onDialogPositiveClick(DialogFragment dialog) {
// 使用者點選DialogFragment中的positive按鈕
...
}
@Override
public void onDialogNegativeClick(DialogFragment dialog) {
// 使用者點選DialogFragment中的 negative 按鈕
...
}
2.重寫onCreateView
有時候需要彈出框,但是不需要AlertDialog裡面的功能,就可以重寫onCreateView實現自己的佈局
@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;
}
}
以下程式碼可根據螢幕尺寸決定將片段顯示為對話方塊還是全屏 UI:
public void showDialog() {
FragmentManager fragmentManager = getSupportFragmentManager();
CustomDialogFragment newFragment = new CustomDialogFragment();
if (mIsLargeLayout) {
// 如果時大螢幕的裝置,顯示為彈出框方式
newFragment.show(fragmentManager, "dialog");
} else {
// 如果是小螢幕的手機,顯示為全屏的Fragment
FragmentTransaction transaction = fragmentManager.beginTransaction();
// 設定動畫效果
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
//獲取android.R.id.content佈局,並將newFragment加入到佈局中
transaction.add(android.R.id.content, newFragment)
.addToBackStack(null).commit();
}
}
DialogFragment中原始碼分析
DialogFragment的繼承結構
public class DialogFragment extends Fragment
implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener
問題1:DialogFragment既然繼承了Fragment為什麼會顯示成一個Dialog的形式?
原來在DialogFragment內部定義了一個Dialog mDialog;
當我們重寫了onCreateDialog()方法時,mDialog就等於我們在onCreateDialog()中返回的Dialog,否則就會預設返回一個Dialog。如果我們重寫了onCreateView方法就將該佈局加入到Dialog中。
@Override
public LayoutInflater onGetLayoutInflater(Bundle savedInstanceState) {
if (!mShowsDialog) {
return super.onGetLayoutInflater(savedInstanceState);
}
mDialog = onCreateDialog(savedInstanceState);//獲取Dialog
if (mDialog != null) {
setupDialog(mDialog, mStyle);
return (LayoutInflater) mDialog.getContext().getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
}
return (LayoutInflater) mHost.getContext().getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
}
建立Dialog,如果重寫了該方法就返回我們定義的Dialog,否則返回預設的Dialog.
@NonNull
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new Dialog(getActivity(), getTheme());
}
問題2 DialogFragment展示
public void show(FragmentManager manager, String tag) {
mDismissed = false;
mShownByMe = true;
FragmentTransaction ft = manager.beginTransaction();
ft.add(this, tag);
ft.commit();
}
/**
* Display the dialog, adding the fragment using an existing transaction
* and then {@link FragmentTransaction#commit() committing} the transaction.
* @param transaction An existing transaction in which to add the fragment.
* @param tag The tag for this fragment, as per
* {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}.
* @return Returns the identifier of the committed transaction, as per
* {@link FragmentTransaction#commit() FragmentTransaction.commit()}.
*/
public int show(FragmentTransaction transaction, String tag) {
mDismissed = false;
mShownByMe = true;
transaction.add(this, tag);
mViewDestroyed = false;
mBackStackId = transaction.commit();
return mBackStackId;
}
@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);
}
}
問題3 DialogFragment消失
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();
}
}
}