1. 程式人生 > >DialogFragment使用到原始碼完全解析

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();
            }
        }
    }