DialogFragment源码分析

目录介绍

  • 1.最简单的使用方法php

    • 1.1 官方建议
    • 1.2 最简单的使用方法
    • 1.3 DialogFragment作屏幕适配
  • 2.源码分析java

    • 2.1 DialogFragment继承Fragment
    • 2.2 onCreate(@Nullable Bundle savedInstanceState)源码分析
    • 2.3 setStyle(@DialogStyle int style, @StyleRes int theme)
    • 2.4 onActivityCreated(Bundle savedInstanceState)源码分析
    • 2.5 onCreateDialog(Bundle savedInstanceState)源码分析
    • 2.6 重点分析弹窗展现和销毁源码
  • 3.经典总结
  • 4.DialogFragment封装库介绍
  • 5.常见问题总结android

    • 5.1 使用中show()方法遇到的IllegalStateException

好消息

  • 博客笔记大汇总【16年3月到至今】,包括Java基础及深刻知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,固然也在工做之余收集了大量的面试题,长期更新维护而且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计47篇[近20万字],转载请注明出处,谢谢!
  • 连接地址:https://github.com/yangchong2...
  • 若是以为好,能够star一下,谢谢!固然也欢迎提出建议,万事起于忽微,量变引发质变!
  • DialogFragment封装库项目地址:https://github.com/yangchong2...
  • 02.Toast源码深度分析git

    • 最简单的建立,简单改造避免重复建立,show()方法源码分析,scheduleTimeoutLocked吐司如何自动销毁的,TN类中的消息机制是如何执行的,普通应用的Toast显示数量是有限制的,用代码解释为什么Activity销毁后Toast仍会显示,Toast偶尔报错Unable to add window是如何产生的,Toast运行在子线程问题,Toast如何添加系统窗口的权限等等
  • 03.DialogFragment源码分析github

    • 最简单的使用方法,onCreate(@Nullable Bundle savedInstanceState)源码分析,重点分析弹窗展现和销毁源码,使用中show()方法遇到的IllegalStateException分析
  • 05.PopupWindow源码分析面试

    • 显示PopupWindow,注意问题宽和高属性,showAsDropDown()源码,dismiss()源码分析,PopupWindow和Dialog有什么区别?为什么弹窗点击一下就dismiss呢?
  • 06.Snackbar源码分析编程

    • 最简单的建立,Snackbar的make方法源码分析,Snackbar的show显示与点击消失源码分析,显示和隐藏中动画源码分析,Snackbar的设计思路,为何Snackbar老是显示在最下面
  • 07.弹窗常见问题segmentfault

    • DialogFragment使用中show()方法遇到的IllegalStateException,什么常见产生的?Toast偶尔报错Unable to add window,Toast运行在子线程致使崩溃如何解决?

1.最简单的使用方法

1.1 官方建议

  • Android比较推荐采用DialogFragment实现对话框,它彻底可以实现Dialog的全部需求,而且还能复用Fragment的生命周期管理,被后台杀死后,能够恢复重建。

