編輯:關於Android編程
當我們用XML中寫完一個布局文件,想在某個Activity中顯示的時候,往往通過setContentView方法加載。在上一篇文章中,我們知道如何通過LayoutInflater將一個布局文件加載到指定的父布局中。如果Activity也提供一個布局,那將xml顯示出來想必離不開LayoutInflater。
Activity中調用setContentView的代碼如下:
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
initActionBar();
}
setContentView調用了getWindow方法裡面的setContentView方法。
public Window getWindow() {
return mWindow;
}
而mWindow初始化為了PhoneWindow的對象實例
mWindow = new PhoneWindow(this);
我們知道,PhoneWindow是抽象類Window的實現類。所以Activity中的setContentView方法其實調用的是PhoneWindow類的setContentView。PhoneWindow屬於framework層。
再看PhoneWindow中的setContentView方法
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else {
mContentParent.removeAllViews();
}
mLayoutInflater.inflate(layoutResID, mContentParent);
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
找到關鍵了mLayoutInflater.inflate(layoutResID, mContentParent),設置的布局id,被添加到了mContentParent父布局中。是不是覺得setContentView的機制你已經清楚了呢?如果你清楚了LayoutInflater的加載機制,那setContentView你就清楚了一大半了。但是還有一點不明白,Activity的界面到底由哪些部分組成,mContentParent又是啥。我們接著把代碼看完,想必心中就有譜了。
上述代碼首先是對mContentParent判空,如果空就調用installDecor()。想必mContentParent的初始化就是在該方法體裡面。
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
//根據窗口的風格修飾,選擇對應的修飾布局文件,並且將id為content的FrameLayout賦值給mContentParent
mContentParent = generateLayout(mDecor);
//......
//初始化一堆屬性值
}
}
可以看到mContentParent的初始化借助了mDecor,mDecor在初始化mContentParent之前借助generateDecor()完成。mDecor是DecorView的對象,DecorView是FrameLayout的子類,如下代碼所示。
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
//......
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
//......
調用generateLayout創建mContentParent對象之後,就可以調用findViewById生成一些標題、ActionBar對象。那在generateLayout中應該進行了放入了一些布局控件等。打開源碼瞅一瞅。
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
TypedArray a = getWindowStyle();
//...Window_windowIsFloating,Window_windowNoTitle,Window_windowActionBar...
//首先通過WindowStyle中設置的各種屬性,對Window進行requestFeature或者setFlags
if (a.getBoolean(com.android.internal.R.styleable.Window_windowNoTitle, false)) {
requestFeature(FEATURE_NO_TITLE);
}
//...
if (a.getBoolean(com.android.internal.R.styleable.Window_windowFullscreen, false)) {
setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
}
//...根據當前sdk的版本確定是否需要menukey
WindowManager.LayoutParams params = getAttributes();
//通過a中設置的屬性,設置 params.softInputMode 軟鍵盤的模式;
//如果當前是浮動Activity,在params中設置FLAG_DIM_BEHIND並記錄dimAmount的值。
//以及在params.windowAnimations記錄WindowAnimationStyle
//Inflate the window decor.
int layoutResource;//將要選擇的窗口根部局
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
/*是根據窗口的風格修飾類型為該窗口選擇不同的窗口根布局文件*/
if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
com.android.internal.R.attr.dialogTitleIconsDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = com.android.internal.R.layout.screen_title_icons;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);
// System.out.println("Title Icons!");
} 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 = com.android.internal.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(
com.android.internal.R.attr.dialogCustomTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = com.android.internal.R.layout.screen_custom_title;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
// If no other features and not embedded, only need a title.
// If the window is floating, we need a dialog layout
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
com.android.internal.R.attr.dialogTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
layoutResource = com.android.internal.R.layout.screen_action_bar;
} else {
layoutResource = com.android.internal.R.layout.screen_title;
}
// System.out.println("Title!");
} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
layoutResource = com.android.internal.R.layout.screen_simple_overlay_action_mode;
} else {
// Embedded, so no decoration is needed.
layoutResource = com.android.internal.R.layout.screen_simple;
// System.out.println("Simple!");
}
//根部局文件inflate到decor中
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
//加入mDecor(布局)中的id為content的View返回給contentParent
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
//...
return contentParent;
}
}
總的來說generateLayout流程就是,mDecor是一個FrameLayout(子類),根據theme選用系統布局文件(一般包含title,actionbar和id 為content的FrameLayout),並inflate為View,加入到mDecor中。而布局文件中包含的id為content的FrameLayout將返回給mContentParent。一旦我們有了mContentParent,就把我們setContentView的布局添加到mContentParent上了。
經過上面的Activity setContentView調用到PhoneWindow setContentView;再利用PhoneWindow中的installDecor初始化mDecor,PhoneWindow的generatelayout完成mDecor對mContentParent的初始化。
給大家看一個例子
AndroidManifest.xml設置如下
......
generateLayout方法中的layoutResource變量值為R.layout.screen_simple,所以我們看下系統這個screen_simple.xml布局文件,如下:
<framelayout android:foreground="?android:attr/windowContentOverlay" android:foregroundgravity="fill_horizontal|top" android:foregroundinsidepadding="false" android:id="@android:id/content" android:layout_height="match_parent" android:layout_width="match_parent"> </framelayout>
布局中,一般會包含ActionBar,TitleBar,和一個id為content的FrameLayout,這個布局是NoTitle的,所以在HierarchyViewer沒有顯現。
再來看下上面這個App的hierarchyviewer圖譜,如下:

根部局是DecorView(FrameLayout子類),LinearLayout是根據Theme設置選擇的系統布局文件,並添加到DecorView中,返回其中id為content的FrameLayout給mContentParent,用來承接setContentView傳進來的布局文件。至此大家對setContentView的布局加載流程應該有了一個比較清楚的認識了。一個完整的Activity默認視圖結構如圖

配置一個好用的Android模擬器讓你不再對模擬器那麼失望
默認情況下的Android模擬器就是下面的這個樣子: 看到這個屏幕截圖最顯眼的問題顯然它的丑陋的界面。模擬器窗口占據了屏幕巨大的空間,而且毫無緣由的放著一個屏幕鍵盤。如果
Android NDK——配置NDK及使用Android studio開發Hello JNI並簡單打包so庫
引言盡管Android Studio已經越來越流行了,但很多人還是習慣於Eclipse或源碼環境下開發JNI應用。筆者是從以前在學校參加谷歌大學學術合作項目的時候接觸JN
Android之手勢的識別與處理(雙擊onDoubleTap、滑動onFling、拖動onScroll)
概述:一般情況下,我們知道View類有個View.OnTouchListener內部接口,通過重寫他的onTouch(View v, MotionEvent event)
一個FlowLayout帶你學會自定義ViewGroup
時間過得真快,又到了寫博客的時候了(/▽╲)。這次按照計劃記錄一個簡單的自定義ViewGroup:流布局FlowLayout的實現過程,將View的繪制流程和Layout