好久没写博客了,这期来讲一下dialog,关于dialog相信很多人和我都一样用的好熟悉了,对它的使用方法也是很了解的,其实真的很了解吗?我不禁问了自己这样的一个问题。
比如dialog到底是什么,就仅仅是一个viewgroup或者是其它更高级的载体呢,又比如dialog为什么一定要依赖于一个activity才可以正常运行呢?还有的就是我们平时自定义dialog的时候在某些方法中给他设置的宽高但是却没有达到我们预期的效果呢?好吧,下面我就走进dialog源码一一解决这些问题。
首先在创建一个dialog的时候其实最好都会调用dialog中的默认构造方法,在默认构造方法中有这么的一段代码引起了我的注意:
Dialog(Context context, int theme, boolean createContextThemeWrapper){
......
mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
//创建一个window,其实就是phonewindow(window的唯一实现类)
Window w = PolicyManager.makeNewWindow(mContext);
mWindow = w;
//设置dialog的点解回调
w.setCallback(this);
//关联windowmanager到window,在这里我们可以看到dialog中的window的 token=null
//这么一来dialog中的window就没有token了
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
//这里创建一个handler实例
mListenersHandler = new ListenersHandler(this);
}
从上面我们可以看到了dialog实例化的时候会创建一个handler实例,这就是为什么在子线程创建dialog回报错的原因了,Thread默认是没有looper的。具体可以看一下这篇博客这里写链接内容,本节只做dialog的笔录,所以不偏离话题。
这里我们可以知道dialog的载体其实就是一个window,也可以理解为dialog是一个window,一开始我读到这里的时候就觉得奇怪了,既然dialog是一个window他应该可以不依赖于其他载体就可以显示出来,然而其实window是有三种主要类型的,dialog是属于SubWindows(与顶层窗口相关联,需将token设置成它所附着宿主窗口的token),这里关于window的不多说,有兴趣的可以去看一下这边博客http://blog.youkuaiyun.com/yanbober/article/details/46361191,接下来我们继续来分析dialog,看一下show()方法
public void show() {
if (mShowing) {
//(1)当dialog设置为不可见,并且mDecor !=null的时候显示mDecor,其实就是显示window,
//下面会继续说明
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) {
//这里调用OnCreate方法
dispatchOnCreate(null);
}
//调用OnStart方法
onStart();
mDecor = mWindow.getDecorView();
......
//这里获取一个默认的WindowManager.LayoutParams对象
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 {
//添加一个window,这样就呼应(1)中显示一个view就是显示一个window的说法了,
//添加view的时候,为它绑定WindowManager.LayoutParams
mWindowManager.addView(mDecor, l);
mShowing = true;
sendShowMessage();
} finally {
}
}
通过上面的方法已经解开了Dialog是window的问题了,然后在继续往下看,关注这一句代码
WindowManager.LayoutParams l = mWindow.getAttributes();getAttributes()方法:
public final WindowManager.LayoutParams getAttributes() {
return mWindowAttributes;
}
private final WindowManager.LayoutParams mWindowAttributes =
new WindowManager.LayoutParams();
返回一个WindowManager.LayoutParams类型,这里使用无参构造方法创建一个WindowManager.LayoutParams对象,接着往下看
public LayoutParams() {
super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
//设置window的类型
type = TYPE_APPLICATION;
format = PixelFormat.OPAQUE;
}
/**
* Window type: a normal application window. The {@link #token} must be
* an Activity token identifying who the window belongs to.
* In multiuser systems shows only on the owning user's window.
*/
public static final int TYPE_APPLICATION = 2;
大概就是说:这是一个平常的应用window,它的token必须是关联activity的token,在多用户系统中仅显示拥有用户的窗口。
这么一来就知道为什么dialog要绑定activity了吧,在前面说了window和windowmanager关联的时候token=null,然而dialog有需要绑定activity的dialog。
这么一来问题就都解开了
下面我们再来,我们看dialog的dismiss()方法:
@Override
public void dismiss() {
if (Looper.myLooper() == mHandler.getLooper()) {
//首先判断是否同在一个线程中,是的话就直接关闭dialog
dismissDialog();
} else {
//不是的就用dialog的handler来进行关闭dialog
mHandler.post(mDismissAction);
}
}
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 {
//删除window的Decorview
mWindowManager.removeViewImmediate(mDecor);
} finally {
if (mActionMode != null) {
mActionMode.finish();
}
mDecor = null;
//关闭window的所有面板,其实这个方法下面的代码也是windowmanage对view进行删除操作的
//所以说添加/删除一个window相当于对一个view进行操作
mWindow.closeAllPanels();
onStop();
mShowing = false;
sendDismissMessage();
}
}
还有的就是为什么我们有时候已经设置了WindowManager.LayoutParams,但是却没有体现出我们想要的效果呢?其实出现这种情况的一般都是在下面这种自定义dialog中出现,如下:
public static void testDialog1(Context context) {
AlertDialog dialog = new AlertDialog.Builder(context).setCancelable(true).create();
dialog.show();
Window window = dialog.getWindow();
window.setContentView(R.layout.dialog_stock_info);
}
我们已经在show(),之前设置了WindowManager.LayoutParams属性,但是再setContentView以后就不能体现出来了,到底是问什么呢?我们来看看下面这段代码,如下:
@Override
public void setContentView(int layoutResID) {
...
//这里判读是否有设置mFeature
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
...
}
上面的代码是再次setContentView的主要执行代码,这里不管走的是哪个判断,它们最后都会走到一个共同的交汇处,就是调用LayoutInflater中的inflate(…)方法,其实使得之前是在的WindowManager.LayoutParams无效就是在里面,请看如下:
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
...
try {
...
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
}
...
} catch (XmlPullParserException e) {
...
} catch (Exception e) {
...
} finally {
...
}
...
}
}
首先假设它执行了else里面,可以很一目了然的发现了params = root.generateLayoutParams(attrs);
来看看这里面的代码:
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
这里是从新生成一个新的LayoutParams,这么一来就知道了为什么在show(),之前是在的LayoutParams会没有效果,其实是在setContentView的时候把LayoutParams重置了,其实为什么要重置也不难理解,因为每个view都和一个LayoutParams绑定的。后面做测量绘制总结的时候会说到。
好了下面在说说走进的是TAG_MERGE.equals(name)那又会是真么样的,我们可以看到这里调用到了
rInflate(parser, root, inflaterContext, attrs, false);方法,看看这里面:
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
...
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
...
if (TAG_REQUEST_FOCUS.equals(name)) {
//当requestFocus标签是进入这里
parseRequestFocus(parser, parent);
} else if (TAG_TAG.equals(name)) {
//当tag标签是进入这里
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) {
//当include标签是进入这里
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) {
//当merge标签是进入这里
throw new InflateException("<merge /> must be the root element");
} else {
...
//到了这里就明白了吧其实这里也是重置了LayoutParams
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflateChildren(parser, view, attrs, true);
viewGroup.addView(view, params);
}
}
if (finishInflate) {
parent.onFinishInflate();
}
}
看了上面这个方法恐怕都明白了吧,其实也是会重置LayoutParams
以上就是今天的总结,后面发现有遗漏会继续不上,也希望大家看到不足的地方指点一下,好及时发现和解决问题