Dialog与FragmentDialog源代码分析

《世界守则》UI一片

注形容自己用语言 android学习之路

转载请保留出处 by Qiao
http://blog.csdn.net/qiaoidea/article/details/46402845

【导航】
- 弹出式对话框各种方案 从仿QQ消息提示框来谈弹出式对话框的实现方式 (Dialog,PopupWind,自己定义View,Activity。FragmentDialog)
- Dialog源代码解析 从源代码上看Dialog与DialogFragment


1.概述

  前一篇写了经常使用的弹出框的几种实现方式,这里通过源代码来简要解析下Dialog的实现原理。后便作为补充会讲下官方提倡的FragmentDialog。


2.源代码解析

  通常创建非堵塞式对话框的方式就是使用dialog了。只是在Android 3.0 之后,google更推荐使用新引入的基于Fragment的DialogFragment。

这里我们从源代码层次来看下具体实现。

2.1 Dialog

1.DialogInterface

  Dialog对话框实现的接口有DialogInterface,Window.Callback, keyEvent.Callback,OnCreateContextMenuListener,后边几个主要的Activity、View等组件都或多或少实现了。这里側重讲下Dialog专有的DialogInterface。

public interface DialogInterface { 
    public static final int BUTTON_POSITIVE = -1;
    public static final int BUTTON_NEGATIVE = -2;
    public static final int BUTTON_NEUTRAL = -3;

    @Deprecated
    public static final int BUTTON1 = BUTTON_POSITIVE;
    @Deprecated
    public static final int BUTTON2 = BUTTON_NEGATIVE;
    @Deprecated
    public static final int BUTTON3 = BUTTON_NEUTRAL;

    public void cancel();
    public void dismiss();

    interface OnCancelListener {
        public void onCancel(DialogInterface dialog);
    }

    interface OnDismissListener {
        public void onDismiss(DialogInterface dialog);
    }

    interface OnShowListener {
        public void onShow(DialogInterface dialog);
    }

    interface OnClickListener {
        public void onClick(DialogInterface dialog, int which);
    }

    interface OnMultiChoiceClickListener {
        public void onClick(DialogInterface dialog, int which, boolean isChecked);
    }

    interface OnKeyListener {
        public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event);
    }
}

  比較简单易懂。没什么要说的,定义了最主要的接口方法。一目了然。具体设置和使用都在在Dialog中具体实现。

2.Dialog全局变量

  相同比較清晰容易理解,只是多解释。

    private static final String TAG = "Dialog";
    private Activity mOwnerActivity;//关联和创建它的activity

    final Context mContext;
    final WindowManager mWindowManager;
    Window mWindow;
    View mDecor;
    private ActionBarImpl mActionBar;

    protected boolean mCancelable = true;

    private String mCancelAndDismissTaken;
    private Message mCancelMessage;//取消指令
    private Message mDismissMessage;//消失指令
    private Message mShowMessage;//显示指令

    private OnKeyListener mOnKeyListener;//点击事件

    private boolean mCreated = false;
    private boolean mShowing = false;
    private boolean mCanceled = false;

    private final Handler mHandler = new Handler();

    private static final int DISMISS = 0x43;
    private static final int CANCEL = 0x44;
    private static final int SHOW = 0x45;

    private Handler mListenersHandler;//消息指令接受处理handler

    private ActionMode mActionMode;

3.Dialog构造方法

  基本全部的缺省构造方法都是最后调用到这里:

