DialogFragment细枝末节

[图片上传失败...(image-2f551a-1552206999736)]

前言

在Android中,创建对话框有两种,一种是Dialog,另外一种则是今天的主题,官方推荐的DialogFragmet,关于Dialog的使用就不赘述了,今天主要介绍DialogFragment的使用以及一些需要注意的事项,主要有以下几点。

  • DialogFragmeng相比Dialog的优势。

  • DialogFrargment的使用。

  • DialogFragment简单源码分析。

  • DialogFragment需要注意的一些小细节。

一、DialogFragment的优势

  • 在 DialogFragment之前,我们创建对话框一般采用 Dialog,而且从代码的编写角度来看,Dialog 使用起来其实更加简单,但是 Google 却是推荐尽量使用 DialogFragment,是不是感觉很奇怪,其实原因也很简单, DialogFragment 有着 Dialog 所没有的非常好的特性,可以让它具有更高的可复用性(降低耦合)和更好的便利性(很好的处理屏幕翻转的情况)。

二、DialogFragment的使用

  • 方式一:重写DialogFragment的onCreateDialog方法
  • 方式二:重写DialogFragment的onCreateView方法

1.方式一具体使用

  • DialogFragment中重写onCreateDialog方法,返回一个Dialog。
    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        Logger.d(TAG, "onCreateDialog");
        AppCompatDialog appCompatDialog = new AppCompatDialog(requireActivity());
        TextView textView = new TextView(requireActivity());
        textView.setText("通过onCreateDialog使用DialogFragment");
        textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 28);
        appCompatDialog.setContentView(textView);
        return appCompatDialog;
    }
  • 在Activity中使用DialogFragment。
    private void showOnCreateDialogFragment(){
        DialogFragment dialogFragment = new DialogFragment();
        dialogFragment.show(getSupportFragmentManager(), "tag");
    }

2.方式二具体使用

  • DialogFragment中重写onCreateView方法,该方法创建的View将会作为Dialog的内容布局,使用方式则是跟方法一在Activity中方式一致,这里就不赘述了。
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        Logger.d(TAG, "onCreateView");
        TextView textView = new TextView(requireActivity());
        textView.setText("通过onCreateView使用DialogFragment");
        textView.setGravity(Gravity.CENTER);
        textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 28);
        return textView;
    }

3.DialogFragment中对Dialog属性的设置,我们可以在DialogFragment的onStar中来对Dialog的样式进行设置,有一点需要注意的是,对于Dialog样式的设置,必须在onCretaeDialog方法后面执行,不然得不到Dialog实例,我们可以打印一下DialogFragment从创建到show出来时执行的具体生命周期方法,具体内容如下:

2019-03-10 14:19:10.971 22626-22626/org.lym.sourcecodeparse D/DialogFragment: onAttach
2019-03-10 14:19:10.971 22626-22626/org.lym.sourcecodeparse D/DialogFragment: onCreate
2019-03-10 14:19:10.972 22626-22626/org.lym.sourcecodeparse D/DialogFragment: onCreateDialog
2019-03-10 14:19:10.994 22626-22626/org.lym.sourcecodeparse D/DialogFragment: onActivityCreated
2019-03-10 14:19:11.186 22626-22626/org.lym.sourcecodeparse D/DialogFragment: onStart
2019-03-10 14:19:11.186 22626-22626/org.lym.sourcecodeparse D/DialogFragment: onResume
  • 从上面打印的回调生命周期方法来看,我们是可以在onStart方法中对Dialog进行属性设置的,具体代码如下:
    @Override
    public void onStart() {
        super.onStart();
        Dialog dialog = getDialog();
        Window window = dialog.getWindow();
        if (window == null) {
            return;
        }
        WindowManager.LayoutParams attributes = window.getAttributes();
        //设置Dialog窗口的高度
        attributes.height = WindowManager.LayoutParams.MATCH_PARENT;
        //设置Dialog窗口的宽度
        attributes.width = WindowManager.LayoutParams.MATCH_PARENT;
        //设置Dialog的居中方向
        attributes.gravity = Gravity.CENTER;
        //设置Dialog弹出时背景的透明度
        attributes.dimAmount = 0.6f;
        //设置Dialog水平方向的间距
        attributes.horizontalMargin = 0f;
        //设置Dialog垂直方向的间距
        attributes.verticalMargin = 0f;
        //设置Dialog显示时X轴的坐标,具体屏幕X轴的偏移量
        attributes.x = 0;
        //设置Dialog显示时Y轴的坐标,距离屏幕Y轴的偏移量
        attributes.y = 0;
        //设置Dialog的透明度
        attributes.alpha = 0f;
        //设置Dialog显示和消失时的动画
        attributes.windowAnimations = 0;
        window.setAttributes(attributes);
        Logger.d(TAG, "onStart");
    }

