Android 撸起袖子,自己封装 DialogFragment

本篇文章已授权为微信公众号 code小生 发布

前言

具体的代码以及示例我都放上 Github 了,有需要的朋友可以去看一下 DialogFragmentDemos,欢迎 star 和 fork.

本文的主要内容

  • DialogFragment 是什么
  • 创建通用的 CommonDialogFragment
  • 实现各种类型的 DialogFragment

在写正文之前,先来一波效果展示吧

DialogFragmentDemos.gif

一、DialogFragment 是什么

DialogFragment 在 Android 3.0 时被引入,是一种特殊的 Fragment,用于在 Activity 的内容之上显示一个静态的对话框。例如:警告框、输入框、确认框等。

1、DialogFragment 的优点

其实在 Android 中显示对话框有两种类型可供使用,一种是 DialogFragment,而另一种则是 Dialog。在 DialogFragment 产生之前,我们创建对话框一般采用 Dialog,而且从代码的编写角度来看,Dialog 使用起来其实更加简单,但是 Google 却是推荐尽量使用 DialogFragment,是不是感觉很奇怪,其实原因也很简单, DialogFragment 有着 Dialog 所没有的非常好的特性

  • DialogFragment 本身是 Fragment 的子类,有着和 Fragment 基本一样的生命周期,使用 DialogFragment 来管理对话框,当旋转屏幕和按下后退键的时候可以更好的管理其生命周期
  • 在手机配置变化导致 Activity 需要重新创建时,例如旋转屏幕,基于 DialogFragment 的对话框将会由 FragmentManager 自动重建,然而基于 Dialog 实现的对话框却没有这样的能力

2、DialogFragment 的使用

使用 DialogFragment 至少需要实现 onCreateView() 或者 onCreateDialog() 方法,onCreateView() 即使用自定义的 xml 布局文件来展示 Dialog,而 onCreateDialog() 即使用 AlertDialog 或者 Dialog 创建出 我们想要的 Dialog,因为这篇文章主要是讲 DialogFragment 的封装,至于 DialogFragment 具体的使用,可以参考下洋神的这篇文章 Android 官方推荐 : DialogFragment 创建对话框

二、创建通用的 CommonDialogFragment

这个类是 DialogFragment 的子类,对 DialogFragment 进行封装,依赖外部传入的 AlertDialog 来构建,同时也处理了 DialogFragment 中 AlertDialog 不能设置外部取消的问题

public class CommonDialogFragment extends DialogFragment {

    /**
     * 监听弹出窗是否被取消
     */
    private OnDialogCancelListener mCancelListener;

    /**
     * 回调获得需要显示的dialog
     */
    private OnCallDialog mOnCallDialog;

    public interface OnDialogCancelListener {
        void onCancel();
    }

    public interface OnCallDialog {
        Dialog getDialog(Context context);
    }

    public static CommonDialogFragment newInstance(OnCallDialog call, boolean cancelable) {
        return newInstance(call, cancelable, null);
    }

    public static CommonDialogFragment newInstance(OnCallDialog call, boolean cancelable, OnDialogCancelListener cancelListener) {
        CommonDialogFragment instance = new CommonDialogFragment();
        instance.setCancelable(cancelable);
        instance.mCancelListener = cancelListener;
        instance.mOnCallDialog = call;
        return instance;
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        if (null == mOnCallDialog) {
            super.onCreateDialog(savedInstanceState);
        }
        return mOnCallDialog.getDialog(getActivity());
    }

    @Override
    public void onStart() {
        super.onStart();
        Dialog dialog = getDialog();
        if (dialog != null) {
            //在5.0以下的版本会出现白色背景边框,若在5.0以上设置则会造成文字部分的背景也变成透明
            if(Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
                //目前只有这两个dialog会出现边框
                if(dialog instanceof ProgressDialog || dialog instanceof DatePickerDialog) {
                    getDialog().getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
                }
            }
            Window window = getDialog().getWindow();
            WindowManager.LayoutParams windowParams = window.getAttributes();
            windowParams.dimAmount = 0.0f;
            window.setAttributes(windowParams);
        }
    }

    @Override
    public void onCancel(DialogInterface dialog) {
        super.onCancel(dialog);
        if (mCancelListener != null) {
            mCancelListener.onCancel();
        }
    }

}

可以看到这个类的代码量也是很少的,先定义了两个接口 OnDialogCancelListener,OnCallDialog,前者用于监听弹出窗是否被取消,后者则可以让我们回调获得想要显示的 Dialog,可以看到在 onCreateDialog() 中我们返回的 是 mOnCallDialog.getDialog(getActivity);,当我们在传入 Dialog 的时候,便会回调到此处,让 onCreateDialog() 返回我们传入的 Dialog,对接口回调不是很清楚的朋友,可以看下这篇文章 一个经典例子让你彻彻底底理解java回调机制

