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都能保持原樣。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM