Android官方推荐在Android 3.0之后,使用 DialogFragment 来代替 Dialog,最近项目中尝试了一下DialogFragment做下总结进一步加深理解。
思考以下几个方面:
首先DialogFragment继承自Fragment,实现了DialogInterface的两个接口,其本质是Fragment,它持有了一个Dialog对象。
public class DialogFragment extends Fragment implements DialogInterface.OnCancelListener,
DialogInterface.OnDismissListener {
...
Dialog mDialog;//持有Dialog
...
}
这就意味着,DialogFragment替我们管理了Dialog,而我们其实使用的是一个含有Dialog的Fragment。
覆写其onCreateDialog方法
覆写其onCreateView方法
这里有个比较关键的方法onGetLayoutInflater,这个方法会在Fragment的onCreateView之前完成LayoutInflater的实例化,DialogFragment在这个时候调用了onCreateDialog来创建Dialog实例,代码如下:
@Override
public LayoutInflater onGetLayoutInflater(Bundle savedInstanceState) {
if (!mShowsDialog) {
return super.onGetLayoutInflater(savedInstanceState);
}
mDialog = onCreateDialog(savedInstanceState);//创建Dialog
if (mDialog != null) {
setupDialog(mDialog, mStyle);
return (LayoutInflater) mDialog.getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
return (LayoutInflater) mHost.getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
然后在接下来的生命周期onCreateView里面中,用LayoutInflater创建了View,DialogFragment onAttatch之后触发了onActivityCreated,在onActivityCreated中调用Dialog的setContentView,将View设置给Dialog。如下代码:
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
...
View view = getView();//拿到onCreateView返回的View
if (view != null) {
if (view.getParent() != null) {
throw new IllegalStateException(
"DialogFragment can not be attached to a container view");
}
mDialog.setContentView(view);//将View设置给Dialog
}
...
}
如果覆写的是onCreateDialog方法,那么这里的view就是空的,那么Dialog的View需要在覆写的onCreateDialog中设置,这也就是两种创建方式的区别
public void show(FragmentManager manager, String tag){...}
public int show(FragmentTransaction transaction, String tag){...}
总的来说,都调用了FragmentTransaction的add方法,提交了DialogFragment。区别在于:
第一种方式(FragmentManager)会开启新的FragmentTransaction,并且不将DialogFragment加入fragment回退栈
第二种方式,因为用的是之前开启的FragmentTransaction,所以在FragmentTransaction提交时,接收了DialogFragment在回退栈中的标识符,代码如下:
public int show(FragmentTransaction transaction, String tag) {
...
transaction.add(this, tag);
mViewDestroyed = false;
mBackStackId = transaction.commit();
return mBackStackId;//标识符
}
标识符在隐藏DialogFragment时,会用来判断是否从回收站移除。在
dismissInternal方法中使用,作为判断条件
public void dismiss(){...}
public void dismissAllowingStateLoss(){...}
很容易理解,DialogFragment本身是Fragment,而DialogFragment在隐藏Dialog的同时,也调用了FragmentTransaction的remove方法将自身移除,而在提交的时候有两种方式ft.commitAllowingStateLoss()和ft.commit(),与两种隐藏方式对应。
以上两种隐藏方法同时调用了dismissInternal方法,代码如下:
void dismissInternal(boolean allowStateLoss) {
...
if (mDialog != null) {
mDialog.dismiss();//Dialog隐藏
mDialog = null;
}
...
if (mBackStackId >= 0) {
//从回退栈中移除
getFragmentManager().popBackStack(mBackStackId,
FragmentManager.POP_BACK_STACK_INCLUSIVE);
mBackStackId = -1;
} else {
//调用FragmentTransaction的remove
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.remove(this);
if (allowStateLoss) {
ft.commitAllowingStateLoss();
} else {
ft.commit();
}
}
}
通常情况下,我们为了处理Dialog比较复杂的创建,通常会像AlertDialog一样使用建造者模式来封装我们的dialog。DialogFragment当然也可以这样用,建造者模式我们可以封装一个较为通用的Dialog。
这里为了使我们的Dialog更灵活一点,我们仅仅使用模板模式做一个抽象的封装(强行用下设计模式?)
抽象类如下:
public abstract class BaseFragmentDialog extends DialogFragment implements View.OnClickListener {
protected View mLoading;
protected Context mContext;
protected View mContentView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//在这里调用setStyle设置主题样式
setStyle(DialogFragment.STYLE_NO_TITLE,
R.style.Theme_TransparentDialog);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
//RootView用来给Dialog添加loading,如果要在Dialog中做网络请求那么Loading会使内容布局复杂一层
ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.dialog_base_view, container, false);
this.mLoading=rootView.findViewById(R.id.loading_layout);
//ContentView是真正的内容
int layoutId = getLayoutResId();
if (layoutId > 0) {
mContentView = inflater.inflate(layoutId, rootView, false);
}
if (mContentView != null) {
//这个位置,把ContentView放在Loading的下面
rootView.addView(mContentView, 0);
return rootView;
} else {
return super.onCreateView(inflater, container, savedInstanceState);
}
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
this.mContext = getDialog().getContext();
//抽象方法,子类通过实现它,在其中实例化View
initView(view);
}
@Override
public void onStart() {
super.onStart();
//初始化窗口大小
initWindow();
//DialogFragment需要的参数可以通过Bundle传递
resolveBundle(getArguments());
//初始化页面数据
initData();
}
//默认实现onClick方法,子类需要处理点击事件时再覆写该方法
@Override
public void onClick(View v) {
}
//给View绑定点击事件
protected void bindViewClickListener(int id) {
if (id <= 0) {
return;
}
View view = mContentView.findViewById(id);
if (view != null) {
view.setOnClickListener(this);
}
}
/**
* 获取资源ID
*
* @return
*/
protected abstract int getLayoutResId();
/**
* 初始化视图
*
* @param view
*/
protected abstract void initView(View view);
/**
* 解析参数
*
* @param args
*/
protected abstract void resolveBundle(Bundle args);
/**
* 初始化数据
*/
protected abstract void initData();
/**
* 显示弹窗
*
* @param manager
* @param args 封装了参数的Bundle对象
* @param tag
*/
public void showDialog(FragmentManager manager, Bundle args, String tag) {
if(isAdded()){
//避免重复添加改Dialog
return;
}
this.setArguments(args);
super.show(manager, tag);
}
/**
* 初始化窗口属性
*/
protected void initWindow() {
//设置窗口属性
configWindowPercent(Gravity.CENTER, 0.35f, 0.45f);
}
/**
* 设置弹窗位置
* 设置弹窗大小(固定大小)
*/
protected void configWindowFixSize(int gravity, int width, int height) {
getDialog().getWindow().setGravity(gravity);
getDialog().getWindow().setLayout(width, height);
}
/**
* 设置弹窗位置
* 设置弹窗大小(按屏幕比例)
*/
protected void configWindowPercent(int gravity, float widthPercent, float heightPercent) {
getDialog().getWindow().setGravity(gravity);
int width = (int) (ScreenUtils.getScreenWidth(getDialog().getContext()) * widthPercent);
int height = (int) (ScreenUtils.getScreenHeight(getDialog().getContext()) * heightPercent);
getDialog().getWindow().setLayout(width, height);
}
}
其他相关代码,如下:
ScreenUtils
public class ScreenUtils {
public static int getScreenWidth(Context context){
WindowManager wm= (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics dm= new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(dm);
return dm.widthPixels;
}
public static int getScreenHeight(Context context){
WindowManager wm= (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics dm= new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(dm);
return dm.heightPixels;
}
}
R.layout.dialog_base_view
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
android:id="@+id/loading_layout"
layout="@layout/dialog_loading"
android:layout_width="120dp"
android:layout_height="120dp"
android:background="@drawable/common_loading_bg"
android:layout_gravity="center"
android:visibility="gone"/>
FrameLayout>
R.layout.dialog_loading
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/common_loading_bg"
android:gravity="center"
android:orientation="vertical">
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="36dp"
android:layout_height="36dp"
android:indeterminateDrawable="@drawable/loading" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center"
android:text="loading..."
android:textColor="@android:color/white"
android:textSize="16sp" />
LinearLayout>
自定义DIalog,实现效果如下:
public class TestDialog extends BaseFragmentDialog{
@Override
protected int getLayoutResId() {
return 0;
}
@Override
protected void initView(View view) {
}
@Override
protected void initWindow() {
configWindowPercent(Gravity.CENTER,0.7f,0.5f);
}
@Override
protected void resolveBundle(Bundle args) {
}
@Override
protected void initData() {
}
}