Android 自3.0版本引入了DialogFragment这个类,并推荐开发者使用这个类替代之前经常使用的Dialog类,那么DialogFragment相对于之前的Dialog究竟有什么优势呢?这个DialogFragment又该如何使用呢?今天总结一下:

一. 与传统的Dialog类的对比

1.更完善的生命周期管理

之前创建的Dialog的方式如下:

static class MyDialog extends Dialog {

        private String TAG = "xp.chen-Dialog";

        public MyDialog(@NonNull Context context)
        {
            super(context);
            setContentView(R.layout.dialog_normal);
        }

        @Override
        protected void onCreate(Bundle savedInstanceState)
        {
            super.onCreate(savedInstanceState);
            Log.i(TAG, "onCreate: MyDialog->onCreate()");
        }

        @Override
        protected void onStart()
        {
            super.onStart();
            Log.i(TAG, "onStart: MyDialog->onStart()");
        }

        @Override
        protected void onStop()
        {
            super.onStop();
            Log.i(TAG, "onStop: MyDialog->onStop()");
        }

        @Override
        public void cancel() {
            super.cancel();
            Log.i(TAG, "cancel: MyDialog->cancel()");
        }

        @Override
        public void dismiss()
        {
            super.dismiss();
            Log.i(TAG, "dismiss: MyDialog->dismiss()");
        }
    }

使用时:

 /**
     * Show a normal dialog use Dialog API.
     */
    private void showNormalDialog() {
        mNormalDialog = new MyDialog(DialogFragmentApiUseDemoActivity.this);
        mNormalDialog.show();
    }

这样创建本来没什么问题,但是如果这个时候屏幕方向发生变化,就会导致Activity重建,然后之前显示的对话框就消失了,Log上也会报如下错误:

2019-09-25 14:58:29.996 24394-24394/com.yongdaimi.android.androidapitest E/WindowManager: android.view.WindowLeaked: Activity com.yongdaimi.android.androidapitest.DialogFragmentApiUseDemoActivity has leaked window DecorView@1fa30b4[DialogFragmentApiUseDemoActivity] that was originally added here
        at android.view.ViewRootImpl.<init>(ViewRootImpl.java:622)
        at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:391)
        at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:129)
        at android.app.Dialog.show(Dialog.java:471)
        at com.yongdaimi.android.androidapitest.DialogFragmentApiUseDemoActivity.showNormalDialog(DialogFragmentApiUseDemoActivity.java:129)
        at com.yongdaimi.android.androidapitest.DialogFragmentApiUseDemoActivity.onClick(DialogFragmentApiUseDemoActivity.java:141)
        at android.view.View.performClick(View.java:6648)
        at android.view.View.performClickInternal(View.java:6620)
        at android.view.View.access$3100(View.java:787)
        at android.view.View$PerformClick.run(View.java:26167)
        at android.os.Handler.handleCallback(Handler.java:891)
        at android.os.Handler.dispatchMessage(Handler.java:102)
        at android.os.Looper.loop(Looper.java:207)
        at android.app.ActivityThread.main(ActivityThread.java:7539)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:524)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:958)

虽说并不影响使用,程序也不会崩溃,但至少说明这里是有问题的,解决这个问题的办法也很简单,在Activity的onDestory方法中主动关闭:

@Override
    protected void onDestroy() {
        super.onDestroy();
        if (mNormalDialog != null){
            mNormalDialog.cancel();
        }
        Log.e(TAG,"onDestroy");
    }

而且如果想在屏幕切换后仍然显示Dialog的话,可以采用如下方法:

在onSaveInstanceState()方法中进行状态的保存:

    @Override
    protected void onSaveInstanceState(Bundle outState)
    {
        super.onSaveInstanceState(outState);
        Log.i(TAG, "onSaveInstanceState: ");
        if (mNormalDialog != null && mNormalDialog.isShowing()) {
            outState.putBoolean("DIALOG_SHOWN", true);
        }
    }

在onCreate()方法对其进行恢复:

@Override
    protected void onCreate(@Nullable Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_dialog_fragment_api_use_demo);
        initView();
        Log.i(TAG, "onCreate: ");

        if (savedInstanceState != null) {
            boolean is_shown = savedInstanceState.getBoolean("DIALOG_SHOWN");
            if (is_shown) {
                showNormalDialog();
            }
        } else {
            Toast.makeText(this, "savedInstanceState is NULL", Toast.LENGTH_SHORT).show();
        }
    }

这样就既解决了上面的异常也处理了屏幕方向切换后Dialog消失的问题。但是个人觉得这样很不合理,屏幕旋转是很正常的操作,旋转前旋转后保持一样的界面UI是很正常的事情,要是每次涉及到屏幕旋转都让我做一遍上面的操作,那真的让人抓狂。而且,如果在Activity的onDestory()方法里销毁了Dialog还好,万一忘记销毁了,Dialog里面又有一些复杂操作,还有可能造成内存泄露,所以没办法自动管理Dialog的生命周期是传统Dialog的第一个缺陷。

2. 更合理的功能划分

如果是弹出一个简单的确认取消的对话框,可能直接就在Activity里使用以下方式:

new AlertDialog.Builder(GuideActivity.this).setTitle("用户申明")
        .setMessage(getResources().getString(R.string.statement))
        .setPositiveButton("我同意", new positiveListener())
        .setNegativeButton("不同意", new negativeListener())
        .setCancelable(false)
        .show();
    }private class positiveListener implements DialogInterface.OnClickListener {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            prefs.setIsFirstTime(false);
        }
    }
    
    private class negativeListener implements DialogInterface.OnClickListener {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            Util.virtualHome(GuideActivity.this);
        }
    }

