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()"); } } }
主界面布局:
<?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>
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: ");
}
}
对话框布局:
<?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>
最终效果:
从图上也能很明显看出,不管屏幕如何切换,使用DialogFragment创建出来的Dialog都能保持原样。