平常开发中,Dialog 是一个每一个 app 所必备的。java
最后封装好的 BaseDialogFragment 已经添加到个人快速开发 lib 包中。android
能够经过:implementation cn.smartsean:lib:0.0.7
快速引入,git
也能够去 AndroidCode 查看示例源码。github
一般来讲,每一个 app 的Dialog 的样式通常都是统一风格的,好比说有:bash
若是每一个都要单独写,就显得有点浪费了,通常状况下,咱们都须要进行封装,便于使用和阅读。app
那为何要使用 DialogFragment 呢?ide
使用 DialogFragment 来管理对话框,当旋转屏幕和按下后退键时能够更好的管理其生命周期,它和 Fragment 有着基本一致的生命周期。动画
而且 DialogFragment 也容许开发者把 Dialog 做为内嵌的组件进行重用,相似 Fragment (能够在大屏幕和小屏幕显示出不一样的效果)ui
那么接下来咱们就一步一步的来封装出一个便于咱们使用的 DialogFragment。this
仍是先看下效果图吧,可能有点不是很好看,毕竟没有 ui,哈哈
在构建 BaseDialogFragment 以前,咱们先分析下正常状况下,咱们使用 Dialog 都须要哪些属性:
固然,有的需求要不了这么多的属性,也有的人须要更多的属性,那就须要本身去探索了,我就讲下基于上面这些属性的封装,而后你能够基于个人 BaseDialogFragment 进行扩展。
有了上面的属性,咱们就明白了在 BaseDialogFragment 中咱们须要的字段: 新建 BaseDialogFragment
public abstract class BaseDialogFragment extends DialogFragment {
private int mWidth = WRAP_CONTENT;
private int mHeight = WRAP_CONTENT;
private int mGravity = CENTER;
private int mOffsetX = 0;
private int mOffsetY = 0;
private int mAnimation = R.style.DialogBaseAnimation;
protected DialogResultListener mDialogResultListener;
protected DialogDismissListener mDialogDismissListener;
}
复制代码
DialogBaseAnimation 是我本身定义的基本的动画样式,在 res-value-styles
下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="DialogBaseAnimation">
<item name="android:windowEnterAnimation">@anim/dialog_enter</item>
<item name="android:windowExitAnimation">@anim/dialog_out</item>
</style>
</resources>
复制代码
在 res下新建文件夹 anim ,而后在里面新建两个文件: 一、dialog_enter.xml
<?xml version="1.0" encoding="utf-8"?>
<translate
android:fromYDelta="100%p"
android:toYDelta="0%p"
android:duration="200"
xmlns:android="http://schemas.android.com/apk/res/android">
</translate>
复制代码
二、dialog_out.xml
<?xml version="1.0" encoding="utf-8"?>
<translate
android:fromYDelta="0%p"
android:toYDelta="100%p"
android:duration="200"
xmlns:android="http://schemas.android.com/apk/res/android">
</translate>
复制代码
咱们须要的基本属性已经好了,接下来就是如何经过构建者模式来赋值了。
咱们在 BaseDialogFragment 中新建 Builder:
/** * @author SmartSean */
public abstract class BaseDialogFragment extends DialogFragment {
private int mWidth = WRAP_CONTENT;
private int mHeight = WRAP_CONTENT;
private int mGravity = CENTER;
private int mOffsetX = 0;
private int mOffsetY = 0;
private int mAnimation = R.style.DialogBaseAnimation;
protected DialogResultListener mDialogResultListener;
protected DialogDismissListener mDialogDismissListener;
public static abstract class Builder<T extends Builder, D extends BaseDialogFragment> {
private int mWidth = WRAP_CONTENT;
private int mHeight = WRAP_CONTENT;
private int mGravity = CENTER;
private int mOffsetX = 0;
private int mOffsetY = 0;
private int mAnimation = R.style.DialogBaseAnimation;
public T setSize(int mWidth, int mHeight) {
this.mWidth = mWidth;
this.mHeight = mHeight;
return (T) this;
}
public T setGravity(int mGravity) {
this.mGravity = mGravity;
return (T) this;
}
public T setOffsetX(int mOffsetX) {
this.mOffsetX = mOffsetX;
return (T) this;
}
public T setOffsetY(int mOffsetY) {
this.mOffsetY = mOffsetY;
return (T) this;
}
public T setAnimation(int mAnimation) {
this.mAnimation = mAnimation;
return (T) this;
}
protected abstract D build();
protected void clear() {
this.mWidth = WRAP_CONTENT;
this.mHeight = WRAP_CONTENT;
this.mGravity = CENTER;
this.mOffsetX = 0;
this.mOffsetY = 0;
}
}
}
复制代码
能够看到:
Builder 是一个泛型抽象类,能够传入当前 Buidler 的子类 T 和 BaseDialogFragment 的子类 D,
咱们在 Builder 中对能够在 Bundle 中存储的变量都进行了赋值,而且返回泛型 T,在最终的抽象方法 build() 中返回泛型 D。
这里使用抽象的 build() 方法是由于:每一个最终的 Dialog 返回的内容是不同的,须要子类去实现。
你可能会问,前面定义的 mDialogResultListener 和 mDialogDismissListener 怎么没在 Buidler 中出现呢?
咱们知道 接口类型是不能存储在 Bundle 中的,因此咱们放在了 BaseDialogFragment 中,后面你会看到,不要急。。。
为了可以让子类也能使用咱们在上面 Builder 中构建的属性,咱们须要写一个方法,把 Builder 中获取到的值放到 Bundle 中,而后在 Fragment 的 onCreate 方法中进行赋值,
获取 Bundle :
protected static Bundle getArgumentBundle(Builder b) {
Bundle bundle = new Bundle();
bundle.putInt("mWidth", b.mWidth);
bundle.putInt("mHeight", b.mHeight);
bundle.putInt("mGravity", b.mGravity);
bundle.putInt("mOffsetX", b.mOffsetX);
bundle.putInt("mOffsetY", b.mOffsetY);
bundle.putInt("mAnimation", b.mAnimation);
return bundle;
}
复制代码
在 onCreate 中赋值:
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mWidth = getArguments().getInt("mWidth");
mHeight = getArguments().getInt("mHeight");
mOffsetX = getArguments().getInt("mOffsetX");
mOffsetY = getArguments().getInt("mOffsetY");
mAnimation = getArguments().getInt("mAnimation");
mGravity = getArguments().getInt("mGravity");
}
}
复制代码
这样咱们就能够在子类中 经过 getArgumentBundle 方法拿到 经过 Builder 拿到的值了。而且不须要在每一个子 Dialog 中获取这些值了,由于父类已经在 onCreate 中取过了。
使用 DialogFragment 必须重写 onCreateView 或者 onCreateDialog ,咱们这里选择使用重写 onCreateView,由于我以为一个项目中的 Dialog 中的样式不会有太多,重写 onCreateView 这样灵活性高,复用起来很方便。
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
setStyle();
return setView(inflater, container, savedInstanceState);
}
复制代码
首先咱们经过 style() 设置了 Dialog 所要遵循的样式:
/** * 设置统同样式 */
private void setStyle() {
//获取Window
Window window = getDialog().getWindow();
//无标题
getDialog().requestWindowFeature(STYLE_NO_TITLE);
// 透明背景
getDialog().getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
//设置宽高
window.getDecorView().setPadding(0, 0, 0, 0);
WindowManager.LayoutParams wlp = window.getAttributes();
wlp.width = mWidth;
wlp.height = mHeight;
//设置对齐方式
wlp.gravity = mGravity;
//设置偏移量
wlp.x = DensityUtil.dip2px(getDialog().getContext(), mOffsetX);
wlp.y = DensityUtil.dip2px(getDialog().getContext(), mOffsetY);
//设置动画
window.setWindowAnimations(mAnimation);
window.setAttributes(wlp);
}
复制代码
而 setView 则是一个抽象方法,让子类根据实际需求去实现:
protected abstract View setView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState);
复制代码
看下咱们定义的两个回调:
public interface DialogResultListener<T> {
void result(T result);
}
复制代码
public interface DialogDismissListener{
void dismiss(DialogFragment dialog);
}
复制代码
给咱们的 DialogFragment 回调赋值:
public BaseDialogFragment setDialogResultListener(DialogResultListener dialogResultListener) {
this.mDialogResultListener = dialogResultListener;
return this;
}
public BaseDialogFragment setDialogDismissListener(DialogDismissListener dialogDismissListener) {
this.mDialogDismissListener = dialogDismissListener;
return this;
}
复制代码
这里咱们经过 set 方法给两个回调监听赋值,而且最终都返回 this,可是这里并非真的返回 BaseDialogFragment,而是调用该方法的 BaseDialogFragment 的子类。
至于为何不放到 Builder 里面,前面已经说了,接口实例不能放到 Bundle 中。
而后在 onDismiss 中回调咱们的 DialogDismissListener
@Override
public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
if (mDialogDismissListener != null) {
mDialogDismissListener.dismiss(this);
}
}
复制代码
至于 DialogResultListener 则须要根据具体的 Dialog 实现去回调不一样的内容。
至此,咱们的基础搭建已经完成,这里再贴下完整的代码,不须要的直接略过,日后翻去看具体实现。
BaseDialogFragment
/** * @author SmartSean */
public abstract class BaseDialogFragment extends DialogFragment {
private int mWidth = WRAP_CONTENT;
private int mHeight = WRAP_CONTENT;
private int mGravity = CENTER;
private int mOffsetX = 0;
private int mOffsetY = 0;
private int mAnimation = R.style.DialogBaseAnimation;
protected DialogResultListener mDialogResultListener;
protected DialogDismissListener mDialogDismissListener;
protected static Bundle getArgumentBundle(Builder b) {
Bundle bundle = new Bundle();
bundle.putInt("mWidth", b.mWidth);
bundle.putInt("mHeight", b.mHeight);
bundle.putInt("mGravity", b.mGravity);
bundle.putInt("mOffsetX", b.mOffsetX);
bundle.putInt("mOffsetY", b.mOffsetY);
bundle.putInt("mAnimation", b.mAnimation);
return bundle;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mWidth = getArguments().getInt("mWidth");
mHeight = getArguments().getInt("mHeight");
mOffsetX = getArguments().getInt("mOffsetX");
mOffsetY = getArguments().getInt("mOffsetY");
mAnimation = getArguments().getInt("mAnimation");
mGravity = getArguments().getInt("mGravity");
}
}
protected abstract View setView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState);
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
setStyle();
return setView(inflater, container, savedInstanceState);
}
/** * 设置统同样式 */
private void setStyle() {
//获取Window
Window window = getDialog().getWindow();
//无标题
getDialog().requestWindowFeature(STYLE_NO_TITLE);
// 透明背景
getDialog().getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
//设置宽高
window.getDecorView().setPadding(0, 0, 0, 0);
WindowManager.LayoutParams wlp = window.getAttributes();
wlp.width = mWidth;
wlp.height = mHeight;
//设置对齐方式
wlp.gravity = mGravity;
//设置偏移量
wlp.x = DensityUtil.dip2px(getDialog().getContext(), mOffsetX);
wlp.y = DensityUtil.dip2px(getDialog().getContext(), mOffsetY);
//设置动画
window.setWindowAnimations(mAnimation);
window.setAttributes(wlp);
}
@Override
public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
if (mDialogDismissListener != null) {
mDialogDismissListener.dismiss(this);
}
}
public BaseDialogFragment setDialogResultListener(DialogResultListener dialogResultListener) {
this.mDialogResultListener = dialogResultListener;
return this;
}
public BaseDialogFragment setDialogDismissListener(DialogDismissListener dialogDismissListener) {
this.mDialogDismissListener = dialogDismissListener;
return this;
}
public static abstract class Builder<T extends Builder, D extends BaseDialogFragment> {
private int mWidth = WRAP_CONTENT;
private int mHeight = WRAP_CONTENT;
private int mGravity = CENTER;
private int mOffsetX = 0;
private int mOffsetY = 0;
private int mAnimation = R.style.DialogBaseAnimation;
public T setSize(int mWidth, int mHeight) {
this.mWidth = mWidth;
this.mHeight = mHeight;
return (T) this;
}
public T setGravity(int mGravity) {
this.mGravity = mGravity;
return (T) this;
}
public T setOffsetX(int mOffsetX) {
this.mOffsetX = mOffsetX;
return (T) this;
}
public T setOffsetY(int mOffsetY) {
this.mOffsetY = mOffsetY;
return (T) this;
}
public T setAnimation(int mAnimation) {
this.mAnimation = mAnimation;
return (T) this;
}
protected abstract D build();
protected void clear() {
this.mWidth = WRAP_CONTENT;
this.mHeight = WRAP_CONTENT;
this.mGravity = CENTER;
this.mOffsetX = 0;
this.mOffsetY = 0;
}
}
}
复制代码
这里咱们以确认、取消选择框为例:
public class ConfirmDialog extends BaseDialogFragment {
@Override
protected View setView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
return null;
}
}
复制代码
在一般的确认、取消选择框中,咱们须要传入的值有什么呢?
来看下具体的展现:
这里咱们定义四个 静态字符换常量:
private static final String LEFT_TEXT = "left_text";
private static final String RIGHT_TEXT = "right_text";
private static final String PARAM_TITLE = "title";
private static final String PARAM_MESSAGE = "message";
复制代码
接下来咱们须要在 Builder 中传入这些值:
新建 Buidler 继承于 BaseDialogFragment 的 Buidler:
public static class Builder extends BaseDialogFragment.Builder<Builder, ConfirmDialog> {
private String mTitle;
private String mMessage;
private String leftText;
private String rightText;
public Builder setTitle(String title) {
mTitle = title;
return this;
}
public Builder setMessage(String message) {
mMessage = message;
return this;
}
public Builder setLeftText(String leftText) {
this.leftText = leftText;
return this;
}
public Builder setRightText(String rightText) {
this.rightText = rightText;
return this;
}
@Override
protected ConfirmDialog build() {
return ConfirmDialog.newInstance(this);
}
}
复制代码
在 build 方法中咱们返回了 ConfirmDialog的实例,来看下 newInstance 方法:
private static ConfirmDialog newInstance(Builder builder) {
ConfirmDialog dialog = new ConfirmDialog();
Bundle bundle = getArgumentBundle(builder);
bundle.putString(LEFT_TEXT, builder.leftText);
bundle.putString(RIGHT_TEXT, builder.rightText);
bundle.putString(PARAM_TITLE, builder.mTitle);
bundle.putString(PARAM_MESSAGE, builder.mMessage);
dialog.setArguments(bundle);
return dialog;
}
复制代码
能够看到,咱们 new 出了一个 ConfirmDialog 实例,而后经过 getArgumentBundle(builder) 得到了在 BaseDialogFragment 中获取的到值,而且放到了 Bundle 中。
很显然,咱们这个 ConfirmDialog 还须要
builder.mTitle
builder.mMessage
builder.leftText
builder.rightText
最后经过 dialog.setArguments(bundle);
传入到 ConfirmDialog 中,返回咱们新建的 dialog 实例。
咱们新建 dialog_confirm.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="#ffffff">
<TextView
android:background="#9d9d9d"
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="center"
android:text="我是标题" />
<TextView
android:padding="24dp"
android:id="@+id/message"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/title"
android:gravity="start"
android:text="我是message" />
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/cancel_btn"
android:layout_width="101dp"
android:layout_height="46dp"
android:layout_weight="1"
android:text="取消" />
<Button
android:id="@+id/confirm_btn"
android:layout_width="103dp"
android:layout_height="48dp"
android:layout_weight="1"
android:text="肯定" />
</LinearLayout>
</LinearLayout>
复制代码
这个时候就须要在 setView 方法中获取到 dialog_confirm.xml 的控件,而后进行赋值和事件操做:
setView() 方法以下:
@Override
protected View setView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.dialog_confirm, container, false);
TextView titleTv = view.findViewById(R.id.title);
TextView messageTv = view.findViewById(R.id.message);
if (!TextUtils.isEmpty(getArguments().getString(PARAM_TITLE))) {
titleTv.setText(getArguments().getString(PARAM_TITLE));
}
if (!TextUtils.isEmpty(getArguments().getString(PARAM_MESSAGE))) {
messageTv.setText(getArguments().getString(PARAM_MESSAGE));
}
setBottomButton(view);
return view;
}
protected void setBottomButton(View view) {
Button cancelBtn = view.findViewById(R.id.cancel_btn);
Button confirmBtn = view.findViewById(R.id.confirm_btn);
if (getArguments() != null) {
cancelBtn.setText(getArguments().getString(LEFT_TEXT));
confirmBtn.setText(getArguments().getString(RIGHT_TEXT));
cancelBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (mDialogResultListener != null) {
mDialogResultListener.result(false);
dismiss();
}
}
});
confirmBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (mDialogResultListener != null) {
mDialogResultListener.result(true);
dismiss();
}
}
});
}
}
复制代码
在 MainActivity 中:
ConfirmDialog.newConfirmBuilder()
.setTitle("这是一个带有确认、取消的dialog")
.setMessage("这是一个带有确认、取消的dialog的message")
.setLeftText("我点错了")
.setRightText("我肯定")
.setAnimation(R.style.DialogAnimFromCenter)
.build()
.setDialogResultListener(new DialogResultListener<Boolean>() {
@Override
public void result(Boolean result) {
Toast.makeText(mContext, "你点击了:" + (result ? "肯定" : "取消"), Toast.LENGTH_SHORT).show();
}
})
.setDialogDismissListener(new DialogDismissListener() {
@Override
public void dismiss(DialogFragment dialog) {
Toast.makeText(mContext, "个人tag:" + dialog.getTag(), Toast.LENGTH_SHORT).show();
}
})
.show(getFragmentManager(), "confirmDialog");
复制代码
是否是调用起来很简单,当项目中的 Dialog 样式统一的时候,用这种封装是很方便的,咱们只用更改传入的值就能够获得不一样的 Dialog,不用写那么多的重复代码,省下的时间可让咱们作不少事情。
若是你有更好的想法,欢迎提出来~~~