DialogFragment的使用总结

Android官方推荐在Android 3.0之后,使用 DialogFragment 来代替 Dialog,最近项目中尝试了一下DialogFragment做下总结进一步加深理解。

思考以下几个方面:

  1. DialogFragment是什么?
  2. 如何使用?
  3. 有哪些需要注意的地方?
  4. 如何封装一下,使用起来更方便?

一、DialogFragment是什么?

首先DialogFragment继承自Fragment,实现了DialogInterface的两个接口,其本质是Fragment,它持有了一个Dialog对象。

public class DialogFragment extends Fragment implements DialogInterface.OnCancelListener,
DialogInterface.OnDismissListener {
    ...
    Dialog mDialog;//持有Dialog
    ...
}

这就意味着,DialogFragment替我们管理了Dialog,而我们其实使用的是一个含有Dialog的Fragment。

二、如何使用?

  1. 创建DialogFragment有两种方式:
     覆写其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中设置,这也就是两种创建方式的区别

  1. 显示DialogFragment,DialogFragment的show方法有两个重载方法
      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方法中使用,作为判断条件

  1. 隐藏DialogFragment的方法有两个
     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();
            }
        }
    }

三、有哪些需要注意的地方?

  1. 避免反复提交
    因为DialogFragment本身是Fragment,那么意味着在FragmentManager中只能添加一次,那么如果连续调用show方法则会报错,所以在显示之前可以调用isAdd()方法判断是否重复添加
  2. setStyle方法需要在OnCreate方法中调用
    setStyle设置Dialog的style和theme两个属性,而style在onGetLayoutInflater方法中使用,所以必须在onCreateView之前设置好style的值。
  3. DialogFragment、FragmentManager、FragmentTransaction最好使用v4包中的,兼容性好

四、如何封装一下,使用起来更方便?

通常情况下,我们为了处理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() {

    }
}

你可能感兴趣的:(Android日常总结)