请尊重他人劳动成果,请勿随意剽窃,转载请注明,谢谢!转载请注明出处:http://blog.csdn.net/evan_man/article/details/51812678
DialogFrament是一类特定的Fragment,会将视图绘制在Activity视图的上方。一般使用场景就是展示一个警示对话框,确认对话框。使用DialogFragment而不是直接使用Dialog是一种比较推荐的方式。如果直接使用Dialog那么Dialog的生命周期是需要我们自己手动去管理的,而对于DialogFragment,它将自身交给FragmentManager进行管理,与Activity生命周期一致。比如当Activity销毁时,此时如果存在对话框,那么系统会自动销毁该对话框。而如果使用Dialog,很可能Activity已经销毁而Dialog依然存在,造成系统错误,程序出现异常。本文的大体脉络和之前博客一致,首先介绍DialogFragment如何在应用中使用,最后分析DialogFragment是如何一步一步的被绘制的屏幕上面的。
简单使用
一、重写一个类继承自android.support.v4.app.DialogFragment
对于Dialog准备采用自定义布局的需要重写onCreateView、onViewCreated方法
@Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.dialog_fragment_profile, container); } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); //view中的控件进行一系列初始化 }
对于准备才用Android那几种内置的Dialog的需要只要重写onCreateDialog方法
@NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { String title = getArguments().getString("title"); //之前通过setArguments传入的参数 AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getActivity()); alertDialogBuilder.setTitle(title); alertDialogBuilder.setMessage("Are you sure?"); alertDialogBuilder.setPositiveButton("OK", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // on success } }); alertDialogBuilder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); return alertDialogBuilder.create(); }
二、定义DialogFragment的样式
DialogFragment是动态创建的,不是在xml布局文件中定义的。因此它的样式基本上都是通过所属Context的theme来指定的。下面是一个简单范例,内容出现在工程项目中的style.xml文件中。
<style name="AppTheme" parent="Theme.AppCompat.Light"> <!-- Apply default style for dialogs --> <item name="android:dialogTheme">@style/AppDialogTheme</item> </style> <style name="EvanBaseDialogTheme" parent="Theme.AppCompat.Light.Dialog"> <!--此处的值也控制ActionBar背景--> <item name="colorPrimary">@color/colorPrimary</item> <!--此处的值也控制ActionBar上面显示电量、信号那行视图的背景--> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <!--控制比如editText被选中状态下下面那条线的颜色--> <item name="colorAccent">@color/red</item> <!--控制比如editText中长按选中的那部分文字的颜色,一般对其进行复制粘贴操作--> <item name="android:textColorHighlight">@color/purple</item> <!--控制比如editText正常状态下下面那条线的颜色--> <item name="colorControlNormal">@color/white</item> <!-- Define window properties as desired --> <item name="android:windowNoTitle">false</item> <item name="android:windowTitleStyle">@style/EvanBaseDialogWindowTitle</item> <item name="android:windowFullscreen">false</item> <item name="android:windowIsFloating">true</item> <item name="android:windowCloseOnTouchOutside">true</item> <!--此处设置为真则背景是灰色,没有阴影效果--> <item name="android:windowIsTranslucent">true</item> <!--整个对话框的背景颜色--> <item name="android:windowBackground">@drawable/white_corners_background</item> </style> <style name="EvanBaseDialogWindowTitle" parent="Base.DialogWindowTitle.AppCompat"> <item name="android:layout_gravity">center</item> <item name="android:gravity">center</item> <!--整个title部分的背景颜色--> <item name="android:background">@color/white</item> <!--title文字显示的样子 大小等--> <item name="android:textAppearance">@style/EvanBaseDialogWindowTitleText</item> </style> <style name="EvanBaseDialogWindowTitleText" parent="@android:style/TextAppearance.DialogWindowTitle"> <item name="android:textSize">@dimen/big_textSize</item> </style>
三、Activity中创建DialogFragment对象,并显示出来
FragmentManager fm = mActivity.getSupportFragmentManager(); MyDialogFragment mDialog = MyDialogFragment.newInstance("Some title"); //DialogFragment的创建一般都是通过getInstance方法创建。 mDialog.show(fm, "fragment_tag");
四、其它DialogFragment的简单使用
AlertDialog:可以修改的内容有一个标题、一个Message、三个Button。其它使用与前面介绍的一致。
ProgressDialog:可以修改的内容有一个标题、一个Message、Progress样式。Reference:http://www.quicktips.in/show-progressdialog-android/
ProgressDialog pd = new ProgressDialog(context); pd.setTitle("Loading..."); pd.setMessage("Please wait."); pd.setCancelable(false); pd.show(); or pd.dismiss();
更多DialogFragment用法参考:http://guides.codepath.com/android/Using-DialogFragment
深入分析
分析目的在于了解DialogFragment视图如何动态添加View到屏幕上,跟PopupWindows一样?我们从android.support.v4.app.DialogFragment的show方法入手。
DialogFragment.class
public class DialogFragment extends Fragment
show()@DialogFragment.class
public void show(FragmentManager manager, String tag) { mDismissed = false; mShownByMe = true; FragmentTransaction ft = manager.beginTransaction(); ft.add(this, tag); //note1 ft.commit(); }
1、添加的Fragment是没有containerID的。onCreateView方法的参数ViewGroup container为null,表明onCreateView所创建出来的View显示到所属Activity的布局中的某个View中。既然如此,那肯定是DialogFrament通过重写父类Fragment的生命周期中的某些方法,在方法内部动态向手机屏幕上显示对话框视图,往下我们就对Fragment中的方法进行说明。对于Fragment的生命周期等感兴趣的可以参考本人另外一篇博客:http://blog.csdn.net/evan_man/article/details/51329320
简单回顾一下Fragment的生命周期先后调用的方法有:onAttach、onCreate、onCreateView、onViewCreated、onActivityCreated、onStart、onResume、onPause 、onStop、onDestroyView、onDestory、
onDetach。
下面我们依次来分析一下DialogFrament的这些方法。
常用域以及其初始化@DialogFrament.class
public static final int STYLE_NORMAL = 0; public static final int STYLE_NO_TITLE = 1; public static final int STYLE_NO_FRAME = 2; public static final int STYLE_NO_INPUT = 3; int mStyle = STYLE_NORMAL; int mTheme = 0; boolean mCancelable = true; boolean mShowsDialog = true; int mBackStackId = -1; Dialog mDialog; //Dialog显示的关键 boolean mViewDestroyed; boolean mDismissed; boolean mShownByMe;
onAttach方法执行之前正常情况下会先执行DialogFragment的show方法,而该方法会执行如下了内容:mDismissed = false; mShownByMe = true;
onAttach()@DialogFrament.class
@Override public void onAttach(Activity activity) { super.onAttach(activity); if (!mShownByMe) { //正常情况不会跳转到这里 mDismissed = false; } }
onCreate()@DialogFrament.class
@Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); mShowsDialog = mContainerId == 0; //note1 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); } }
1、正常情况这里mShowsDialog为真,mContainerId属性来自于Fragment值为0
往下就要分析onCreateView方法,但是根据博客http://blog.csdn.net/evan_man/article/details/51329320的分析,在onCreateView方法的第一个参数(LayoutInflater
inflater)通过调用Fragment的getLayoutInflater()方法获得,因此执行顺序是先getLayoutInflater()之后再onCreateView方法。DialogFragment重写了Fragment的getLayoutInflater()方法没有重写onCreateView方法。
getLayoutInflater()@DialogFrament.class
public LayoutInflater getLayoutInflater(Bundle savedInstanceState) { if (!mShowsDialog) { //根据前面的分析正常情况下mShowsDialog为真 return super.getLayoutInflater(savedInstanceState); } mDialog = onCreateDialog(savedInstanceState); //note1 if (mDialog != null) { setupDialog(mDialog, mStyle); //note2 return (LayoutInflater) mDialog.getContext().getSystemService( Context.LAYOUT_INFLATER_SERVICE); //返回所属dialog的LayoutInflate对象,用于解析onCreateView中的xml布局文件 } return (LayoutInflater) mHost.getContext().getSystemService( Context.LAYOUT_INFLATER_SERVICE); //返回所属context的LayoutInflate对象,用于解析onCreateView中的xml布局文件 }
1、调用onCreateDialog方法创建一个特定的Dialog对象
@NonNull
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new Dialog(getActivity(), getTheme()); //参数分别为当前DialogFragment所属Context、以及自身属性int mTheme = 0;
}
创建了一个android.app.Dialog对象
2、调用setupDialog方法,针对不同的style 对dialog进行相关的设置,默认style是normal,这里不进行特别处理。
public void setupDialog(Dialog dialog, int style) {
switch (style) {
case STYLE_NO_INPUT:
dialog.getWindow().addFlags(
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
case STYLE_NO_FRAME:
case STYLE_NO_TITLE:
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
}
}
往下分析onViewCreated,但是DialogFragment没有重写该方法,因此分析onActivityCreated方法。这里补充一下Fragment的onActivityCreated和onStart方法会在Activity的onStart方法中被先后调用。
onActivityCreated()@DialogFragment.class
public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); if (!mShowsDialog) { return; } View view = getView(); //onCreateView创建出来的view if (view != null) { if (view.getParent() != null) { throw new IllegalStateException("DialogFragment can not be attached to a container view"); } mDialog.setContentView(view); //将view交给dialog显示 } mDialog.setOwnerActivity(getActivity()); 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); } } }
这个方法的功能也就是对前面getLayoutInflater方法中创建的dialog进行设置、将onCreateView方法创建的View进行绑定、设置监听器等。
onActivityCreated方法执行完成后接着就是执行DialogFragment的onStart方法
onStart()@DialogFragment.class
@Override public void onStart() { super.onStart(); if (mDialog != null) { mViewDestroyed = false; mDialog.show(); //note1 } }
1、调用dialog的show方法进行视图真正的显示
DialogFramet没有重写Fragment的onResume方法,因此对于一个Dialog的显示就到此为止。相应的DialogFragment重写的Fragment的onStop、onDestroyView、 onDetach方法具体代码如下
onStop()@DialogFragment.class
@Override public void onStop() { super.onStop(); if (mDialog != null) { mDialog.hide(); } }
onDestroyView()@DialogFragment.class
@Override public void onDestroyView() { super.onDestroyView(); if (mDialog != null) { mViewDestroyed = true; mDialog.dismiss(); mDialog = null; } }
onDetach()@DialogFragment.class
@Override public void onDetach() { super.onDetach(); if (!mShownByMe && !mDismissed) { mDismissed = true; } }
上面代码很简单就不具体介绍了,通过前面的分析大体得出如下结论DialogFragment,之所以继承自Fragment而不是直接使用Dialog目的在于使用FragmentManager管理其生命周期。FragmentManager只是对Dialog的生命周期进行管理,而具体的视图显示工作还是交给android.app.Dialog进行处理,如调用Dialog的show、hide、dismiss方法对对话框进行显示、隐藏、销毁
下面我们探究一下Dialog是如何显示到屏幕上的,回顾前面用到的方法,大体有如下几个:
//创建Dialog
mDialog = new Dialog(getActivity(), getTheme()); //第二个参数正常情况为0
- mDialog.setContentView(view);
- mDialog.setOwnerActivity(getActivity());
- mDialog.setCancelable(mCancelable);
- mDialog.setOnCancelListener(this);
- mDialog.setOnDismissListener(this);
//显示、隐藏和销毁Dialog
mDialog.show();
mDialog.hide();
mDialog.dismiss();
根据这几个方法我们来分析一下Dialog的显示原理
Dialog.class
(android.app.Dialog)
final Context mContext; final WindowManager mWindowManager; Window mWindow; private Handler mListenersHandler;
Dialog()@Dialog.class
public Dialog(@NonNull Context context, @StyleRes int themeResId) { this(context, themeResId, true); } Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) { if (createContextThemeWrapper) { if (themeResId == 0) { //通过DialogFragment创建的一般都是这种情况 final TypedValue outValue = new TypedValue(); context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true); //从context的主题中获取到dialogTheme属性 themeResId = outValue.resourceId; } mContext = new ContextThemeWrapper(context, themeResId); //利用初始化mContext带主题 } else { mContext = context; } mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); //负责窗口管理服务 final Window w = new PhoneWindow(mContext); //创建PhoneWindow,用于具体显示 mWindow = w; w.setCallback(this); //dialog实现了Window.Callback接口,里面定义了如dispatchKeyEvent等处理用户点击事件的操作 w.setOnWindowDismissedCallback(this); //处理窗口销毁时的接口 w.setWindowManager(mWindowManager, null, null); w.setGravity(Gravity.CENTER); mListenersHandler = new ListenersHandler(this); }
构造器主要完成功能就是获得context的WindowManager引用;创建一个PhoneWindow,并对其进行一定初始化操作。
setContentView()@Dialog.class
public void setContentView(View view) { mWindow.setContentView(view); //调用PhoneWindow的同名方法 }
setOwnerActivity()@Dialog.class
private Activity mOwnerActivity; public final void setOwnerActivity(Activity activity) { mOwnerActivity = activity; mWindow.setVolumeControlStream(mOwnerActivity.getVolumeControlStream()); }
setCancelable()@Dialog.class
protected boolean mCancelable = true; public void setCancelable(boolean flag) { mCancelable = flag; }
setOnCancelListener()@Dialog.class
private String mCancelAndDismissTaken; private Message mCancelMessage; //用于存储一个取消Message,在Dialog销毁时将该Message交给Handler处理 public void setOnCancelListener(final OnCancelListener listener) { if (mCancelAndDismissTaken != null) { throw new IllegalStateException( "OnCancelListener is already taken by " + mCancelAndDismissTaken + " and can not be replaced."); } if (listener != null) { mCancelMessage = mListenersHandler.obtainMessage(CANCEL, listener); } else { mCancelMessage = null; } }
setOnDismissListener()@Dialog.class
private Message mDismissMessage; public void setOnDismissListener(final OnDismissListener listener) { if (mCancelAndDismissTaken != null) { throw new IllegalStateException( "OnDismissListener is already taken by " + mCancelAndDismissTaken + " and can not be replaced."); } if (listener != null) { mDismissMessage = mListenersHandler.obtainMessage(DISMISS, listener); } else { mDismissMessage = null; } }
与前面的setOnCancelListener()方法类似,也是包装成一个Message后期交给handler去处理。
show()@Dialog.class
private boolean mShowing = false; private boolean mCreated = false; View mDecor; public void show() { if (mShowing) { //第一次调用不会进入到这里 if (mDecor != null) { if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) { mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR); } mDecor.setVisibility(View.VISIBLE); } return; } mCanceled = false; if (!mCreated) { //第一次调用会执行下面的代码 dispatchOnCreate(null); //note1 } onStart(); //note2 mDecor = mWindow.getDecorView(); //从PhoneWindows中获取DecorView if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) { //ActionBar为null,同时当前PhoneWindow拥有标题栏 final ApplicationInfo info = mContext.getApplicationInfo(); mWindow.setDefaultIcon(info.icon); mWindow.setDefaultLogo(info.logo); mActionBar = new WindowDecorActionBar(this); } WindowManager.LayoutParams l = mWindow.getAttributes(); if ((l.softInputMode & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) { WindowManager.LayoutParams nl = new WindowManager.LayoutParams(); nl.copyFrom(l); nl.softInputMode |= WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION; l = nl; } try { mWindowManager.addView(mDecor, l); //note3 mShowing = true; sendShowMessage(); //note4 } finally { } }
1、进行一些初始化操作,但是Dialog并没有在里面进行任何操作除了将mCreatd属性设置为true
void dispatchOnCreate(Bundle savedInstanceState) {
if (!mCreated) {
onCreate(savedInstanceState);
mCreated = true;
}
}
protected void onCreate(Bundle savedInstanceState) { }
2、该start方法对AcitonBar设置隐藏动画使能
protected void onStart() {
if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(true);
}
3、将得到的DecorView添加到WindowManger进行显示。
4、将showMessage发送给Handler去处理,showMessage是在setOnShowListener(OnShowListener listener)时传入的,用于监听窗口显示时的动作,与OnDismissListener和onCancelListener类似。如果没有设置该监听器那么下面的方法就不会其任何作用。
private void sendShowMessage() {
if (mShowMessage != null) {
Message.obtain(mShowMessage).sendToTarget();
}
}
hide()@Dialog.class
public void hide() { if (mDecor != null) { mDecor.setVisibility(View.GONE); } }
dismiss()@Dialog.class
@Override public void dismiss() { if (Looper.myLooper() == mHandler.getLooper()) { dismissDialog(); } else { mHandler.post(mDismissAction); //如果不是UI线程则将dismiss交给handler去处理 } } dismissDialog()@Dialog.class void dismissDialog() { if (mDecor == null || !mShowing) { return; } if (mWindow.isDestroyed()) { Log.e(TAG, "Tried to dismissDialog() but the Dialog's window was already destroyed!"); return; } try { mWindowManager.removeViewImmediate(mDecor); //从WindowManager中remove DecorView } finally { if (mActionMode != null) { mActionMode.finish(); .//销毁ActionBar } mDecor = null; mWindow.closeAllPanels(); onStop(); mShowing = false; sendDismissMessage(); //触发dismiss监听器 } }
综上我们对Dialog的分析可以知道,首先根据Context和Theme得到一个新的Context,随后根据Context得到一个WindowManager,并根据context创建一个PhoneWindow,随后通过PhoneWindow.setContentView(view)将自定义的View传给PhoneWindow。show方法中则从PhoneWindow中获得一个DecorView(DecorView是PhoneView的内部类),最后将该DecoreView添加到WindowManager中。hide方法最为简单就是调用mDecor.setVisibility(View.GONE),设置view为不可见状态。而dismiss就是类似show的逆过程,将DecorView从WindowManager中移除出去。
补充:如果以前有对Android系统底层有过分析的话可以知道这里的过程与Activity中创建View的过程类似,也是先后创建PhoneWindow和DecorWindow,最后通过WindowManager.addView方法将DecorView交给WindowManagerService进行显示。Activity的setContentView方法如下
[email protected]
public void setContentView(@LayoutRes int layoutResID) {
mWindow.setContentView(layoutResID);
initWindowDecorActionBar(); //对ActionBar进行一些初始化
}
private Window mWindow = new PhoneWindow(this);
可以发现跟这里的流程基本都是一样的,OnCreate中创建PhoneWindow然后得到Decorview,最后在onResume方法中将Decorview交给WindowManager去处理,其实最终是交给WindowManagerService进行管理,后者负责显示视图和传递用户的事件。
到此为止我们沿着DialogFragment->Dialog->PhoneWindow DecoreView WindowManager的轨迹分析了DialogFragment的显示流程。最后如果对PhoneWindow如何显示感兴趣,那就需要查看com.android.internal.policy.impl.PhoneWindow的setContentView以及getDecorView两个方法,以及android.view.WindowManager的addView方法进行探究。这部分内容留待后面有机会再来分析。