三、DialogFragmeng源码分析

1.下面先贴一下DialogFragment的部分源码,并进行简要分析。

public class DialogFragment extends Fragment implements OnCancelListener, OnDismissListener {
    boolean mViewDestroyed;
    boolean mDismissed;
    boolean mShownByMe;

    public DialogFragment() {
    }
    
    //标准的显示Fragment的方法,没什么好说的
    public void show(FragmentManager manager, String tag) {
        this.mDismissed = false;
        this.mShownByMe = true;
        FragmentTransaction ft = manager.beginTransaction();
        ft.add(this, tag);
        ft.commit();
    }
    
    //传入一个事务管理,将fragment加入到事务管理中,并返回回退栈id
    //返回的mBackStackId将在下文中用到
    public int show(FragmentTransaction transaction, String tag) {
        this.mDismissed = false;
        this.mShownByMe = true;
        transaction.add(this, tag);
        this.mViewDestroyed = false;
        this.mBackStackId = transaction.commit();
        return this.mBackStackId;
    }
    
    //第一个show方法的加强版,看名字就知道,使用这个方法之后,则会立即执行fragment当中相关的方法,这个待会儿作出解释
    public void showNow(FragmentManager manager, String tag) {
        this.mDismissed = false;
        this.mShownByMe = true;
        FragmentTransaction ft = manager.beginTransaction();
        ft.add(this, tag);
        ft.commitNow();
    }

    //Dialaog消失时的回调,setOndismissListener是在onActivityCreate中设置的,当前正是把dialog给dismiss,并没有让dialogfragment出栈
    public void dismiss() {
        this.dismissInternal(false);
    }
    
    //dismiss的加强版,先消失dialog,并将dialogfragment移除栈内
    public void dismissAllowingStateLoss() {
        this.dismissInternal(true);
    }

    //显示判断dialog,是否已经消失,如果已经消失,则不做任何操作
    //1.如果dialog实例不为空,先调用dialog的dismiss方法,隐藏dialog
    //2.如果先前调用的是public int show(FragmentTransaction transaction, String tag)
    //方法显示的dialogfragment,那么此时会根据之前返回的mBackStackId来将fragment移除栈内
    //3.如果不是则再启用事务将dialogfragment移除栈内,这里会根据传入的allowStateLoss来区分
    //提交事务的方法
    void dismissInternal(boolean allowStateLoss) {
        if (!this.mDismissed) {
            this.mDismissed = true;
            this.mShownByMe = false;
            if (this.mDialog != null) {
                this.mDialog.dismiss();
            }

            this.mViewDestroyed = true;
            if (this.mBackStackId >= 0) {
                this.getFragmentManager().popBackStack(this.mBackStackId, 1);
                this.mBackStackId = -1;
            } else {
                FragmentTransaction ft = this.getFragmentManager().beginTransaction();
                ft.remove(this);
                if (allowStateLoss) {
                    ft.commitAllowingStateLoss();
                } else {
                    ft.commit();
                }
            }

        }
    }
    
    //如果子类重写该方法,那么使用的就是你自定义的dialog
    @NonNull
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        return new Dialog(this.getActivity(), this.getTheme());
    }
    
    //dialog设置了onDismissListener后的回调,如果dialog正常消失,次回调中的方法不会调用到
    //因为在DialogFragment的onStar方法中将mViewDestroyed变量赋值为true,dialog显示设置到调用显示出来的生命周期回调我们已经打印过了。
    public void onDismiss(DialogInterface dialog) {
        if (!this.mViewDestroyed) {
            this.dismissInternal(true);
        }
    }

    //该方法,先判断Dialog是否已经显示,然后会取onCreateView中返回的View,如果View不为空,那么该View将作为Dialog的内容布局,所以,如果你同时重写了onCreateDialog和onCreateView方法,那么会优先采用onCreateView当中的View作为内容布局,然后再作了一些监听设置
    //设置了dialog是否可点击
    //设置了dialog的消失监听onDismissListener,所以消失时会回调文中的dimiss方法
    //设置了dialog的取消监听onCancelListener,在取消时会回调文中的onCancel方法
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        if (this.mShowsDialog) {
            View view = this.getView();
            if (view != null) {
                if (view.getParent() != null) {
                    throw new IllegalStateException("DialogFragment can not be attached to a container view");
                }

                this.mDialog.setContentView(view);
            }

            Activity activity = this.getActivity();
            if (activity != null) {
                this.mDialog.setOwnerActivity(activity);
            }

            this.mDialog.setCancelable(this.mCancelable);
            this.mDialog.setOnCancelListener(this);
            this.mDialog.setOnDismissListener(this);
            if (savedInstanceState != null) {
                Bundle dialogState = savedInstanceState.getBundle("android:savedDialogState");
                if (dialogState != null) {
                    this.mDialog.onRestoreInstanceState(dialogState);
                }
            }

        }
    }
}