Dialog(Context context, int theme, boolean createContextThemeWrapper) {
        if (createContextThemeWrapper) {
            if (theme == 0) {
                TypedValue outValue = new TypedValue();
                    context.getTheme().resolveAttribute(com.android.internal.R.attr.dialogTheme,
                        outValue, true);
                theme = outValue.resourceId;
            }
            mContext = new ContextThemeWrapper(context, theme);
        } else {
            mContext = context;
        }

        mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
        Window w = PolicyManager.makeNewWindow(mContext);
        mWindow = w;
        w.setCallback(this);
        w.setWindowManager(mWindowManager, null, null);
        w.setGravity(Gravity.CENTER);
        mListenersHandler = new ListenersHandler(this);
    }

  theme为零,则使用系统定义的主题风格,createContextThemeWrapper表示是否使用自己的主题风格。
  然后初始化窗口管理器和消息指令处理handler。
  贴上它的缺省构造方法:

    public Dialog(Context context) {
        this(context, 0, true);
    }

    public Dialog(Context context, int theme) {
        this(context, theme, true);
    }

    @Deprecated
    protected Dialog(Context context, boolean cancelable,
            Message cancelCallback) {
        this(context);
        mCancelable = cancelable;
        mCancelMessage = cancelCallback;
    }

    protected Dialog(Context context, boolean cancelable,
            OnCancelListener cancelListener) {
        this(context);
        mCancelable = cancelable;
        setOnCancelListener(cancelListener);
    }

4.Dialog显示 show()

  通过加入dialog至根视图并附加到window窗口中去,同一时候发送dialog显示的消息指令。

 public void show() {
         /**
         * 假设已经显示了,则重绘actionBar并显示根视图View mDecor
         */
        if (mShowing) {
            if (mDecor != null) {
                if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
                    mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
                }
                mDecor.setVisibility(View.VISIBLE);
            }
            return;
        }

        mCanceled = false;//设置取消状态为false

        /**
         * 假设Dialog没有被创建和初始化则调用dispatchOnCreate创建操作
         */
        if (!mCreated) {
            dispatchOnCreate(null);
        }

        /**
         * 初始化ActionBar动画效果和 根视图View
         */
        onStart();
        mDecor = mWindow.getDecorView();

        /**
         * 初始化ActionBar文本图片
         */
        if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
            final ApplicationInfo info = mContext.getApplicationInfo();
            mWindow.setDefaultIcon(info.icon);
            mWindow.setDefaultLogo(info.logo);
            mActionBar = new ActionBarImpl(this);
        }

        //依据输入法模式来载入布局參数
        WindowManager.LayoutParams l = mWindow.getAttributes();
        if ((l.softInputMode
                & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
            WindowManager.LayoutParams nl = new WindowManager.LayoutParams();
            nl.copyFrom(l);
            nl.softInputMode |=
                    WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
            l = nl;
        }

        //加入根视图View到窗口,发送显示指令
        try {
            mWindowManager.addView(mDecor, l);
            mShowing = true;

            sendShowMessage();//显示消息指令
        } finally {
        }
    }

    //设置同意ActionBar的显示隐藏动画
     protected void onStart() {
        if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(true);
    }

    //从消息池去一条消息,发送显示指令给目标handler
     private void sendShowMessage() {
        if (mShowMessage != null) {
               Message.obtain(mShowMessage).sendToTarget();
        }
    }

  说到这里最好还是看一下处理消息指令的handler和其具体逻辑。

5.ListenersHandler

  不了解Hanler的同学能够參考一下前面在线程更新UI时候讲到的 消息处理系统模型,当中有关于hanlder的简要解说。
  这里看ListenersHandler 的具体逻辑,事实上非常easy。就是调用对应的接口监听事件:

private static final class ListenersHandler extends Handler {
        private WeakReference<DialogInterface> mDialog;

        public ListenersHandler(Dialog dialog) {
            mDialog = new WeakReference<DialogInterface>(dialog);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case DISMISS:
                    ((OnDismissListener) msg.obj).onDismiss(mDialog.get());
                    break;
                case CANCEL:
                    ((OnCancelListener) msg.obj).onCancel(mDialog.get());
                    break;
                case SHOW:
                    ((OnShowListener) msg.obj).onShow(mDialog.get());
                    break;
            }
        }
    }

6.Dialog的hide()

  方法非常easy,仅仅是让根视图不可见:

    public void hide() {
        if (mDecor != null) {
            mDecor.setVisibility(View.GONE);
        }
    }

  真正让dialog移除和消失的是dismiss()方法:

6.Dialog的dismiss()

假设当前线程不是mHandler所在线程,则通过发送消息处理。终于都是运行dismissDialog()方法:
    @Override
    public void dismiss() {
        if (Looper.myLooper() == mHandler.getLooper()) {
            dismissDialog();
        } else {
            mHandler.post(mDismissAction);
        }
    }

     void dismissDialog() {
         /**
         *假设根视图为空或者当前dialog并没有显示,直接返回
         */
        if (mDecor == null || !mShowing) {
            return;
        }

        //假设当前窗口已经destoryed掉。直接返回
        if (mWindow.isDestroyed()) {
            Log.e(TAG, "Tried to dismissDialog() but the Dialog's window was already destroyed!");
            return;
        }

        /**
        *首先移除根视图View,调用mActionMode的finish()方法,然后清空窗口。改变显示状态并发送dialog消失指令
        */
        try {
            mWindowManager.removeViewImmediate(mDecor);
        } finally {
            if (mActionMode != null) {
                mActionMode.finish();
            }
            mDecor = null;
            mWindow.closeAllPanels();
            onStop();
            mShowing = false;

            sendDismissMessage();
        }
    }

    //关闭ActionBar的动画效果
    protected void onStop() {
        if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(false);
    }

     private void sendDismissMessage() {
        if (mDismissMessage != null) {
            // Obtain a new message so this dialog can be re-used
            Message.obtain(mDismissMessage).sendToTarget();
        }
    }

7.Dialog取消cancel()

改动状态并发送消息,最后调用的是dismiss()方法
  public void cancel() {
        if (!mCanceled && mCancelMessage != null) {
            mCanceled = true;
            Message.obtain(mCancelMessage).sendToTarget();
        }
        dismiss();
    }

8.Dialog状态保存与恢复

 private static final String DIALOG_SHOWING_TAG = "android:dialogShowing";
    private static final String DIALOG_HIERARCHY_TAG = "android:dialogHierarchy";

    /**
     * 保存dialog当前状态
     */
    public Bundle onSaveInstanceState() {
        Bundle bundle = new Bundle();
        bundle.putBoolean(DIALOG_SHOWING_TAG, mShowing);
        if (mCreated) {
            bundle.putBundle(DIALOG_HIERARCHY_TAG, mWindow.saveHierarchyState());
        }
        return bundle;
    }

    /**
     * 载入状态并恢复
     */
    public void onRestoreInstanceState(Bundle savedInstanceState) {
        final Bundle dialogHierarchyState = savedInstanceState.getBundle(DIALOG_HIERARCHY_TAG);
        if (dialogHierarchyState == null) {
            // dialog has never been shown, or onCreated, nothing to restore.
            return;
        }
        dispatchOnCreate(savedInstanceState);
        mWindow.restoreHierarchyState(dialogHierarchyState);
        if (savedInstanceState.getBoolean(DIALOG_SHOWING_TAG)) {
            show();
        }
    }

9.Dialog事件监听

  该部分省略,主要定义了返回事件。按键事件和触摸事件以及长按菜单和焦点变化事件等,能够在源代码中查看具体。
  Dialog 和DialogFragment源代码

2.2 DialogFragment

  android 3.0之后引入Fragment,并推荐DialogFragment代替Dialog,一方面更好的碎片化布局能够内签到基本界面,还有一方面更能有效地在屏幕方向切换时保存状态和恢复。


  DialogFragment继承自Fragment并实现了DialogInterface的OnCancelListener和OnDismissListener 两个接口。
其基本样式有:

  • 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;

1.DialogFragment全局变量

加上前面的几种样式和静态常量Tag。其全局变量也非常easy醒目
    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;//回退栈id

    Dialog mDialog;
    boolean mViewDestroyed;
    boolean mDismissed;
    boolean mShownByMe;

2.DialogFragment继承和重写Fragment

  看下在DialogFragment中重写的Fragment方法

    //空的构造方法
    public DialogFragment() {
    }

    //关联绑定activity
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        if (!mShownByMe) {
            // 假设不是通过我们的API创建,则该布局将不再被消失(比如作为activity界面碎片的一部分,而不是作为对话框)
            mDismissed = false;
        }
    }

    //解除绑定
     @Override
    public void onDetach() {
        super.onDetach();
        if (!mShownByMe && !mDismissed) {
            // 同上。在家畜绑定的时候才消失
            mDismissed = true;
        }
    }

    //fragment创建
     @Override
    public void onCreate(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);
        }

    }

    //依据主题风格来返回布局载入器LayoutInflater 
     @Override
    public LayoutInflater getLayoutInflater(Bundle savedInstanceState) {
        if (!mShowsDialog) {
            return super.getLayoutInflater(savedInstanceState);
        }

        mDialog = onCreateDialog(savedInstanceState);
        switch (mStyle) {
            case STYLE_NO_INPUT:
                mDialog.getWindow().addFlags(
                        WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
                        WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
                // fall through...
            case STYLE_NO_FRAME:
            case STYLE_NO_TITLE:
                mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
        }
        if (mDialog != null) {
            return (LayoutInflater) mDialog.getContext().getSystemService(
                    Context.LAYOUT_INFLATER_SERVICE);
        }
        return (LayoutInflater) mActivity.getSystemService(
                Context.LAYOUT_INFLATER_SERVICE);
    }

    //当Activity创建时设置绑定Dialog的对应事件
     @Override
    public void onActivityCreated(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);
        }
        mDialog.setOwnerActivity(getActivity());
        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 onStop() {
        super.onStop();
        if (mDialog != null) {
            mDialog.hide();
        }
    }

    //移除Fragment的时候同一时候移除和置空dialog
    @Override
    public void onDestroyView() {
        super.onDestroyView();
        if (mDialog != null) {
            mViewDestroyed = true;
            mDialog.dismiss();
            mDialog = null;
        }
    }

3.DialogFragment的onCreateDialog()

  该方法负责创建我们的创建Dialog,返回一个Dialog实例

     public Dialog onCreateDialog(Bundle savedInstanceState) {
        return new Dialog(getActivity(), getTheme());
    }

3.DialogFragment的show()

通过获取Fragment的FragmentTransaction 载入并显示当前布局。
    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;
    }

4.DialogFragment的dismiss()

  假设回退栈不为空,则返回前一个栈页面,否则直接移除掉当前页。allowStateLoss參数表示是否同意提交的时候丢失保存的状态,false则状态未保存会抛异常。

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

5.DialogFragment的其它方法

  DialogFragment还定义了获取当前Dialog,设置style和取得是否显示的状态等方法,这里省略。不做赘述。具体查看源代码。
  Dialog 和DialogFragment源代码

  

3.综述

  这两个雷相对照较简单和易于理解。重在实现view的加入。显示、隐藏和移除。

补充: Dialog的Builder模式

  在AlertDialog的实现中。有使用到Builder这么一个静态类。事实上实用的设计模式中的Builder模式。

关于建造者模式(Builder Pattern)。也叫生成器模式。它能将一个复杂对象的构建与它的表示分离开。同一时候使相同的构建过程能够创建不同的表示。
 从AlertDialog简单解释来讲,就是我们不用关心这个dialog是怎样创建,怎么实现,仅仅用简单的通过builder来设计我们想要的结果,他的每一个set方法都返回其对象本身,我们仅仅需将想要的效果和属性附加到这个对象上去就好了。想深入了解的同学。能够自行谷歌/百度 Java设计模式–建造者模式(Builder Pattern).
  
  下一篇。我们将通过自己定义来实现IOS一个常见的ActionSheet样式。demo结合自己定义View,到使用builder模式,重写DialogFragment来讲,具体效果前一篇已经展示了,内容详情请关注接下来的新博文。


版权声明:本文博主原创文章。博客,未经同意不得转载。

原文地址:https://www.cnblogs.com/hrhguanli/p/4825839.html