接着在 onStart() 中进行了一些特殊性的处理,因为在 5.0 以下的版本,ProgressDialog 和 DatePickerDialog 会出现白色的边框,这使得用户体验非常不好,所以我们要在此处进行相应的处理

最后便是封装我们的构造函数
newInstance(OnCallDialog call, boolean cancelable, OnDialogCancelListener cancelListener),当我们要使用这个 CommonDialogFragment 的时候,先 new 一个 OnCallDialog,将我们想要显示的 Dialog 传进去,cancelable,用于设置对话框是否能被取消,可以看到在 onCancel() 有这样一段代码

if(mCancelListener != null){
  mCancelListener.onCancel();
}

这便是我们在构造函数中传入 OnCancelListener 的原因,当我们想要做一些取消对话框后的处理时,只要在构造函数中传入 OnCancelListener,实现 onCancel() 方法就行了

三、实现各种类型的 DialogFragment

既然前面我们创建了 CommonFragment 作为所有 DialogFragment 的基类,那么接下来我们当然要好好地来实现各种类型的 DialogFragment 了,我的思路是创建一个 DialogFragmentHelper 作为实现提示框的帮助类,帮我们把代码都封装起来,使用的时候只需要关注与 AlertDialog 的交互,Helper 会帮助我们用 DialogFragment 来进行显示,这样既能统一整个应用的 Dialog 风格,又能让我们实现各种各样的对话框变得相当的简单

在实现 DialogFragmentHelper 之前我们有两件事先要做一下

1、在 styles 文件中定义我们定义我们对话框的风格样式
<style name="Base_AlertDialog" parent="Base.Theme.AppCompat.Light.Dialog">

        <!--不设置在6.0以上会出现,宽度不充满屏幕的情况-->
        <item name="windowMinWidthMinor">90%</item>

        <!-- 取消标题栏,如果在代码中settitle的话会无效 -->
        <item name="android:windowNoTitle">true</item>

        <!-- 标题的和Message的文字颜色 -->
        <!--<item name="android:textColorPrimary">@color/black</item>-->

        <!-- 修改顶部标题背景颜色,具体颜色自己定,可以是图片 -->
        <item name="android:topDark">@color/app_main_color_deep</item>

        <!--<item name="android:background">@color/white</item>-->

        <!-- 在某些系统上面设置背景颜色之后出现奇怪的背景,处这里设置背景为透明,为了隐藏边框 -->
        <!--<item name="android:windowBackground">@android:color/transparent</item>-->
        <!--<item name="android:windowFrame">@null</item>-->

        <!-- 进入和退出动画,左进右出(系统自带) -->
        <!--<item name="android:windowAnimationStyle">@android:style/Animation.Translucent</item>-->

        <!-- 按钮字体颜色,全部一起改,单个改需要在Java代码中修改 -->
        <item name="colorAccent">@color/app_main_color</item>
    </style>

我已经打上了详细的注释,相信应该很容易理解

2、写一个接口,用于 DialogFragmentHelper 与逻辑层之间进行数据监听
public interface IDialogResultListener<T> {
    void onDataResult(T result);
}

准备工作做完了,就让我们开工撸 DialogFragmentHelper 吧,因为篇幅有限,我只是代表性的选了其中的一些效果来讲,具体的代码,可以参考下 DialogFragmentDemos

public class DialogFragmentHelper {

    private static final String TAG_HEAD = DialogFragmentHelper.class.getSimpleName();

    /**
     * 加载中的弹出窗
     */
    private static final int PROGRESS_THEME = R.style.Base_AlertDialog;
    private static final String PROGRESS_TAG = TAG_HEAD + ":progress";


    public static CommonDialogFragment showProgress(FragmentManager fragmentManager, String message){
        return showProgress(fragmentManager, message, true, null);
    }

    public static CommonDialogFragment showProgress(FragmentManager fragmentManager, String message, boolean cancelable){
        return showProgress(fragmentManager, message, cancelable, null);
    }

    public static CommonDialogFragment showProgress(FragmentManager fragmentManager, final String message, boolean cancelable
            , CommonDialogFragment.OnDialogCancelListener cancelListener){

        CommonDialogFragment dialogFragment = CommonDialogFragment.newInstance(new CommonDialogFragment.OnCallDialog() {
            @Override
            public Dialog getDialog(Context context) {
                ProgressDialog progressDialog = new ProgressDialog(context, PROGRESS_THEME);
                progressDialog.setMessage(message);
                return progressDialog;
            }
        }, cancelable, cancelListener);
        dialogFragment.show(fragmentManager, PROGRESS_TAG);
        return dialogFragment;
    }

