目录介绍

  • 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. Cnpack ctrl+alt+v 来回切换 变量声明区,和代码写区,非常方便

    Cnpack ctrl+alt+v 来回切换 变量声明区,和代码写区,非常方便 非常方便

  2. Power BI 7 DAY

    DAX 表达式(Data Analysis Expressions) DAX表达式的结果应用在数据透视表中 DAX表达式的结果作用于整列或者表中所有行 还需注意以下几点: a. 表名用"'' ...

  3. 【LGR-154-Div.4】洛谷入门赛 #15

    [LGR-154-Div.4]洛谷入门赛 #15 \(A\) luoguB3813 [语言月赛 202308]四个人的排名加起来没有小粉兔高 AC 水题. #include<bits/stdc+ ...

  4. 【Unity3D】伽马校正

    1 伽马相关概念 1.1 人眼对亮度变化的感知 ​ 人眼对亮度变化的感知不是线性的,如下图,人眼对亮区的亮度变化不太敏感,对暗区的亮度变化较敏感.另外,我们可以想象一下,在一个黑暗的房间里,由 1 根 ...

  5. [攻防世界][Web]ics-06

    打开靶机对应的url,展开是一个网站的样子,其实啥也么有 所有tab都点一遍,发现只有报表中心有内容,url为 http://61.147.171.105:49797/index.php?id=1 猜 ...

  6. Vue 3 的 setup语法糖到底是什么东西?

    前言 我们每天写vue3项目的时候都会使用setup语法糖,但是你有没有思考过下面几个问题.setup语法糖经过编译后是什么样子的?为什么在setup顶层定义的变量可以在template中可以直接使用 ...

  7. [Azure Developer]把Azure Function中ILogger对象静态化为静态方法提供日志记录

    问题描述 在Azure Function代码中,有默认的ILogger对象来记录函数的日志,如果函数引用了一些静态对象,是否有办法使用这个默认的ILogger对象来记录日志呢? using Syste ...

  8. 【Azure Service Fabric】关于Service Fabric的相关问题

    问题一:Service Fabric 是否支持Private Link? 在Azure Private Endpoint文档中,罗列出了 Azure 上支持 Private Link 的服务.Serv ...

  9. nmcli命令详解(创建热点,连接wifi,管理连接等)

    目录 简述 语法 比较有用的选项(OPTION) 对象 general对象(常规信息) 用途 语法 networking对象(整个网络) 用途 语法 命令示例 radio对象(无线开关) 用途 语法 ...

  10. 如何查看apk安装包源代码??Android反编译apk,解包,打包,签名一体化实测 ,修改图片音频软件名称版本号等入门

    首先下载反编译工具包 下载地址 链接:  https://zly520.lanzoui.com/ibtuxhf7rab 一.反编译工具介绍 首先 如果你想改动图片音频之类的,见末尾! 1.apktoo ...