平常開發中,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,不用寫那麼多的重複代碼,省下的時間可讓咱們作不少事情。
若是你有更好的想法,歡迎提出來~~~