本系列文章循序漸進(jìn)的學(xué)習(xí)Android View的使用和核心源碼分析。
Android View (1) View的樹形結(jié)構(gòu)和坐標(biāo)計(jì)算
Android View (2) View的加載過程
Android View (3) View LayoutInflater 源碼分析
Android View (4) View的繪制過程
View的加載過程
android 在加載視圖的過程是通過在Activity的 setContentView(@LayoutRes int layoutResID) 來加載的,我們通過分析源碼(在線源碼鏈接)過程來理解。
- setContentView(int layoutResID)源碼分析
上篇我們說過WindowPhone類繼承了Window類,Window類只是定義了一些方法和規(guī)范,所以我們直接看WindowPhone類中的方法。有三個(gè)重載方法,我們只分析一個(gè),代碼如下:
@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.
//判斷是否第一次進(jìn)入
if (mContentParent == null) {
//創(chuàng)建DecorView,并添加布局到mContentParent
installDecor();
//hasFeature(FEATURE_CONTENT_TRANSITIONS)是否設(shè)置了過場動(dòng)畫
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
//清空之前的View
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
//通過Scene執(zhí)行過場動(dòng)畫(執(zhí)行過程中會(huì)清空之前的View和添加新的布局)
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
//向mContentParent添加布局
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
//回調(diào)通知表示完成界面加載
cb.onContentChanged();
}
// Whether the client has explicitly set the content view. If false and mContentParent is notnull, then the content parent was set due to window preservation.
//是否顯式的設(shè)置了視圖
mContentParentExplicitlySet = true;
}
接下來看installDecor() 代碼如下:
private void installDecor() {
2640 mForceDecorInstall = false;
// 如果mDecor為空,則生成一個(gè)Decor,并設(shè)置其屬性
2641 if (mDecor == null) {
// 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.
//generateDecor()主要用于創(chuàng)建Decor且不依賴與Activity
2642 mDecor = generateDecor(-1);
//設(shè)置父View 與子View的聚焦關(guān)系
2643 mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
// 設(shè)置mDecor為整個(gè)Activity窗口的根節(jié)點(diǎn),從此處可以看出窗口根節(jié)點(diǎn)為一個(gè)DecorView
2644 mDecor.setIsRootNamespace(true);
// 滿足條件執(zhí)行動(dòng)畫操作
2645 if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
2646 mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
2647 }
2648 } else {
2649 mDecor.setWindow(this);
2650 }
2651 if (mContentParent == null) {
//根據(jù)窗口的風(fēng)格修飾,選擇對應(yīng)的修飾布局文件,并且將id為content的FrameLayout賦值給mContentParent
2652 mContentParent = generateLayout(mDecor);
2653
2654 // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
2655 mDecor.makeOptionalFitsSystemWindows();
2656 //獲取DecorView頂級布局,提供一些設(shè)置title,window主題等的方法
2657 final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
2658 R.id.decor_content_parent);
2659
2660 if (decorContentParent != null) {
2661 //1. 將decorContentParent賦值給mDecorContentParent
//2. 設(shè)置窗口回調(diào)函數(shù)
//3.設(shè)置窗口的title、icon、logo等屬性值
2699 } else {
2700 mTitleView = findViewById(R.id.title);
//這里有一個(gè)我們?nèi)粘J褂玫腁ctionBar是否顯示的判斷,所以我們的設(shè)置要在setContentView方法之前
2701 if (mTitleView != null) {
2702 if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
2703 final View titleContainer = findViewById(R.id.title_container);
2704 if (titleContainer != null) {
2705 titleContainer.setVisibility(View.GONE);
2706 } else {
2707 mTitleView.setVisibility(View.GONE);
2708 }
2709 mContentParent.setForeground(null);
2710 } else {
2711 mTitleView.setText(mTitle);
2712 }
2713 }
2714 }
2715
2716 if (mDecor.getBackground() == null && mBackgroundFallbackResource != 0) {
2717 mDecor.setBackgroundFallback(mBackgroundFallbackResource);
2718 }
2719 //....好多關(guān)于背景,動(dòng)畫等的判斷,省略
2770 }
2771 }
2772 }
2773 }
接下來分析generateLayout(DecorView decor)方法的源碼
//返回當(dāng)前Activity的內(nèi)容區(qū)域視圖,即我們的布局文件顯示區(qū)域mContentParent
protected ViewGroup generateLayout(DecorView decor) {
2309 // Apply data from current theme.
2310 //獲取屬性值
2311 TypedArray a = getWindowStyle();
2312 //根據(jù)Them來設(shè)置或修改各種屬性,選定不同的資源文件等
.......
//設(shè)置title和background屬性
2627 }
2628
2629 mDecor.finishChanging();
2630
2631 return contentParent;
2632 }
其實(shí)在geneateLayout中會(huì)根據(jù)我們在清單文件中設(shè)置的主題來進(jìn)行加載相應(yīng)的主題,布局等等。
總結(jié)
- 梳理setContentView()加載布局的過程:首先如果是首次調(diào)用的話,我們會(huì)創(chuàng)建DecorView對象作為Activity根視圖,如果不是第一次調(diào)用,我們只需要移除之前的布局,繼續(xù)用DecorView對象去作為根視圖, 在加載布局過程中會(huì)通過設(shè)置的ThemStyle 設(shè)置一些屬性值,然后通過findviewbyid的方式將跟布局文件添加到DecorView中,然后通過回調(diào)Activity的onContentChanged方法,通知布局加載完成
- 在加載View的過程中,系統(tǒng)是通過遞歸遍歷一步步的進(jìn)行解析加載的,所以我們在開發(fā)中,盡量不要嵌套太多層的布局,會(huì)影響到布局的加載效率,可以通過merge標(biāo)簽來減少嵌套層數(shù)。
參考
- 源碼:http://androidxref.com/8.0.0_r4/xref/frameworks/base/core/
- 扔物線 博客:http://hencoder.com/
- 簡書:
http://www.itdecent.cn/p/f76a6f6f75aa setContentView 加載視圖機(jī)制
http://www.itdecent.cn/p/0819a858b53c setContentView 源碼分析
http://www.itdecent.cn/p/bb7977990baa android view繪制流程,源碼解讀
http://www.itdecent.cn/p/b272528165a2 android 自定義view - ColorFilter:https://github.com/chengdazhi/StyleImageView(自定義view的玩法)