目录介绍

  • 1.最简单的使用方法

    • 1.1 官方建议
    • 1.2 最简单的使用方法
    • 1.3 DialogFragment做屏幕适配
  • 2.源码分析
    • 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.常见问题总结
    • 5.1 使用中show()方法遇到的IllegalStateException

好消息

  • 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计47篇[近20万字],转载请注明出处,谢谢!
  • 链接地址:https://github.com/yangchong211/YCBlogs
  • 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变!
  • DialogFragment封装库项目地址:https://github.com/yangchong211/YCDialog
  • 02.Toast源码深度分析
    • 最简单的创建,简单改造避免重复创建,show()方法源码分析,scheduleTimeoutLocked吐司如何自动销毁的,TN类中的消息机制是如何执行的,普通应用的Toast显示数量是有限制的,用代码解释为何Activity销毁后Toast仍会显示,Toast偶尔报错Unable to add window是如何产生的,Toast运行在子线程问题,Toast如何添加系统窗口的权限等等
  • 03.DialogFragment源码分析
    • 最简单的使用方法,onCreate(@Nullable Bundle savedInstanceState)源码分析,重点分析弹窗展示和销毁源码,使用中show()方法遇到的IllegalStateException分析
  • 05.PopupWindow源码分析
    • 显示PopupWindow,注意问题宽和高属性,showAsDropDown()源码,dismiss()源码分析,PopupWindow和Dialog有什么区别?为何弹窗点击一下就dismiss呢?
  • 06.Snackbar源码分析
    • 最简单的创建,Snackbar的make方法源码分析,Snackbar的show显示与点击消失源码分析,显示和隐藏中动画源码分析,Snackbar的设计思路,为什么Snackbar总是显示在最下面
  • 07.弹窗常见问题
    • DialogFragment使用中show()方法遇到的IllegalStateException,什么常见产生的?Toast偶尔报错Unable to add window,Toast运行在子线程导致崩溃如何解决?

1.最简单的使用方法

1.1 官方建议

  • Android比较推荐采用DialogFragment实现对话框,它完全能够实现Dialog的所有需求,并且还能复用Fragment的生命周期管理,被后台杀死后,可以恢复重建。
  • 在手机配置变化导致 Activity 需要重新创建时,例如旋转屏幕,基于 DialogFragment 的对话框将会由 FragmentManager 自动重建,然而基于 Dialog 实现的对话框却没有这样的能力

1.2 最简单的使用方法

  • 如下所示:

    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主题样式,并且进行设置
    • 设置样式,以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中调用。

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创建出来。同时还设置了弹窗是否可以被取消,以及点击事件等等。

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中移除掉。
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/yangchong211/YCDialog

  • 自定义对话框,其中包括:自定义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.关于我的博客

