DialogFragment: DialogFragment的一些理解

Posted yongdaimi

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了DialogFragment: DialogFragment的一些理解相关的知识,希望对你有一定的参考价值。

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

主界面布局:

技术图片
<?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:orientation="vertical"
    >

    <Button
        android:id="@+id/btn_show_normal_dialog"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Show Normal Dialog"
        android:textAllCaps="false"
        />


    <Button
        android:id="@+id/btn_show_dialog_fragment"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Show Dialog Fragment"
        android:textAllCaps="false"
        />


</LinearLayout>
activity_dialog_fragment_api_use_demo.xml

DialogFragment的对话框:

技术图片
package com.yongdaimi.android.androidapitest.view;

import android.app.Dialog;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.DialogFragment;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;

import com.yongdaimi.android.androidapitest.R;

public class MyDialogFragment extends DialogFragment


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

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState)
    
        super.onCreate(savedInstanceState);
        Log.i(TAG, "onCreate: ");
        // setStyle(DialogFragment.STYLE_NO_TITLE, R.style.DialogFullScreen);
    

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)
    
        Log.i(TAG, "onCreateView: ");
        //去掉dialog的标题,需要在setContentView()之前
        this.getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE);
        View view = inflater.inflate(R.layout.dialog_fragment_use_demo, container, false);
        return view;
    


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

    @Override
    public void onStart()
    
        super.onStart();
        Window dialogWindow = getDialog().getWindow();
        if (dialogWindow != null) 
            dialogWindow.getDecorView().setPadding(0, 0, 0, 0);
            dialogWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
            WindowManager.LayoutParams lp = dialogWindow.getAttributes();
            lp.width = WindowManager.LayoutParams.MATCH_PARENT;
            lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
            lp.gravity = Gravity.BOTTOM;
            lp.windowAnimations = android.R.style.Animation_InputMethod;
            dialogWindow.setAttributes(lp);
        
    


    @Override
    public void onDetach()
    
        super.onDetach();
        Log.i(TAG, "onDetach: ");
    


    @Override
    public void onDestroy()
    
        super.onDestroy();
        Log.i(TAG, "onDestroy: ");
    

MyDialogFragment.java

对话框布局:

技术图片
<?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="wrap_content"
    android:background="#F0F0F0"
    android:orientation="vertical">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/white"
        android:gravity="center"
        android:minHeight="50dip"
        android:text="请选择指定的类型"
        android:textColor="#CCC" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="1dip"
        android:background="@drawable/btn_action_sheet_item_selector"
        android:gravity="center"
        android:minHeight="44dip"
        android:text="ActionSheet 1"
        android:textAllCaps="false"
        android:textColor="#333"
        style="?android:attr/borderlessButtonStyle"
        />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="1dip"
        android:background="@drawable/btn_action_sheet_item_selector"
        android:gravity="center"
        android:minHeight="44dip"
        android:text="ActionSheet 2"
        android:textAllCaps="false"
        android:textColor="#333"
        style="?android:attr/borderlessButtonStyle"
        />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="1dip"
        android:background="@drawable/btn_action_sheet_item_selector"
        android:gravity="center"
        android:minHeight="44dip"
        android:text="ActionSheet 3"
        android:textAllCaps="false"
        android:textColor="#333"
        style="?android:attr/borderlessButtonStyle"
        />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="6dip"
        android:background="@drawable/btn_action_sheet_item_selector"
        android:gravity="center"
        android:minHeight="44dip"
        android:text="取消"
        android:textAllCaps="false"
        android:textColor="@android:color/holo_red_light"
        style="?android:attr/borderlessButtonStyle"
        />


</LinearLayout>
dialog_fragment_use_demo.xml

最终效果:

技术图片

 

从图上也能很明显看出,不管屏幕如何切换,使用DialogFragment创建出来的Dialog都能保持原样。

 

以上是关于DialogFragment: DialogFragment的一些理解的主要内容,如果未能解决你的问题,请参考以下文章

Activity与DialogFragment交互的方法

Dialogfragment 未清除

Android:DialogFragment.dismissInternal 处 DialogFragment.dismissAllow 处的 NullPointerException

DialogFragment.getDialog 返回 null

为啥要使用 DialogFragment?

从 DialogFragment 回调片段