    /**
     * 带输入框的弹出窗
     */
    private static final int INSERT_THEME = R.style.Base_AlertDialog;
    private static final String INSERT_TAG  = TAG_HEAD + ":insert";

    public static void showInsertDialog(FragmentManager manager, final String title, final IDialogResultListener<String> resultListener, final boolean cancelable){

        CommonDialogFragment dialogFragment = CommonDialogFragment.newInstance(new CommonDialogFragment.OnCallDialog() {
            @Override
            public Dialog getDialog(Context context) {
                // ...
                AlertDialog.Builder builder = new AlertDialog.Builder(context, INSERT_THEME);
                builder.setPositiveButton(DIALOG_POSITIVE, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        if(resultListener != null){
                            resultListener.onDataResult(editText.getText().toString());
                        }
                    }
                });
                builder.setNegativeButton(DIALOG_NEGATIVE, null);
                return builder.create();
            }
        }, cancelable, null);
        dialogFragment.show(manager, INSERT_TAG);
    }
}

可以看到因为我们实现封装了 CommonFragment,所有这些效果的实现都变得相当的简单吗,这便是封装给我们带来的便利和好处。

就以 加载中的弹出窗 为例,来看看我们是怎么实现的

    public static CommonDialogFragment showProgress(FragmentManager fragmentManager, final String message, boolean cancelable
            , CommonDialogFragment.OnDialogCancelListener cancelListener){

        CommonDialogFragment dialogFragment = CommonDialogFragment.newInstance(new CommonDialogFragment.OnCallDialog() {
            @Override
            public Dialog getDialog(Context context) {
                ProgressDialog progressDialog = new ProgressDialog(context, PROGRESS_THEME);
                progressDialog.setMessage(message);
                return progressDialog;
            }
        }, cancelable, cancelListener);
        dialogFragment.show(fragmentManager, PROGRESS_TAG);
        return dialogFragment;
    }

我们先调用了 CommonDialogFragment 的构造函数,将一个 ProgressDialog 传进去,然后依次传入 cancelable 和 cancelListener,最后调用 show() 函数,将DialogFragment 显示出来,因为我们使用了构造函数的重载,可以看到最简单的构造函数只需要传入两个参数就行了,是不是相当的简洁啊。

应该还没忘了我们上面创建了一个 IDialogResultListener<T> 用于 DialogFragment 与逻辑层之间进行数据监听吧,为了能传入各种各样类型的数据,这里我使用了 泛型 来进行处理,就以 带输入框的弹出窗 为例来看看究竟要怎么使用吧

    public static void showInsertDialog(FragmentManager manager, final String title, final IDialogResultListener<String> resultListener, final boolean cancelable){

        CommonDialogFragment dialogFragment = CommonDialogFragment.newInstance(new CommonDialogFragment.OnCallDialog() {

            @Override
            public Dialog getDialog(Context context) {

             // ... 这里省略一部分代码
                AlertDialog.Builder builder = new AlertDialog.Builder(context, INSERT_THEME);
                builder.setPositiveButton(DIALOG_POSITIVE, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        if(resultListener != null){
                            resultListener.onDataResult(editText.getText().toString());
                        }
                    }
                });
                builder.setNegativeButton(DIALOG_NEGATIVE, null);
                return builder.create();

            }
        }, cancelable, null);
        dialogFragment.show(manager, INSERT_TAG);
    }

可以看到我们在 showInsertDialog() 方法中传入了IDialogResultListener<String> resultListener,当我们想要处理输入的内容的时候,只要在外部调用的时候,new 一个IDialogResultListener 传进去,然后实现 onDataResult() 方法就行了

以上便是全文的内容,具体的代码以及示例我都放上 Github 了,有需要的朋友可以去看一下 DialogFragmentDemos,如果觉得对你有所帮助的话,就赏个 star 吧!


猜你喜欢

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,392评论 25 707
  • 我为何要封装DialogFragment 最近在重构项目代码,项目中创建对话框用的是Dialog,AlertDia...
    牛晓伟阅读 37,278评论 52 181
  • 1 前言 上文我们实现了基本的对话框,但是这个话题并没有进行完。我们紧接上文,没有广告,精彩马上开始。 2自定义对...
    夏桑阅读 541评论 0 0
  • 回忆起来的时候还是很开心 。 但是现在 我们从最熟悉变成了最陌生 你有你的生活 我也有 你明明就在那里 我可以跟你...
    唐大阅读 206评论 0 0
  • 一、6月总结 1. 学习 完成70% 01 又学了一遍赖老师的美语音标 02 学习奶爸的在线课程,并开始从初中级学...
    biorhythmliu阅读 189评论 0 1