一.View是如何繪制到窗口屏幕上的
1.源碼解析
創(chuàng)建activity的時(shí)候我們都會(huì)在onCreate里看到setContentView方法,并且傳入了我們創(chuàng)建的layout
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
第一步 我們點(diǎn)開setContentView會(huì)看到如下源碼
/**
* Set the activity content from a layout resource. The resource will be
* inflated, adding all top-level views to the activity.
*
* @param layoutResID Resource ID to be inflated.
*
* @see #setContentView(android.view.View)
* @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
*/
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
我們可以看到 資源文件傳入到了getWindow().setContentView(layoutResID);
那么可以繼續(xù)看看getWindow(),進(jìn)入getWindow()我們可以看到返回的是一個(gè)Window對(duì)象:
/**
* Retrieve the current {@link android.view.Window} for the activity.
* This can be used to directly access parts of the Window API that
* are not available through Activity/Screen.
*
* @return Window The current window, or null if the activity is not
* visual.
*/
public Window getWindow() {
return mWindow;
}
進(jìn)入Window中我們發(fā)現(xiàn)這個(gè)Window是一個(gè)抽象類
/**
* Abstract base class for a top-level window look and behavior policy. An
* instance of this class should be used as the top-level view added to the
* window manager. It provides standard UI policies such as a background, title
* area, default key processing, etc.
*
* <p>The only existing implementation of this abstract class is
* android.view.PhoneWindow, which you should instantiate when needing a
* Window.
大概翻譯: 這個(gè)抽象類是唯一現(xiàn)有的實(shí)現(xiàn)android.view.PhoneWindow,需要時(shí)你應(yīng)該實(shí)例化窗口。
*/
public abstract class Window {
/** Flag for the "options panel" feature. This is enabled by default. */
public static final int FEATURE_OPTIONS_PANEL = 0;
/** Flag for the "no title" feature, turning off the title at the top
* of the screen. */
public static final int FEATURE_NO_TITLE = 1;
.....此處代碼省略
}
( 這個(gè)抽象類是唯一現(xiàn)有的實(shí)現(xiàn)android.view.PhoneWindow,需要時(shí)你應(yīng)該實(shí)例化窗口擎值。)
根據(jù)這句話我們可以看出PhoneWindow實(shí)現(xiàn)了這個(gè)抽象類,所以我們需要進(jìn)入到PhoneWindow查看setContentView(int layoutResID)方法的具體實(shí)現(xiàn)
public class PhoneWindow extends Window implements MenuBuilder.Callback {
....此處省略若干代碼
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
....此處省略若干代碼
}
我們進(jìn)入到 setContentView方法中查看后發(fā)現(xiàn)薛窥,這個(gè)方法主要的過程在 installDecor()方法和mLayoutInflater.inflate(layoutResID, mContentParent)袜蚕。接下來我們主要進(jìn)入到installDecor() 方法(這個(gè)方法我們可以理解為加載DecorView的一個(gè)過程)。通過它横殴,我們可以去了解加載Decor的整個(gè)過程。
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
.....此處省略若干代碼
}
}
通過以上源碼我們可以發(fā)現(xiàn)兩個(gè)方法
- mDecor = generateDecor(-1)-->(創(chuàng)建了一個(gè)DecorView直接返回)
- mContentParent = generateLayout(mDecor)-->(傳入了DecorView,并將layoutResource基礎(chǔ)布局添加(加載)到了DecorView上)
1.generateDecor()源碼:
protected DecorView generateDecor(int featureId) {
// System process doesn't have application context and in that case we need to directly use
// the context we have. Otherwise we want the application context, so we don't cling to the
// activity.
Context context;
if (mUseDecorContext) {
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null) {
context = getContext();
} else {
context = new DecorContext(applicationContext, getContext());
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
//這里是重點(diǎn) 我們可以看到返回了一個(gè)DecorView對(duì)象 然后傳入到了generateLayout(mDecor)方法中
return new DecorView(context, featureId, this, getAttributes());
}
這里我們可以簡(jiǎn)單看下DecorView的源碼,到底是用來干什么的
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
...省略代碼
}
我們可以看到DecorView繼承自FrameLayout,說明DecorView是一個(gè)容器,所以 generateDecor(int featureId)方法就是獲取一個(gè)DecorView容器
2.generateLayout(mDecor)源碼
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.設(shè)置系統(tǒng)當(dāng)前主題
TypedArray a = getWindowStyle();
if (false) {
System.out.println("From style:");
String s = "Attrs:";
for (int i = 0; i < R.styleable.Window.length; i++) {
s = s + " " + Integer.toHexString(R.styleable.Window[i]) + "="
+ a.getString(i);
}
System.out.println(s);
}
mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
& (~getForcedWindowFlags());
if (mIsFloating) {
setLayout(WRAP_CONTENT, WRAP_CONTENT);
setFlags(0, flagsToUpdate);
} else {
setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
}
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
requestFeature(FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
requestFeature(FEATURE_ACTION_BAR);
}
if (a.getBoolean(R.styleable.Window_windowActionBarOverlay, false)) {
requestFeature(FEATURE_ACTION_BAR_OVERLAY);
}
if (a.getBoolean(R.styleable.Window_windowActionModeOverlay, false)) {
requestFeature(FEATURE_ACTION_MODE_OVERLAY);
}
...省略
// Inflate the window decor.
int layoutResource;
int features = getLocalFeatures();
...省略部分
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(
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;
}
// System.out.println("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();
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
...省略部分
return contentParent;
}
generateLayout方法通過源碼我們可以看出他做了如下工作:
1.通過 setFlags(0, flagsToUpdate);和 requestFeature();一系列方法設(shè)置了系統(tǒng)主題等配置
2.解析窗口View
根據(jù)不同的features獲取不同的layoutResource ,然后通過DecorView的onResourcesLoaded方法將layoutResource加載到DecorView中
3.根據(jù)layoutResource我們可以通過 ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);獲取到對(duì)應(yīng)的contentParent,并且返回
/**
* The ID that the main layout in the XML layout file should have.
*/
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
ID_ANDROID_CONTENT就是XML中容器的ID,我們可以進(jìn)入到 如:R.layout.screen_simple 中查看
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
<ViewStub android:id="@+id/action_mode_bar_stub"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?attr/actionBarTheme" />
<FrameLayout
android:id="@android:id/content"http://這里就是我們需要的ID
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundInsidePadding="false"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
當(dāng)我們返回contentParent后,generateLayout方法也就執(zhí)行完了,installDecor執(zhí)行的整個(gè)過程其實(shí)就是在獲取DecorView,然后再將layoutResource加載到DecorView中,最終我們又通過com.android.internal.R.id.content這個(gè)id獲取到contentParent,根據(jù)layout代碼我們可以看出contentParent實(shí)際上就是一個(gè)FrameLayout容器
在instalDecor中執(zhí)行完generateDecor和generateLayout方法后我們獲取到contentParent我們回到setContentView方法中
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
installDecor();//執(zhí)行完 獲取到contentParent
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
//通過此方法將我們的layoutResID綁定到mContentParent上
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
總結(jié):
1.在PhoneWindow中我們會(huì)創(chuàng)建一個(gè)頂層布局容器DecorView(是繼承自FrameLayout的)
2.創(chuàng)建完DecorView后,我們會(huì)將系統(tǒng)布局加載到DecorView中
3.獲取到基礎(chǔ)布局中基礎(chǔ)容器(R.id.content),這個(gè)容器實(shí)際上是一個(gè)FrameLayout
4.最終將我們通過setContentView傳遞過來的資源ID綁定到基礎(chǔ)容器中
圖解:
暫時(shí)寫到這里后續(xù)補(bǔ)充..........