轉(zhuǎn)載自blog.csdn.net/u013356254/article/details/55116259
android交流:364595326
android中我們常見的Activity,Diaog等內(nèi)部都封裝了PhoneWindow對象。
我們今天要探討的是兩個問題
為什么系統(tǒng)在創(chuàng)建Acivity或者Dialog的時候封裝了PhoneWindow對象,而我們自己寫懸浮窗口的時候并沒有使用PhoneWindow對象?
為什么Diaog封裝了PhoneWindow對象揣非,而PopupWindow卻直接將contentView封裝成PopupDecorView(FrameLayout子類),直接調(diào)用WM來添加view?
我們從Dialog的setContentView()方法說起繁涂。源碼
public void setContentView(@NonNull View view, @Nullable ViewGroup.LayoutParams params) {
// 調(diào)用的是window的方法
mWindow.setContentView(view, params);
}
下面是PhoneWindow的setContentView()方法衡招。
@Override
public void setContentView(int layoutResID ) {
// mContentParent是id為ID_ANDROID_CONTENT的FrameLayout
// 我們經(jīng)常寫的setContentView第练,這個方法阔馋,其實就是給id為ID_ANDROID_CONTENT的view添加一個孩子
if (mContentParent == null) {
// 下面這個方法。完成了兩件事情
// 1 創(chuàng)建DecorView(FrameLayout),也就是我們經(jīng)常說的window中有個DecorView對象娇掏。
// 2 給mContentParent賦值
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
// 如果沒有5.0轉(zhuǎn)場動畫呕寝,remove掉之前添加的所有view
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
//? 5.0專場動畫
view.setLayoutParams(params);
final Scene newScene = new Scene(mContentParent, view);
transitionTo(newScene);
} else {
// 給id為ID_ANDROID_CONTENT的view添加新的孩子
// 將layoutResID添加到ContentParent上面
mLayoutInflater.inflate(layoutResID, mContentParent);
}
}
PhoneWindow.setContentView()方法的核心是,生成DecorView和mContentParent對象婴梧,之后將布局文件添加到mContentParent上面去
接下來我們分析installDecor()方法
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
// 產(chǎn)生decorView 也就是ViewTree的根節(jié)點
mDecor = generateDecor(-1);
} else {
// 將decorView和window關(guān)聯(lián)起來
mDecor.setWindow(this);
}
if (mContentParent == null) {
// 根據(jù)decorview產(chǎn)生我們的ContentParent也就是id為content的viewGroup,
mContentParent = generateLayout(mDecor);
}
}
我們可以看到installDecor()方法主要是創(chuàng)建了DecorView下梢,和mContentParent對象客蹋。
下面是generateDecor(-1)源碼
protected DecorView generateDecor(int featureId) {
// 創(chuàng)建DecorView(FrameLayout)對象,ViewTree的根節(jié)點
return new DecorView(context, featureId, this, getAttributes());
}
下面是創(chuàng)建mContentParent的代碼
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
// 獲得window的樣式
TypedArray a = getWindowStyle();
/*省略掉一些設(shè)置樣式的代碼/
// 下面的代碼是給decorView填充孩子的
// 主要功能是根據(jù)不同的配置給decorView添加不同的布局文件(即給decorView添加不同的孩子節(jié)點)
int layoutResource;
int features = getLocalFeatures();
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
} else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleIconsDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_title_icons;
}
removeFeature(FEATURE_ACTION_BAR);
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
&& (features & (1 << FEATURE_ACTION_BAR)) == 0) {
// Special case for a window with only a progress bar (and title).
// XXX Need to have a no-title version of embedded windows.
layoutResource = R.layout.screen_progress;
// System.out.println("Progress!");
} else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
// Special case for a window with a custom title.
// If the window is floating, we need a dialog layout
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogCustomTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_custom_title;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);
// 設(shè)置notitle的布局文件
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
// Dialog樣式的
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
layoutResource = a.getResourceId(
R.styleable.Window_windowActionBarFullscreenDecorLayout,
R.layout.screen_action_bar);
} else {
layoutResource = R.layout.screen_title;
}
} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
layoutResource = R.layout.screen_simple_overlay_action_mode;
} else {
// Embedded, so no decoration is needed.
layoutResource = R.layout.screen_simple;
// System.out.println("Simple!");
}
mDecor.startChanging();
// 下面的方法是將找到的不同的布局文件孽江,添加給decorView.
// 這里也說明了讶坯,我們經(jīng)常寫的requestWindowFeature(Window.FEATURE_NO_TITLE)代碼為什么一定放在setContentView之前。
// 因為系統(tǒng)會根據(jù)配置找不同的布局文件岗屏,而一旦添加了布局文件辆琅,就沒有辦法再移除title了。因此會拋出異常
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
// 接下來是給賦值这刷,這里直接調(diào)用的findViewById()婉烟,其實內(nèi)部會調(diào)用decorView.findViewById();
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
return contentParent;
}
generateLayout(DecorView decor) 主要完成了兩件事,1通過不同的配置給decorView添加不同layoutResource布局文件暇屋, 2找到id為IDANDROIDCONTENT的view似袁。
分析完setContentView代碼,我們發(fā)現(xiàn)setContentView.其實是將view添加到PhoneWindow的成員變量DecorView中的id為IDCONTENTANDROID的View節(jié)點上咐刨。還發(fā)現(xiàn)了DecorView的孩子節(jié)點會根據(jù)我們的requestWindowFeature()的不同昙衅,添加不同的layoutResource布局文件,而這些不同的layoutResource布局文件都是一個id為IDANDROIDCONTENT的孩子所宰。
接下來我們分析Diaog的show()方法
public void show() {
// 拿到PhoneWindow中的decorView對象
mDecor = mWindow.getDecorView();
// 產(chǎn)生布局參數(shù)
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;
}
// wm添加decorView
mWindowManager.addView(mDecor, l);
}
我們發(fā)現(xiàn),寫到最后show()方法其實就是將decorView添加到wm中
而我們寫懸浮窗口的時候畜挥,直接用wm添加view仔粥。通過以上分析我們可以得出以下結(jié)論
結(jié)論
PhoneWindow的一個作用是給view包裹上一層DecorView。而DecorView中的布局結(jié)構(gòu)蟹但,會根據(jù)requestWindowFeature()的不同而不同(requestWindowFeature()方法躯泰,會影響DecorView的孩子節(jié)點(layoutResource布局文件))
我們的Activity和Dialog的布局都比較復(fù)雜,比如都可能有appbar(toolbar/actionbar)等华糖。因此通過PhoneWindow來封裝下可以更好的解耦代碼
PopupWindow或者Toast的布局比較簡單麦向。因此沒有必要包裹一層PhoneWindow。在源碼中也沒有發(fā)現(xiàn)有PhoneWindow的痕跡客叉。