1.2 最简单的使用方法

  • 以下所示:markdown

    public class CustomDialogFragment extends DialogFragment {
    
        @Override
        public void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            //设置样式
            setStyle(DialogFragment.STYLE_NO_TITLE, R.style.CenterDialog);
        }
    
        @Nullable
        @Override
        public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
                                 @Nullable Bundle savedInstanceState) {
            return inflater.inflate(R.layout.view_fragment_dialog, container, false);
        }
    
        public static void showDialog(FragmentActivity activity){
            CustomDialogFragment customDialogFragment = new CustomDialogFragment();
            customDialogFragment.show(activity.getSupportFragmentManager(),"yc");
        }
    }
    
    //而后一行代码调用
    CustomDialogFragment.showDialog(this);
  • 1.2.1 建立theme主题样式,而且进行设置app

    • 设置样式,以DialogFragment为例,只须要在onCreate中setStyle(DialogFragment.STYLE_NO_TITLE, R.style.CenterDialog)便可。
    • 注意,CenterDialog中能够设置弹窗的动画效果。
    • 注意一下style常量,这里只是展现经常使用的。
    STYLE_NORMAL:会显示一个普通的dialog
    STYLE_NO_TITLE:不带标题的dialog
    STYLE_NO_FRAME:无框的dialog
    STYLE_NO_INPUT:没法输入内容的dialog,即不接收输入的焦点,并且触摸无效。
  • 1.2.2 重写onCreateView方法建立弹窗
  • 1.2.3 建立类的对象,而后调用show(FragmentManager manager, String tag)方法便可建立出弹窗
  • 1.2.4 如何去掉标题栏,也许你会问,为何第二种要在super.onActivityCreated(savedInstanceState)以前设置呢。这个是由于,看了源码以后才知道onActivityCreated这个方法中,有mDialog.setContentView(view)这一步,说到setContentView是否是很熟悉。没错,后面再深度解析这块源码思路……

    //第一种
    //设置样式时,使用STYLE_NO_TITLE
    setStyle(DialogFragment.STYLE_NO_TITLE, R.style.CenterDialog);
    
    //第二种
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        Window window = getDialog().getWindow();
        if(window!=null){
            window.requestFeature(Window.FEATURE_NO_TITLE);
        }
        super.onActivityCreated(savedInstanceState);
    }

2.源码分析

2.1 DialogFragment继承Fragment

  • DialogFragment是继承Fragment,具备Fragment的生命周期,本质上说就是Fragment,只是其内部还有一个dialog而已。你既能够当它是Dialog使用,也能够把它做为Fragment使用。

2.2 onCreate(@Nullable Bundle savedInstanceState)源码分析

  • onCreate这个方法主要是保存一些属性状态,好比style样式,theme注意,是否能够取消,后退栈的ID等等。

    • 重点看一下mShowsDialog这个参数,这个参数是Boolean值,mShowsDialog = mContainerId == 0;因此,默认状况下,mContainerId就是0,因此mShowsDialog就是true;而当你在把它当成Fragment使用时,会为其指定xml布局中位置,那么mContainerId也会不为0,因此mShowsDialog就是false。
    @Override
    public void onCreate(@Nullable 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);
        }
    }
  • mShowsDialog这个参数的做用

    • 而后直接搜索,能够看到这个参数,能够看到mShowsDialog是false,若是不是Dialog,则调用Fragment自身的方法;不然,就先建立一个dialog,而后,根据以前设置的style,经过setupDialog(mDialog, mStyle),对dialog赋值。因此,setStyle这个方法调用,必定要在onCreateView以前。通常来说,都会放到onCreate中调用。
    • image

2.3 setStyle(@DialogStyle int style, @StyleRes int theme)源码分析

  • 这个方法很重要呢,注意是设置对话框的基本外观和设置主题等等。经过手动设置Dialog和Window能够实现相同的效果,若是是在对话框建立以后调用它将会失去做用……

    • 经过这个方法,能够看到,在不设置theme,即为0的状况下,theme会被设置为android.R.style.Theme_Panel。
    public void setStyle(@DialogStyle int style, @StyleRes int theme) {
        mStyle = style;
        if (mStyle == STYLE_NO_FRAME || mStyle == STYLE_NO_INPUT) {
            mTheme = android.R.style.Theme_Panel;
        }
        if (theme != 0) {
            mTheme = theme;
        }
    }

2.4 onActivityCreated(Bundle savedInstanceState)源码分析

  • 该方法的做用主要是:当DialogFragment依附的Activity被建立的时候调用,此时fragment的活动窗体被初始化

    • 能够看到这个方法,若是是弹窗已经show出来的话,则直接return。而后经过setContentView方法将view建立出来。同时还设置了弹窗是否能够被取消,以及点击事件等等。
    • image

2.5 onCreateDialog(Bundle savedInstanceState)源码分析

  • onCreateDialog方法,你能够重写这个方法,建立一个本身定义好的dialog。默认状况下,会本身建立一个Dialog。

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

2.6 重点分析弹窗展现和销毁源码