2.源码分析总结

  • 可以重写onCreateDialog或者onCreateView来创建DialogFragment的视图,但是onCretaeView的优先级要高于onCreateDilaog,所以没有特殊的需要,一般通过重写onCreateView来创建Dialog的视图即可。

  • 从一开始我们就打印了DialogFragment从创建视图到show出来回调的一系列生命周期方法,我们得知设置dialog的属性可以在DialogFragment中的onStar回调中进行。

  • 对于dialog的显示我们通过查看源码得知一共有三个方法,show()方法传入一个fragmentManager以及一个tag,showNow方法,传入一个fragmentManger和一个tag,show()方法,传入一个事务和一个tag,并返回该事务中回退栈的id,在调用dismissInternal()方法时,会根据显示DialogFragment的类型来操作DialogFragment出栈。

  • DialogFragment会在onActivityCreate()中对Dialog的内容试图进行一次赋值,如果你重写了onCrateView方法,并返回了一个不为空的实例,那么将会作为该Dialog的内容视图,然后再对dialog设置了是否可取消,消失监听,以及取消监听

  • 通过上面的源码简析,我们得知在DialogFragment消失时,只会将dialog给隐藏,并不会讲DialogFragment移除栈内,如果想讲DialogFragment在dialog消失时移除栈内,那么需要手动调用dismissInternal()方法

四、细节注意事项

  • 可以通过重写onCreateDialog和onCreateView来设置DialogFragment的视图,但onCraeteView设置的视图优先级要高于在onCreateDialog。

  • 设置Dialog的属性需要在onCreateDialog回调后设置,不然getDialog得到的对象则会是null,推荐在onStar方法中对Dialog进行属性设置。

  • DialogFragment单个实例只能show一次DialogFrament,如果多次展示的话,则会抛出如下异常

    Process: org.lym.sourcecodeparse, PID: 27108
    java.lang.IllegalStateException: Fragment already added: DialogFragment{8262d74 #0 tag}
        at android.support.v4.app.FragmentManagerImpl.addFragment(FragmentManager.java:1893)
        at android.support.v4.app.BackStackRecord.executeOps(BackStackRecord.java:760)
        at android.support.v4.app.FragmentManagerImpl.executeOps(FragmentManager.java:2595)
        at android.support.v4.app.FragmentManagerImpl.executeOpsTogether(FragmentManager.java:2382)
        at android.support.v4.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManager.java:2337)
  • DialogFragment在正常Dismiss后并不会直接从当前的栈中移除,而是在DialogFragment中的onDestroyView()回调时,才会对DialogFragment进行出栈操作,所以如果你如果需要在Activity中频繁的显示隐藏一个DialogFragment,那么在dismiss时需要手动的调用dismissAllowingStateLoss()方法,并且再次show时不能用上一个DialogFragment实例。

  • DialogFragment并没有对Dialog的消失提供监听给调用者使用,但是我们通过源码分析得知,DialogFragment在onActivityCreate当中其实已经帮我们设置了onDismissListener,所以我们如果需要对Dialog的消失进行监听的话只需重写onDismiss方法即可,还有一种方式则是覆盖父类设置的onDismissListener监听,但是需注意的时,这个监听的复写,必须在父类onActivityCreate方法调用之后,关于消失监听的两种写法如下:

//mListener为提供到外部使用的回调
 @Override
    public void onDismiss(DialogInterface dialog) {
        super.onDismiss(dialog);
        mListener.onDismiss(dialog);
        Logger.d(TAG, "onDismiss");
    }
    
    //复写setOnDismissListener必须发生在父类的onActivityCreate之后
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        if (getDialog() != null && null != mListener) {
            getDialog().setOnDismissListener(new DialogInterface.OnDismissListener() {
                @Override
                public void onDismiss(DialogInterface dialog) {
                    ToastUtils.showToast("覆盖后的OnDismiss Listener");
                }
            });
        }
        Logger.d(TAG, "onActivityCreated");
    }

结语:以上便是全文的内容,是自己在使用DialogFragment中碰到了一些坑以及学习后的一些理解,希望能对您有帮助。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,423评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,147评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,019评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,443评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,535评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,798评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,941评论 3 407
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,704评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,152评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,494评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,629评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,295评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,901评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,742评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,978评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,333评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,499评论 2 348