本文共 12115 字,大约阅读时间需要 40 分钟。
对于一个页面的窗口展示,很多时候都可以理解为是一个activity,这样理解没错,但更确切的说window才是负责页面展示的,那为什么可以理解为一个页面就是一个activity呢?这是由于activity持有window对象,activity负责加载布局并将布局添加到window对象中。这里需要想个问题,dialog的显示并不由activity来控制,所以这里可以推测dialog的显示应该是由window来控制的,那到底是不是呢?那就得去源码中看看了。
先来个简单的使用:
mTextMessage.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this); builder.setTitle("titile"); builder.setMessage("message"); AlertDialog dialog = builder.create(); dialog.show(); } });
使用起来还是挺简单的,就从这段简单的代码开始入手了,先来看下AlertDialog.Builder的构造函数:
public Builder(@NonNull Context context) { this(context, resolveDialogTheme(context, 0)); } public Builder(@NonNull Context context, @StyleRes int themeResId) { P = new AlertController.AlertParams(new ContextThemeWrapper( context, resolveDialogTheme(context, themeResId))); mTheme = themeResId; }
初始化了一个AlertController.AlertParams类型的P对象,可以理解为是一个存放设置dialog属性的容器,再来看下AlertController.AlertParams的构造函数:
public AlertParams(Context context) { mContext = context; mCancelable = true; mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); }
其实就是初始化了一些成员变量。
到这AlertDialog.Builder的初始化就算完了,还是比较简单的,接下来看dialog的参数是怎么设置的:
builder.setTitle("titile") builder.setMessage("message")
这是设置dialog的显示信息,当然还有好多属性可以设置,不过都是类似的,所以这里就看这两个属性的实现:
public Builder setTitle(@Nullable CharSequence title) { P.mTitle = title; return this; } public Builder setMessage(@Nullable CharSequence message) { P.mMessage = message; return this; }
可以看到,这些设置进来的属性都赋值给了P对象,到目前为止,已经设置了显示dialog的消息,接下来执行的是:
AlertDialog dialog = builder.create();
看来得去看看create()方法中做了些什么了:
public AlertDialog create() { // We can't use Dialog's 3-arg constructor with the createThemeContextWrapper param, // so we always have to re-set the theme final AlertDialog dialog = new AlertDialog(P.mContext, mTheme); P.apply(dialog.mAlert); dialog.setCancelable(P.mCancelable); if (P.mCancelable) { dialog.setCanceledOnTouchOutside(true); } dialog.setOnCancelListener(P.mOnCancelListener); dialog.setOnDismissListener(P.mOnDismissListener); if (P.mOnKeyListener != null) { dialog.setOnKeyListener(P.mOnKeyListener); } return dialog; }
先创建了AlertDialog对象,那就去看下AlertDialog的构造方法了:
protected AlertDialog(@NonNull Context context, @StyleRes int themeResId) { super(context, resolveDialogTheme(context, themeResId)); mAlert = new AlertController(getContext(), this, getWindow()); }
这里创建了一个AlertController类型的mAlert对象,create()方法中有用到,AlertDialog继承自Dialog:
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) { if (createContextThemeWrapper) { if (themeResId == ResourceId.ID_NULL) { final TypedValue outValue = new TypedValue(); context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true); themeResId = outValue.resourceId; } mContext = new ContextThemeWrapper(context, themeResId); } else { mContext = context; } mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); final Window w = new PhoneWindow(mContext); mWindow = w; w.setCallback(this); w.setOnWindowDismissedCallback(this); w.setOnWindowSwipeDismissedCallback(() -> { if (mCancelable) { cancel(); } }); w.setWindowManager(mWindowManager, null, null); // 设置window显示的位置,默认是居中显示 w.setGravity(Gravity.CENTER); mListenersHandler = new ListenersHandler(this); }
这里主要是创建了一个Window对象(Window的唯一实现是PhoneWindow),然后设置了一些回调和Window显示的位置,所以Dialog是持有Window对象的。在回到create()方法中:
public AlertDialog create() { // We can't use Dialog's 3-arg constructor with the createThemeContextWrapper param, // so we always have to re-set the theme final AlertDialog dialog = new AlertDialog(P.mContext, mTheme); P.apply(dialog.mAlert); dialog.setCancelable(P.mCancelable); if (P.mCancelable) { dialog.setCanceledOnTouchOutside(true); } dialog.setOnCancelListener(P.mOnCancelListener); dialog.setOnDismissListener(P.mOnDismissListener); if (P.mOnKeyListener != null) { dialog.setOnKeyListener(P.mOnKeyListener); } return dialog; }
创建AlertDialog对象后,接着调用的是P.apply(dialog.mAlert),P对象是在构建Builder的时候创建的,其类型是AlertController.AlertParams,mAlert对象在AlertDialog构造函数初始化的,那就来看下apply()函数做了些什么:
public void apply(AlertController dialog) { if (mCustomTitleView != null) { dialog.setCustomTitle(mCustomTitleView); } else { if (mTitle != null) { dialog.setTitle(mTitle); } if (mIcon != null) { dialog.setIcon(mIcon); } if (mIconId != 0) { dialog.setIcon(mIconId); } if (mIconAttrId != 0) { dialog.setIcon(dialog.getIconAttributeResId(mIconAttrId)); } } if (mMessage != null) { dialog.setMessage(mMessage); } if (mPositiveButtonText != null || mPositiveButtonIcon != null) { dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText, mPositiveButtonListener, null, mPositiveButtonIcon); } if (mNegativeButtonText != null || mNegativeButtonIcon != null) { dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText, mNegativeButtonListener, null, mNegativeButtonIcon); } if (mNeutralButtonText != null || mNeutralButtonIcon != null) { dialog.setButton(DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText, mNeutralButtonListener, null, mNeutralButtonIcon); } // For a list, the client can either supply an array of items or an // adapter or a cursor if ((mItems != null) || (mCursor != null) || (mAdapter != null)) { createListView(dialog); } if (mView != null) { if (mViewSpacingSpecified) { dialog.setView(mView, mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight, mViewSpacingBottom); } else { dialog.setView(mView); } } else if (mViewLayoutResId != 0) { dialog.setView(mViewLayoutResId); } /* dialog.setCancelable(mCancelable); dialog.setOnCancelListener(mOnCancelListener); if (mOnKeyListener != null) { dialog.setOnKeyListener(mOnKeyListener); } */ }
前面有说到使用Builder设置参数时,实际是将参数设置到P对象中,所以apply()函数的作用就是将P对象中参数又设置到了mAlert对象中。接着回到create()函数中看剩下的方法,基本是对dialog的回调设置,这里就不跟进去了。
分析到这,dialog的配置就算是全部配置完了,接下去要执行的是:
dialog.show();
嗯嗯,看来实现就在这个方法中了:
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); } else { // Fill the DecorView in on any configuration changes that // may have occured while it was removed from the WindowManager. final Configuration config = mContext.getResources().getConfiguration(); mWindow.getDecorView().dispatchConfigurationChanged(config); } onStart(); mDecor = mWindow.getDecorView(); if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) { 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; } mWindowManager.addView(mDecor, l); mShowing = true; sendShowMessage(); }
先是if(mShowing)判断dialog是否处在显示状态,初次显示这里为false,那这里这样做判断有什么用意呢?在Dialog中有个hide()方法,这个方法是隐藏dialog,实际是没用销毁dialog的,且dialog的mShowing还是为true的,这就是这里所作判断的用意,继续往下看,接下来是if(!mCreated),默认是false,所以这里要执行的是dispatchOnCreate(null):
void dispatchOnCreate(Bundle savedInstanceState) { if (!mCreated) { onCreate(savedInstanceState); mCreated = true; } } protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mAlert.installContent(); }
在执行完dispatchOncreate(null)后将mCreated置为true,紧接着执行是onCreate(),这个在Dialog中是空实现,在AlertDialog有实现,就执行了mAlert.installContent(),作用是什么呢?看看就知道了:
public void installContent() { final int contentView = selectContentView(); mDialog.setContentView(contentView); setupView(); }
这里简单说下这几行代码的作用:
1、selectContentView()是获取显示dialog的布局文件;
2、mDialog.setContentView(),这里是将布局文件设置到window对象中去,还记得activity是如何设置view的么?没错,用的就是setContentView()这个方法,这里和activity中使用是一个意思,其实质都是将布局文件设置到window对象中,之后就可显示了。
3、将布局文件设置到window对象中,会生成view对象,但是这些view对象还没有设置内容和一些事件监听,这也就是setupView()要做的事;
做完这些后就可以回到show()方法中接着往下走了,mWindowManager.adddView()这个方法,这个方法实际起到什么用呢?那就的好好说下了,在这方法中会创建一个ViewRootImpl对象,然后将view对象添加到ViewRootImpl对象中去,ViewRootImpl中会通知下一帧屏幕信号到来时执行view的测量、布局、绘制,这样dialog就显示到界面上了。
最后在说下sendShowMessage()方法,当设置了显示dialog时的回调监听时,这里实际的作用就是去通知这个回调。
接下来看下Dialog的一些不常见设置,也可以说是一些高级点的设置,上面说到dialog实际也是有window来显示,那么就可以对window进行一些设置,已达到想要的效果,这里先说几点需求:
1、指定dialog的宽高;
2、控制dialog的透明度;
3、调整dialog外部的灰度;
4、指定dialog的显示位置;
5、设置dialog的出现和消失时的动画;
如果你对window对象不太明白,那这个实现起来那就会有点头大了,但是当你明白window对象后,你就会发现这实在是太简单了,因为只需要设置一些window的属性就可以了,来看看:
mTextMessage.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this); builder.setTitle("titile"); builder.setMessage("message"); AlertDialog dialog = builder.create(); dialog.show(); Window window = dialog.getWindow(); WindowManager.LayoutParams params = window.getAttributes(); params.width = 200; params.height = 100; params.alpha = 1f; params.dimAmount = 0.5f; params.gravity = Gravity.BOTTOM; params.windowAnimations = R.style.BaseDialog_Bottom_Anim; window.setAttributes(params); dialog.show(); } });
这里稍微提下这几个属性,还有其他的一些属性,自己可以去测试下,代码还是比较简单的:
params.width = 200;params.height = 100; 设置dialog显示的宽高;
params.alpha = 1f;params.dimAmount = 0.5; 设置弹框的透明度和弹框外部的灰度;
params.gravity = Gravity.BOTTOM; 设置dialog显示的位置在底部,如果想指定在具体位置,可以使用params.x;
params.windowAnimations = R.style.BaseDialog_Botton_Anim; 设置dialog显示隐藏动画;
R.style.BaseDialog_Botton_Anim的配置如下:
netlib_bottom_enter_anim:
netlib_bottom_exit_anim:
到这就算说完了,有不明白的欢迎提问一起探讨!
转载地址:http://skhai.baihongyu.com/