这倒也没什么不对,对话框也能正常显示 ,可问题是“Activity知道太多了”,你点击对话框上的按钮,那是对话框本身的事情,对话框本身的事情对话框自己知道就好了,Activity没必要知道,上面的onClick()方法里的代码量还算少,多了的话,简直惨不忍睹。

二. DialogFragment的使用

使用上并没有什么特别值得注意的地方,大致和Fragment的使用差不多。以前是在onCreateView()方法里写Fragment的界面,现在在这个方法里写Dialog的界面。

@Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)
    {
        View view = inflater.inflate(R.layout.dialog_fragment_use_demo, container, false);
        return view;
    }

另外它还新提供了一个onCreateDialog()的方法,我们可以直接在这个方法里创建传统的Dialog,然后直接返回,很方便。

 @NonNull
    @Override
    public Dialog onCreateDialog(@Nullable Bundle savedInstanceState)
    {
        return super.onCreateDialog(savedInstanceState);
    }

显示的话,以前的Dialog是调用show()方法显示的,现在同样是调用show()方法显示,只不过参数有点不同:

/**
     * Show a normal dialog use Dialog API.
     */
    private void showNormalDialog() {
        mNormalDialog = new MyDialog(DialogFragmentApiUseDemoActivity.this);
        mNormalDialog.show();
    }

    private void showDialogFragment() {
        MyDialogFragment dialogFragment = new MyDialogFragment();
        dialogFragment.show(getSupportFragmentManager(), "dialogFragment");
    }

我这里分别在代码中使用Dialog和DialogFragment创建了两个对话框,然后在横竖屏切换的时候分别比较两个对话框的状态,并用DialogFragment实现了一个类似iOS上UIActionSheet的效果,代码和效果图如下:

主界面

package com.yongdaimi.android.androidapitest;

import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import com.yongdaimi.android.androidapitest.view.MyDialogFragment;

public class DialogFragmentApiUseDemoActivity extends AppCompatActivity implements View.OnClickListener
{


    private static final String TAG = "xp.chen";

    private Button btn_show_dialog_fragment;
    private Button btn_show_normal_dialog;


    private MyDialog mNormalDialog;


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_dialog_fragment_api_use_demo);
        initView();
        Log.i(TAG, "onCreate: ");
        /*if (savedInstanceState != null) {
            boolean is_shown = savedInstanceState.getBoolean("DIALOG_SHOWN");
            if (is_shown) {
                showNormalDialog();
            }
        } else {
            Toast.makeText(this, "savedInstanceState is NULL", Toast.LENGTH_SHORT).show();
        }*/
    }


    @Override
    protected void onSaveInstanceState(Bundle outState)
    {
        super.onSaveInstanceState(outState);
        Log.i(TAG, "onSaveInstanceState: ");
        /*if (mNormalDialog != null && mNormalDialog.isShowing()) {
            outState.putBoolean("DIALOG_SHOWN", true);
        }*/
    }


    @Override
    protected void onStart()
    {
        super.onStart();
        Log.i(TAG, "onStart: ");
    }


    @Override
    protected void onResume()
    {
        super.onResume();
        Log.i(TAG, "onResume: ");
    }


    @Override
    protected void onPause()
    {
        super.onPause();
        Log.i(TAG, "onPause: ");
    }


    @Override
    protected void onStop()
    {
        super.onStop();
        Log.i(TAG, "onStop: ");
    }


    @Override
    protected void onDestroy()
    {
        super.onDestroy();
        Log.i(TAG, "onDestroy: ");
    }


    private void initView()
    {
        btn_show_dialog_fragment = findViewById(R.id.btn_show_dialog_fragment);
        btn_show_dialog_fragment.setOnClickListener(this);

        btn_show_normal_dialog = findViewById(R.id.btn_show_normal_dialog);
        btn_show_normal_dialog.setOnClickListener(this);
    }

    /**
     * Show a normal dialog use Dialog API.
     */
    private void showNormalDialog() {
        mNormalDialog = new MyDialog(DialogFragmentApiUseDemoActivity.this);
        mNormalDialog.show();
    }

    private void showDialogFragment() {
        MyDialogFragment dialogFragment = new MyDialogFragment();
        dialogFragment.show(getSupportFragmentManager(), "dialogFragment");
    }


    @Override
    public void onClick(View v)
    {
        switch (v.getId()) {
            case R.id.btn_show_dialog_fragment:
                showDialogFragment();
                break;
            case R.id.btn_show_normal_dialog:
                showNormalDialog();
                break;
            default:
                break;
        }
    }


    static class MyDialog extends Dialog {

        private String TAG = "xp.chen-Dialog";

        public MyDialog(@NonNull Context context)
        {
            super(context);
            setContentView(R.layout.dialog_normal);
        }

        @Override
        protected void onCreate(Bundle savedInstanceState)
        {
            super.onCreate(savedInstanceState);
            Log.i(TAG, "onCreate: MyDialog->onCreate()");
        }

        @Override
        protected void onStart()
        {
            super.onStart();
            Log.i(TAG, "onStart: MyDialog->onStart()");
        }

        @Override
        protected void onStop()
        {
            super.onStop();
            Log.i(TAG, "onStop: MyDialog->onStop()");
        }

        @Override
        public void cancel() {
            super.cancel();
            Log.i(TAG, "cancel: MyDialog->cancel()");
        }

        @Override
        public void dismiss()
        {
            super.dismiss();
            Log.i(TAG, "dismiss: MyDialog->dismiss()");
        }
    }


}
DialogFragmentApiUseDemoActivity.java

相关文章: