zoukankan      html  css  js  c++  java
  • 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来讲,具体效果前一篇已经展示了,内容详情请关注接下来的新博文。


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

  • 相关阅读:
    IP和java.net.InetAddress类的使用
    Redis(五):几个常用概念
    Redis(一):概述
    mongodb写入策略(WriteConcern)
    mongodb配置详解
    MongoDB优化
    Python 多进程异常处理
    Python多进程编程-进程间协作(Queue、Lock、Semaphore、Event、Pipe)
    Mongodb 性能测试
    把 MongoDB 当成是纯内存数据库来使用(Redis 风格)
  • 原文地址:https://www.cnblogs.com/hrhguanli/p/4825839.html
Copyright © 2011-2022 走看看