2.6.1 show方法
  • 第一种:显示对话框,将片断添加到给定的FragmentManager中。这对于显式建立事务、使用给定的标记将片断添加到事务并提交它是很方便的。这样作能够将事务添加到后台堆栈。当片断被取消时,将执行一个新的事务来从活动中删除它。
  • 第二种:显示对话框,使用现有事务添加片断,而后提交事务。
  • 共同点:这两种显示方式都是经过tag的方式将DialogFragment以事务的形式提交,不一样的是第二种方式是采用已经建立过的transaction,而且他返回了一个int类型的数值mBackStackId,mBackStackId是干什么用的呢?

    • mBackStackId:是作为将DialogFragment压入回退栈的编号,初始值是-1,若是DialogFragment是用第二种方式show的话,他将被transaction默认压入回退栈,mBackStackId=transaction.commit(),此时她的回退栈编号大于0,她的具体使用在dismissInternal方法中后面会具体介绍
    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;
    }
2.6.2 dismiss()销毁方法
  • 在源码中能够看到这两个方法都调用了dismissInternal(boolean)方法,不一样的是传入的boolean值一个为false一个为true,那么究竟这个boolean起到什么做用呢?

    • 在dismissInternal这个方法中,主要操做了:若是对话框已经不可见就跳出方法体;设置对话框消失,而后将对话框属性设置不可见;若是DialogFragment中的Dialog对象不为空,就让其内的对话框消失;而后销毁View;对于回退栈编号mBackStackId,在前面show方法源码分析时提到这个呢!主要是用show(FragmentTransaction transaction, String tag)这个方法来压栈的,因此要取消对话框须要在这里面判断,已压栈的要弹出回退栈,这个回退栈是由Activity来管理的,若是show(FragmentManager manager, String tag)方式的话则不须要弹栈,只须要在FragmentTransaction中将其remove掉便可。
    • 简单总结就是:调用dialog的dismiss方法后,若是本身在后退栈中,就将本身从后退栈中移除掉;若是本身不在后退栈中,就将本身从FragmentManager中移除掉。
    • image
    • image
2.6.3 dialog显示与隐藏
  • 具体看下面代码

    • 在OnStart的时候,将dialog进行show出来;在生命周期方法onStop()时,则是将其先隐藏;最后在onDestroyView方法,它会将dialog销毁并置null。
    @Override
    public void onStart() {
        super.onStart();
        if (mDialog != null) {
            mViewDestroyed = false;
            mDialog.show();
        }
    }
    
    @Override
    public void onStop() {
        super.onStop();
        if (mDialog != null) {
            mDialog.hide();
        }
    }
    
    @Override
    public void onDestroyView() {
        super.onDestroyView();
        if (mDialog != null) {
            // Set removed here because this dismissal is just to hide
            // the dialog -- we don't want this to cause the fragment to
            // actually be removed.
            mViewDestroyed = true;
            mDialog.dismiss();
            mDialog = null;
        }
    }

3.经典总结

  • DialogFragment是继承Fragment,具备Fragment的生命周期,本质上说就是Fragment,只是其内部还有一个dialog而已。你既能够当它是Dialog使用,也能够把它做为Fragment使用。
  • onCreateView能够加载客户化更高的对话框,onCreateDialog加载系统AlertDialog类型对话框比较合适。
  • DialogFragmnet对话框横屏时对话框不会关闭,由于DailogFragment有Fragment属性,会在屏幕发生变化时从新建立DialogFragment。
  • setStyle的调用点,要放在onCreateView前,通常是放在onCreat方法中执行,不然,设置的style和theme将不起做用!setStyle中,style的参数是不能够相互一块儿使用的,只能用一个,若是还不知足你使用,能够经过设置theme来知足。

4.DialogFragment封装库介绍

项目地址:https://github.com/yangchong2...

  • 自定义对话框,其中包括:自定义Toast,采用builder模式,支持设置吐司多个属性;自定义dialog控件,仿IOS底部弹窗;自定义DialogFragment弹窗,支持自定义布局,也支持填充recyclerView布局;自定义PopupWindow弹窗,轻量级,还有自定义Snackbar等等;还有自定义loading加载窗,简单便用。这里只是展现dialogFragment用法!
  • 第一种:链式编程,以下所示

    BottomDialogFragment.create(getSupportFragmentManager())
        .setViewListener(new BottomDialogFragment.ViewListener() {
            @Override
            public void bindView(View v) {
                
            }
        })
        .setLayoutRes(R.layout.dialog_bottom_layout_list)
        .setDimAmount(0.5f)
        .setTag("BottomDialog")
        .setCancelOutside(true)
        .setHeight(getScreenHeight() / 2)
        .show();
  • 第二种:直接继承,能够高度定制本身想要的弹窗

    public class ADialog extends BaseDialogFragment {
        @Override
        protected boolean isCancel() {
            return false;
        }
    
        @Override
        public int getLayoutRes() {
            return 0;
        }
    
        @Override
        public void bindView(View v) {
    
        }
    }

5.常见问题总结

5.1 使用中show()方法遇到的IllegalStateException

  • 报错日志以下:

    lang.IllegalStateException: Can not perform this action after onSaveInstanceState at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1493)
  • 出现该问题的缘由

    • Activity 调用了onSaveInstanceState()之后有触发了dialog的显示,dialog.show()方法里边用的是commit()而不是commitAllowingStateLoss()
  • 追踪报错日志的来源

    • 因而,我挺好奇,show方法中只有两个参数,决定从getSupportFragmentManager()方法分析.FragmentManager是抽象类,我这里主要是看FragmentManagerImpl实现类代码
    //第一步:
    public FragmentManager getSupportFragmentManager() {
        return mFragments.getSupportFragmentManager();
    }
    
    //第二步:
    public FragmentManager getSupportFragmentManager() {
        return mHost.getFragmentManagerImpl();
    }
    
    //第三步:
    FragmentManagerImpl getFragmentManagerImpl() {
        return mFragmentManager;
    }
    
    //第四步:看beginTransaction()方法
    @Override
    public FragmentTransaction beginTransaction() {
        return new BackStackRecord(this);
    }
    
    //第五步:看BackStackRecord类中看commit方法
    @Override
    public int commit() {
        return commitInternal(false);
    }
    
    @Override
    public int commitAllowingStateLoss() {
        return commitInternal(true);
    }
    
    //第六步:能够看到这俩函数的区别就是commitInternal()方法中参数一个为true,一个为false
    int commitInternal(boolean allowStateLoss) {
        if (mCommitted) throw new IllegalStateException("commit already called");
        if (FragmentManagerImpl.DEBUG) {
            Log.v(TAG, "Commit: " + this);
            LogWriter logw = new LogWriter(TAG);
            PrintWriter pw = new PrintWriter(logw);
            dump("  ", null, pw, null);
            pw.close();
        }
        mCommitted = true;
        if (mAddToBackStack) {
            mIndex = mManager.allocBackStackIndex(this);
        } else {
            mIndex = -1;
        }
        mManager.enqueueAction(this, allowStateLoss);
        return mIndex;
    }
    
    //第七步:再追踪到enqueueAction(this,allowStateLoss)
    public void enqueueAction(OpGenerator action, boolean allowStateLoss) {
        if (!allowStateLoss) {
            checkStateLoss();
        }
        synchronized (this) {
            if (mDestroyed || mHost == null) {
                throw new IllegalStateException("Activity has been destroyed");
            }
            if (mPendingActions == null) {
                mPendingActions = new ArrayList<>();
            }
            mPendingActions.add(action);
            scheduleCommit();
        }
    }
    
    //第八步:checkStateLoss()方法,这里能够看到抛出的错误日志呢
    private void checkStateLoss() {
        if (mStateSaved) {
            throw new IllegalStateException(
                    "Can not perform this action after onSaveInstanceState");
        }
        if (mNoTransactionsBecause != null) {
            throw new IllegalStateException(
                    "Can not perform this action inside of " + mNoTransactionsBecause);
        }
    }

关于其余内容介绍

01.关于博客汇总连接

02.关于个人博客

相关文章
相关标签/搜索