Android高手进阶之两幅图搞定DilogFragment

前言

关于DilogFragment以前写过几篇博客,可是并非很深刻,有些问题也没有解决。 例如:java

一、DialogFragment和Activity的关系。android

二、DilogFragment生命周期和和设置布局大小无效问题。web

为了解决上面的2个问题,须要从两个方面来入手,一、DialogFragment和Activity的关系。二、DialogFragment和Dialog的关系。理清楚了这些关系后上面的三点问题也就解决了。数据库

一、DialogFragment和Activity的关系。

DialogFragment继承Fragment生命周期和所在的Activity生命周期相关,由FragmentManager管理。Activity的生命周期由Framework层中的ActivityManagerService来管理的,而Fragment只对Activity可见,对Framework层并不可见,也就是Framework并不知道Fragment的存在,Fragment的生命周期彻底由Activity中的FragmentManager来管理。Activity和Fragment关系以下图所示: ide

在这里插入图片描述

Activity经过FragmentManager来管理Fragment。FragmentManager能够经过FragmentTransaction把Fragment加入到Back Stack中,FragmentTransaction和数据库操做的方法同样,有add(),remove(),commit()等方法来操做Fragment。已经知道了Fragment是依赖于Activity的,下面给出Fragment和Activity生命周期的关系。 函数

在这里插入图片描述
Fragment和Activity的生命周期的对应关系后面我会专门写一篇博客。其实原理并不复杂,就是在Activity的各个生命周期中去分别调用其包含的Fragment对应的生命周期的函数。在Activtiy中使用Fragment很是简单以下:

public class DemoActivity extends FragmentActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_crime);
        FragmentManager fm = getSupportFragmentManager();
        Fragment fragment = fm.findFragmentById(R.id.fragmentContainer);
        if (fragment == null) {
            fragment = new CrimeFragment();
            fm.beginTransaction()
                    .add(R.id.fragmentContainer, fragment)
                    .commit();
        }
    }
}

复制代码

上面的例子是在Activity的onCreate方法中调用FragmentManager 来将Fragment加入到Activity中。布局

若是在Activity的其余生命周期中将Fragment加入到Activity中呢?好比说在Activity处于stopped,paused,或者running状态时,加入Fragment的生命周期是怎样的?学习

这个时候FragmentManager会当即执行Fragment须要的生命状态直到和activity相匹配的状态。好比说在Activity处于running状态时加入Fragment,此时Fragment会执行生命 onAttach(Activity), onCreate(Bundle), onCreateView(…), onActivityCreated(Bundle), onStart(),最后执行 onResume(). 以下图:在Activity处于onResume的时候addFragment,此时Fragment执行的生命周期。 ui

在这里插入图片描述

二、DilogFragment生命周期和和设置布局大小无效问题。

下面看看在Activity中使用DialogFragment的例子,而后分析两点:this

一、为何DialogFragment会以弹框的形式显示。

二、为何在要在onStart()方法中动态改变DialogFragment才能动态改变布局。

new MyDialogFragment().show(getFragmentManager(),"id");
复制代码

DialogFragment的使用十分简单,直接在Activity中调用DialogFragment的show()方法同时传入当前Activity中的FragmentManager,传入的FragmentManager确定是用来管理DialogFragment的。

public void show(FragmentManager manager, String tag) {
        mDismissed = false;
        mShownByMe = true;
        FragmentTransaction ft = manager.beginTransaction();
        ft.add(this, tag);
        ft.commit();
    }
复制代码

DialogFragment中的show()方法调用FragmentManager把本身加入到back stack中,生命周期由FragmentManager管理。在Activity中调用DialogFragment的show()方法此时并不会当即显示,这个时候关联了DialogFragment和activity的生命周期,那么DialogFragment在何时显示的呢?

@Override
    public void onStart() {
        super.onStart();
        if (mDialog != null) {
            mViewDestroyed = false;
            mDialog.show();
        }
    }
复制代码

只有DialogFragment的生命周期执行到onStart()的时候才会调用mDialog.show();显示弹框,在DialogFragment源码解析中已经说过,DialogFragment的布局会被加到mDialog中,因此DialogFragment才会显示成弹框形式。这解决了为何DialogFragment会以弹框的形式显示的问题。

在onCreateView中定义的布局最后会被加入到Dialog中,因此要改变弹出框的大小实际上是要改变Dialog的大小。Dialog的大小要在Dialog.show()方法以后才能动态改变。下面分析一下为何Dialog的大小在Dialog.show()方法以后才能改变。看看Dialog.show()的源码:

public void show() {
        if (mShowing) {
            return;
        }
        if (!mCreated) {
            dispatchOnCreate(null);
        } else {
        }
    mWindowManager.addView(mDecor, l);    
}
  
复制代码

上面代码是简化以后的,若是Dialog尚未建立就会调用dispatchOnCreate(null);方法来建立Dialog的布局,下面看看dispatchOnCreate(null)方法,简化后以下:

void dispatchOnCreate(Bundle savedInstanceState) {
        if (!mCreated) {
            mDialog.setContentView(selectContentView());
            mCreated = true;
        }
    }
//获取相应的布局样式
    private int selectContentView() {
        if (mButtonPanelSideLayout == 0) {
            return mAlertDialogLayout;
        }
        if (mButtonPanelLayoutHint == AlertDialog.LAYOUT_HINT_SIDE) {
            return mButtonPanelSideLayout;
        }
        return mAlertDialogLayout;
    }
复制代码

由上面能够看出,使用Dialog的子类AlertDialog时,使用的contentView是Android自带样式和大小的Layout,用户自定义的view被加到mDecor上,因此在show()以前设置xml的大小是无效的,最后仍是会在show中被覆盖成系统自带的格式,只有在show后面改变布局属性才会生效。

这也就解释了为何DialogFragment在onCreate()和onCreateView()中设置布局大小无效,由于onCreate()和onCreateView()生命周期在onStart()生命周期以前,此时还未调用Dialog.show()方法,设置大小无效。能够总结出Activity和DialogFragment的关系以下图:

在这里插入图片描述

DialogFragment自定义布局大小坑分析

坑自定义弹框的大小 在自定义布局中设置的大小是不起做用的,要设置自定义布局的大小只有在代码中动态设置,在onStart中重写布局大小,在onCreat或者onCreateView中无效

/** * 修改布局的大小 */
    @Override
    public void onStart() {
        super.onStart();
        XLLog.d(TAG, "onStart");
        resizeDialogFragment();

    }

    private void resizeDialogFragment() {
        Dialog dialog = getDialog();
        if (null != dialog) {
            Window window = dialog.getWindow();
            WindowManager.LayoutParams lp = getDialog().getWindow().getAttributes();
            lp.height = (25 * ScreenUtil.getScreenHeight(getContext()) / 32);//获取屏幕的宽度,定义本身的宽度
            lp.width = (8 * ScreenUtil.getScreenWidth(getContext()) / 9);
            if (window != null) {
                window.setLayout(lp.width, lp.height);
            }
        }
    }
复制代码

这里不得不提到的坑是,看起来已经动态设置了本身想要的布局大小。但实际运行出来的大小和定义的尺寸有误差。上面代码中设置的宽度是屏幕的8/9,运行代码是获得的 lp.width=960,但我用Layout Inspector检测出来自定义的布局宽度仅仅是876,这中间差了84。因此确定是系统在自定义的布局外面又包了一层其余的东西,致使设置出来的宽度和实际显示的不同。 经过

Dialog dialog = getDialog();
        if (null != dialog) {
            Window window = dialog.getWindow();
            Log.d(TAG, "padding.................." + window.getDecorView().getPaddingLeft() + "............................." + window.getDecorView().getPaddingTop());
//结果:padding..................42.............................42
复制代码

由上面结果可知,在自定义布局外面还有一个padding的距离,这个padding 距离四周的距离都是42,876+42*2=960,正好和设置的宽度相同。

检测布局

用Android studio 中的Layout Inspector检测布局

在这里插入图片描述
由上图能够看到布局结构:
在这里插入图片描述
整个弹出框的根布局是DecorView,DecorView里面包含了一个FragmentLayout,FragmentLayout里面包含两个布局一个是content,就是咱们自定义的布局,Action_mode_bar_stub这个是actionBar,咱们这里的actionBar布局为null什么都没有。 其中DecorView的定义能够看一段英文:

The DecorView is the view that actually holds the window’s background drawable. Calling getWindow().setBackgroundDrawable() from your Activity changes the background of the window by changing the DecorView‘s background drawable. As mentioned before, this setup is very specific to the current implementation of Android and can change in a future version or even on another device.

其中的padding=42就是DecorView与其余布局的间距,因此获取到DecorView再设置它的padding就行了。

解决方案:设置透明背景

要在onCreateView中设置背景为透明,原来dialogFragment在自定义的布局外加了一个背景,设置为透明背景后,尺寸就和设置的尺寸同样了。加上

getDialog().getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
复制代码
@Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        getDialog().getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
        View view = inflater.inflate(R.layout.message_share_websit_dialog, container);
        return view;

    }
复制代码

搞定

总结

本文主要从DialogFragment和Activity的关系以及DialogFragment的生命周期特色来分析为何DialogFragment会显示弹框形式,以及动态设置布局时为何要在onStart()方法中生效,在onCreate()和onCreateView()中设置不生效问题。

参考文献

一、Android Programming_ The Big Nerd Ranch Guide

二、developer.android.com/courses/fun…

备注:2连接中有google官方提供的初级和高级教程,并且配有ppt讲解十分详细,是很好的学习资源。

相关文章
相关标签/搜索