志不强者智不达,言不信者行不果。这篇文章主要讲述Android为啥推荐用DialogFragment创建Dialog?相关的知识,希望能为你提供帮助。
前言:
这段时候有点忙,
因为在赶项目,
说忙也都是在敷衍,
时间挤挤还是有的,
从开始写博客的那一刻起就应该一直坚持下来,
不要三天打鱼两天晒网,
上次写过一个Android进阶之(
dialog详解一)
,
今天继续上次的内容探究一下,
从android3.0后Android引入的DailogFragment,
我们一起从源码的角度去看看为啥谷歌推荐这样创建Dialog.
DialogFragment对话框出现的意义
为什么android系统有AlertDialog,
PopupWindow对话框,
基本满足客户需求,
为啥还要跑出一个DialogFragment对话框呢?
这就要从DialogFragment的优点说起了:
有和Fragment基本一致的生命周期,
因此便于Activity更好的控制管理DialogFragment。 随屏幕旋转(
横竖屏幕切换)
DialogFragment对话框随之自动调整对话框大小。而AlertDialog和PopupWindow随屏幕切换而消失。 DialogFragment的出现解决 横竖屏幕切换Dialog消失的问题。
既然是探究性的话题,
我们就从DialogFragment源码探究一下。
在此次之前先贴一张Fragment的生命周期图:
文章图片
DialogFragment的全部源码我就不贴了, 要研究的童鞋就多按按Ctrl+ 左键吧(^__^) 嘻嘻……我们按照Fragment的生命周期一个一个往下看, 揭开它的神秘面纱。
首先看看onAttach( ) 方法
@
Override
public void onAttach(Context context) {
super.onAttach(context);
if (!mShownByMe) {
// 如果不是通过调用DialogFragment自己的show方法弹出Dialog的话
// 标志着这个Dialog不再被消失
mDismissed =
false;
}
}
这个方法也没什么重要代码。
接下来看看onCreate( ) 方法
@
Override
public void onCreate(@
Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mShowsDialog =
mContainerId =
=
0;
if (savedInstanceState !=
null) {
mStyle =
savedInstanceState.getInt(SAVED_STYLE, STYLE_NORMAL);
mTheme =
savedInstanceState.getInt(SAVED_THEME, 0);
mCancelable =
savedInstanceState.getBoolean(SAVED_CANCELABLE, true);
mShowsDialog =
savedInstanceState.getBoolean(SAVED_SHOWS_DIALOG, mShowsDialog);
mBackStackId =
savedInstanceState.getInt(SAVED_BACK_STACK_ID, -1);
}
}
当Activity生命周期发生变换的时候, 也就是比如切换横竖屏的时候, 对Dialog的一些属性进行保存, 当再次创建Fragment的时候回到上一次Dialog的状态, 既然有取的过程, 那必定有存的过程。
@
Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (mDialog !=
null) {
Bundle dialogState =
mDialog.onSaveInstanceState();
if (dialogState !=
null) {
outState.putBundle(SAVED_DIALOG_STATE_TAG, dialogState);
}
}
if (mStyle !=
STYLE_NORMAL) {
outState.putInt(SAVED_STYLE, mStyle);
}
if (mTheme !=
0) {
outState.putInt(SAVED_THEME, mTheme);
}
if (!mCancelable) {
outState.putBoolean(SAVED_CANCELABLE, mCancelable);
}
if (!mShowsDialog) {
outState.putBoolean(SAVED_SHOWS_DIALOG, mShowsDialog);
}
if (mBackStackId !=
-1) {
outState.putInt(SAVED_BACK_STACK_ID, mBackStackId);
}
}
紧接着我们看到这么一个方法:
/** @
hide */
@
Override
public LayoutInflater getLayoutInflater(Bundle savedInstanceState) {
if (!mShowsDialog) {
return super.getLayoutInflater(savedInstanceState);
}mDialog =
onCreateDialog(savedInstanceState);
if (mDialog !=
null) {
setupDialog(mDialog, mStyle);
return (LayoutInflater) mDialog.getContext().getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
}
return (LayoutInflater) mHost.getContext().getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
}
这个方法虽然隐藏了, 但是我们看到, 在方法里面new了一个Dialog, onCreateDialog, 所以我们知道, 其实DialogFragment里面也是悄悄的创建了一个Dialog,创建了一个Dialog, 那系统怎么知道我们创建的Dialog的样式呢? 总要一些Dialog的初始化吧?
带着疑问, 接着我们看看onActivityCreated这个方法:
@
Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (!mShowsDialog) {
return;
}View view =
getView();
if (view !=
null) {
if (view.getParent() !=
null) {
throw new IllegalStateException(
"
DialogFragment can not be attached to a container view"
);
}
mDialog.setContentView(view);
}
final Activity activity =
getActivity();
if (activity !=
null) {
mDialog.setOwnerActivity(activity);
}
mDialog.setCancelable(mCancelable);
mDialog.setOnCancelListener(this);
mDialog.setOnDismissListener(this);
if (savedInstanceState !=
null) {
Bundle dialogState =
savedInstanceState.getBundle(SAVED_DIALOG_STATE_TAG);
if (dialogState !=
null) {
mDialog.onRestoreInstanceState(dialogState);
}
}
}
我们看到有这么一段代码:
View view =
getView();
if (view !=
null) {
if (view.getParent() !=
null) {
throw new IllegalStateException(
"
DialogFragment can not be attached to a container view"
);
}
mDialog.setContentView(view);
}
看到这个我们很熟悉, 就是给Dialog设置了一个布局文件, 那么这个布局又是哪来的呢? View view = getView(); , 这里的getView获取到的值, 正是我们在onCreateView中return的那个view。那么我们在onCreateView中返回了什么, 我们Dialog就会显示啥样。
看到这里我们一定有点思路了, 如果要用DialogFragment来弹出一个Dialog, 有两种方法。
一: 重写createDialog方法, 给一个自己的Dialog
@
NonNull
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new Dialog(getActivity(), getTheme());
}
二: 重写Fragment的onCreateView方法, 返回一个自定义个布局
@
Nullable
public View onCreateView(LayoutInflater inflater, @
Nullable ViewGroup container,
@
Nullable Bundle savedInstanceState) {
return null;
}
好了, 现在Dialog创建好了, 也初始化过了, 那么怎么显示Dialog呢?
机智的你肯定有思路了,
一: 调用DialogFragment的getDialog方法然后show
public Dialog getDialog() {
return mDialog;
}
二: 调用DialogFragment的show方法
public void show(FragmentManager manager, String tag)public int show(FragmentTransaction transaction, String tag)
但是这只是把一个没有UI的Fragment添加到了Activity的Fragment栈中, Dialog又是哪弹出来的呢?
我们接着Fragment的生命周期看看onStart方法, 这个方法也就是当Activity可见的时候调用。
@
Override
public void onStart() {
super.onStart();
if (mDialog !=
null) {
mViewDestroyed =
false;
mDialog.show();
}
}
我们很清晰的看到了mDialog.show(); 此时Dialog对用户可见。
再延伸一下, 我们可以看到这里有一个 if (mDialog != null)的判断, 那么什么时候mDialog= = null? 我们看看它在哪创建的:
/** @
hide */
@
Override
public LayoutInflater getLayoutInflater(Bundle savedInstanceState) {
if (!mShowsDialog) {
return super.getLayoutInflater(savedInstanceState);
}mDialog =
onCreateDialog(savedInstanceState);
if (mDialog !=
null) {
setupDialog(mDialog, mStyle);
return (LayoutInflater) mDialog.getContext().getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
}
return (LayoutInflater) mHost.getContext().getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
}
也就是当mShowsDialog为false的时候, 我们的dialog就是null了。
我们再onCreate方法中看到这么一行代码:
mShowsDialog =
mContainerId =
=
0;
那么这个mContainerId 又是什么呢?
// When a fragment is being dynamically added to the view hierarchy, this
// is the identifier of the parent container it is being added to.
英语不是很好, 大致翻译一下, 此id就是你需要将Fragment添加的那个容器, 也就是跟我们平时使用的普通的Fragment一样, 当需要把一个Fragment添加到Activity的时候, 调用
FragmentTransaction ft =
manager.beginTransaction();
ft.add(R.id.container, tag);
到这, 我们终于把DialogFragment的源码跟着Fragment的生命周期跑了一遍, 总结一下:
DialogFragment也就是在内部new了一个Dialog然后显示在Activity上, 但是有了Fragment的附属后, 我们有了一套完整的生命周期了, Dialog依附Fragment从而当Activty生命周期发生变换的时候, 会重新把Fragment添加进Activity中, 从而不会使Dialog消失。
说了这么多原理性的东西, 我们都有点疲惫了, 接下来让我们来实战一下~~~~
为了展示效果, 我们还是模仿一下微信的Dialog吧, 先看看效果
文章图片
普通的创建Dialog的方法, 我在Android进阶之( dialog详解一) , 已经有实现了, 如果有需要的朋友可以直接去拖代码, (^__^) 嘻嘻……
方式一: 重写DialogFragment的onCreateView
wechat_dialog.xml:
<
?xml version=
"
1.0"
encoding=
"
utf-8"
?>
<
RelativeLayout xmlns:android=
"
http://schemas.android.com/apk/res/android"
android:id=
"
@
+
id/parentPanel"
android:layout_width=
"
match_parent"
android:layout_height=
"
wrap_content"
>
<
RelativeLayout
android:id=
"
@
+
id/body"
android:layout_width=
"
match_parent"
android:layout_height=
"
wrap_content"
android:layout_centerInParent=
"
true"
android:padding=
"
10dp"
>
<
ProgressBar
android:id=
"
@
+
id/progress"
android:layout_width=
"
50dp"
android:layout_height=
"
50dp"
android:layout_marginRight=
"
12dip"
android:indeterminate=
"
false"
android:indeterminateDrawable=
"
@
drawable/shape_ring"
/>
<
TextView
android:id=
"
@
+
id/customFrameMsg"
android:layout_width=
"
wrap_content"
android:layout_height=
"
wrap_content"
android:layout_centerVertical=
"
true"
android:layout_toRightOf=
"
@
id/progress"
android:singleLine=
"
true"
android:text=
"
正在登录..."
android:textColor=
"
#ffffff"
android:textSize=
"
14.5sp"
/>
<
/RelativeLayout>
<
/RelativeLayout>
布局很简单, 我们定义一个Fragment叫WechatFragment
package com.cisetech.dialogdemo.dialog;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.DialogFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.cisetech.dialogdemo.R;
/**
* author:
yinqingy
* date:
2016-11-01 22:20
* blog:
http://blog.csdn.net/vv_bug
* desc:
*/public class WechatFragment extends DialogFragment {
@
Nullable
@
Override
public View onCreateView(LayoutInflater inflater, @
Nullable ViewGroup container, @
Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.wechat_dialog,null);
}
}
我们创建Fragment然后调用show代码:
public void showWechatDialog(){
WechatFragment wechatDialog=
new WechatFragment();
wechatDialog.show(getSupportFragmentManager(),"
wechatDialog"
);
}
然后我们运行看看效果:
文章图片
好吧, Dialog是显示出来了, 出来的是这吊样, 跟我们想要的结果还很远啊,
1、去掉Dialog默认的Title位置, 然后背景设成自己的透明色。
public void showWechatDialog(){
WechatFragment wechatDialog=
new WechatFragment();
wechatDialog.setStyle(DialogFragment.STYLE_NO_TITLE,R.style.loading_dialog);
wechatDialog.show(getSupportFragmentManager(),"
wechatDialog"
);
}
Dialog的style文件:
<
!-- 自定义loading dialog -->
<
style name=
"
loading_dialog"
parent=
"
android:style/Theme.Dialog"
>
<
!--提示框是否有边框-->
<
item name=
"
android:windowFrame"
>
@
null<
/item>
<
!--是否需要标题-->
<
item name=
"
android:windowNoTitle"
>
true<
/item>
<
!--对话框的背景-->
<
item name=
"
android:windowBackground"
>
@
drawable/pd_shape<
/item>
<
!--对话框是否悬浮-->
<
item name=
"
android:windowIsFloating"
>
true<
/item>
<
!--默认Window的content背景-->
<
item name=
"
android:windowContentOverlay"
>
@
null<
/item>
<
!--dialog遮罩透明度-->
<
item name=
"
android:backgroundDimAmount"
>
0.5<
/item>
<
!--是否需要dialog遮罩-->
<
item name=
"
android:backgroundDimEnabled"
>
true<
/item>
<
/style>
shape文件:
<
?xml version=
"
1.0"
encoding=
"
utf-8"
?>
<
shape xmlns:android=
"
http://schemas.android.com/apk/res/android"
>
<
corners android:radius=
"
8dp"
/>
<
solid android:color=
"
#7f000000"
/>
<
/shape>
然后我们再运行下代码:
文章图片
好吧, 经过我们的修改我们终于离我们的目标就差一点了, 就是Dialog的宽度了, 我们让Dialog的宽度为屏幕宽的0.618, (^__^) 嘻嘻……
重写onStart( ) 方法, 在dialog弹出之后进行修改宽度属性
@
Override
public void onStart() {
super.onStart();
Dialog dialog =
getDialog();
//获取Dialog
WindowManager.LayoutParams attr =
dialog.getWindow().getAttributes();
//获取Dialog属性
WindowManager wm=
(WindowManager) dialog.getContext().getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics outMetric=
new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(outMetric);
attr.width=
(int) (outMetric.widthPixels*0.618f);
dialog.getWindow().setAttributes(attr);
}
再次运行代码:
文章图片
可以看到我们已经成功达到了我们的目标, 还没完。。。
我们转换屏幕看看什么效果:
文章图片
可以看到, 效果是一致的。(^__^) 嘻嘻……
我们使用直接new Dialog的形式, 然后切换横屏试试:
效果还是一致, 但是系统报了一个错误
android.view.WindowLeaked: Activity com.cisetech.dialogdemo.MainActivity has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView{230312c5 V.E..... R....... 0,0-791,140} that was originally added here
at android.view.ViewRootImpl.<
init>
(ViewRootImpl.java:363)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:265)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:69)
at android.app.Dialog.show(Dialog.java:298)
at com.cisetech.dialogdemo.MainActivity.onCreate(MainActivity.java:94)
at android.app.Activity.performCreate(Activity.java:5941)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1105)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2256)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2363)
at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:3915)
at android.app.ActivityThread.access$900(ActivityThread.java:147)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1289)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5235)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:906)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:701)
虽然效果一样, 但是如果我们Dialog是一个输入用户名密码的Dialog, 那么当切换屏幕的时候, 用直接new Dialog的话会被清空掉, 如果是DialogFragment的话则不会被清空。
方式二, 重写onCreateDialog实现DialogFragment弹出Dialog
MainActivity.java
private WechatFragment wechatDialog;
public void showWechatDialog(){
if(wechatDialog!=
null&
&
wechatDialog.getDialog()!=
null&
&
wechatDialog.getDialog().isShowing()){
wechatDialog.dismiss();
return;
}
wechatDialog=
new WechatFragment();
// wechatDialog.setStyle(DialogFragment.STYLE_NO_TITLE,R.style.loading_dialog);
wechatDialog.show(getSupportFragmentManager(),"
wechatDialog"
);
}
WechatFragment.java:
@
NonNull
@
Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
LayoutInflater inflater =
LayoutInflater.from(getActivity());
View v =
inflater.inflate(R.layout.wechat_dialog, null);
// 得到加载view
Dialog loadingDialog =
new Dialog(getActivity(), R.style.loading_dialog);
// 创建自定义样式dialog
loadingDialog.setCancelable(true);
// 不可以用“返回键”取消
loadingDialog.setContentView(v);
// 设置布局
return loadingDialog;
}
运行效果:
文章图片
和我们上面那种重写onCreateView的方式效果一样。
【Android为啥推荐用DialogFragment创建Dialog?】到此~ DialogFragment的基本内容已经完毕了, 以前看到一些大牛封装的DialogUtil, 接下来还会写一篇关于这个工具类的博客, 未完待续, (^__^) 嘻嘻……
推荐阅读
- Android_JNI
- 跟着Innost理解下Android控件(ViewRoot)系统
- 我的Android进阶之旅如何在浏览器上使用Octotree插件树形地展示Github项目代码()
- Android之线程池深度剖析
- Android开发5(应用程序窗口小部件App Widgets的实现)
- Android Context getSystemService分析
- Android View的事件分发机制
- Android 升级到android studio 2.2项目死活run不起来
- React Native Android生成已签名的APK