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()");
主界面布局:
<?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都能保持原样。
以上是关于DialogFragment: DialogFragment的一些理解的主要内容,如果未能解决你的问题,请参考以下文章
Android:DialogFragment.dismissInternal 处 DialogFragment.dismissAllow 处的 NullPointerException