DialogFragment源码分析的更多相关文章

  1. ABP源码分析一:整体项目结构及目录

    ABP是一套非常优秀的web应用程序架构,适合用来搭建集中式架构的web应用程序. 整个Abp的Infrastructure是以Abp这个package为核心模块(core)+15个模块(module ...

  2. HashMap与TreeMap源码分析

    1. 引言     在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Ja ...

  3. nginx源码分析之网络初始化

    nginx作为一个高性能的HTTP服务器,网络的处理是其核心,了解网络的初始化有助于加深对nginx网络处理的了解,本文主要通过nginx的源代码来分析其网络初始化. 从配置文件中读取初始化信息 与网 ...

  4. zookeeper源码分析之五服务端(集群leader)处理请求流程

    leader的实现类为LeaderZooKeeperServer,它间接继承自标准ZookeeperServer.它规定了请求到达leader时需要经历的路径: PrepRequestProcesso ...

  5. zookeeper源码分析之四服务端(单机)处理请求流程

    上文: zookeeper源码分析之一服务端启动过程 中,我们介绍了zookeeper服务器的启动过程,其中单机是ZookeeperServer启动,集群使用QuorumPeer启动,那么这次我们分析 ...

  6. zookeeper源码分析之三客户端发送请求流程

    znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个功能是zookeeper对于应用最重要的特性,通过这个特性可以实现的功能包括配置的 ...

  7. java使用websocket,并且获取HttpSession,源码分析

    转载请在页首注明作者与出处 http://www.cnblogs.com/zhuxiaojie/p/6238826.html 一:本文使用范围 此文不仅仅局限于spring boot,普通的sprin ...

  8. ABP源码分析二:ABP中配置的注册和初始化

    一般来说,ASP.NET Web应用程序的第一个执行的方法是Global.asax下定义的Start方法.执行这个方法前HttpApplication 实例必须存在,也就是说其构造函数的执行必然是完成 ...

  9. ABP源码分析三:ABP Module

    Abp是一种基于模块化设计的思想构建的.开发人员可以将自定义的功能以模块(module)的形式集成到ABP中.具体的功能都可以设计成一个单独的Module.Abp底层框架提供便捷的方法集成每个Modu ...

  10. ABP源码分析四:Configuration

    核心模块的配置 Configuration是ABP中设计比较巧妙的地方.其通过AbpStartupConfiguration,Castle的依赖注入,Dictionary对象和扩展方法很巧妙的实现了配 ...

随机推荐

  1. ASP.NET Core分布式项目实战(oauth2 + oidc 实现 server部分)--学习笔记

    任务15:oauth2 + oidc 实现 server部分 基于之前快速入门的项目(MvcCookieAuthSample): https://www.cnblogs.com/MingsonZhen ...

  2. SP21690 POWERUP - Power the Power Up 题解

    题目传送门 前置知识 扩展欧拉定理 解法 直接对 \(a\) 和 \(b^c\) 分讨,跑一遍扩展欧拉定理就行了. 另外由于本题的特殊规定 \(0^0=1\),故需要在当 \(a=0\) 时,对 \( ...

  3. Yum安装的Nginx安装新模块解决办法

    Nginx版本1.22 Yum安装 Step1 去官网下载对应版本的源码包 Nginx源码包官网下载地址 Step2 上传到服务器 tar -xf nginx-1.22.1.tar.gz cd ngi ...

  4. patch命令

    patch命令 patch指令让用户利用设置修补文件的方式.修改.更新原始文件,倘若一次仅修改一个文件,可直接在指令列中下达指令依序执行,如果配合修补文件的方式则能一次修补大批文件,这也是Linux系 ...

  5. 《深入理解Java虚拟机》(八) 记录一次OOM问题分析实战

    目录 一.问题分析思路 二.主要问题概述以及分析 1.相关操作 2.主要问题现象 3.初步分析问题 三.相关工具介绍 四.实际问题快照分析 1.通过Memory查看老年代内存占用情况 2.选择Live ...

  6. C# EnumWindows示例代码

    代码开箱即用,唯一需要处理的就是要提供一个进程的pid. using System; using System.Collections.Generic; using System.Linq; usin ...

  7. java学生管理系统(界面版)

    运行截图 项目说明: 本系统界面我个人就从简设计了,本来打算使用windowbuilder插件设计的,可想到使用windowbuilder插件之后导致代码冗余,会影响到代码可读性,可能对小白不友好.虽 ...

  8. Mysql 删除binlog日志方法

    方法1 RESET MASTER; 解释: 该方法可以删除列于索引文件中的所有二进制日志,把二进制日志索引文件重新设置为空,并创建一个以.000001为后缀新的二进制日志文件. 该语法一般只用在主从环 ...

  9. Vue3基础知识提炼

    1.{{}} data2.""等同{{}},数据单向绑定v-bind: 简化 :3.v-if v-else4.v-for="(i, index) in array&quo ...

  10. C++ //常用算法 adjacent_find //查找相邻的重复元素

    1 //常用算法 adjacent_find 2 //查找相邻的重复元素 3 #include<iostream> 4 #include<string> 5 #include& ...