android 代码加view,Android中将View添加至窗口的源码分析
本文主要內(nèi)容是講解一個視圖View或者一個ViewGroup對象是如何添加至應用程序窗口中的。下文中提到的窗口可泛指我們能看到的界面,包括一個Activity呈現(xiàn)的界面(我們可以將之理解為應用程序窗口),一個Dialog,一個Toast,一個Menu菜單等。
首先先來看一個UI框架的關(guān)系圖
然后再來對相關(guān)類的作用進行一下簡單介紹,這樣你的印象才深刻:
Window 類
位于/frameworks/base/core/java/android/view/Window.java
說明:該類是一個抽象類,提供了繪制窗口的一組通用API。可以將之理解為一個載體,各種View在這個載體上顯示。
源文件(部分)如下:
public abstract class Window {
//...
//指定Activity窗口的風格類型
public static final int FEATURE_NO_TITLE = 1;
public static final int FEATURE_INDETERMINATE_PROGRESS = 5;
//設置布局文件
public abstract void setContentView(int layoutResID);
public abstract void setContentView(View view);
//請求指定Activity窗口的風格類型
public boolean requestFeature(int featureId) {
final int flag = 1<
mFeatures |= flag;
mLocalFeatures |= mContainer != null ? (flag&~mContainer.mFeatures) : flag;
return (mFeatures&flag) != 0;
}
//...
}
PhoneWindow類
/frameworks/policies/base/phone/com/android/internal/policy/impl/PhoneWindow.java
說明:該類繼承于Window類,是Window類的具體實現(xiàn),即我們可以通過該類具體去繪制窗口。該類內(nèi)部包含了一個DecorView對象,該DecorView對象是所有應用窗口(Activity界面)的根View。PhoneWindow類:把一個FrameLayout類即DecorView對象進行一定的包裝,將其作為應用窗口的根View,并提供一組通用的窗口操作接口。
源文件(部分)如下:
public class PhoneWindow extends Window implements MenuBuilder.Callback {
//...
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor; //該對象是所有應用窗口的根視圖 , 是FrameLayout的子類
//該對象是Activity布局文件的父視圖,一般來說是一個FrameLayout型的ViewGroup
// 同時也是DecorView對象的一個子視圖
// This is the view in which the window contents are placed. It is either
// mDecor itself, or a child of mDecor where the contents go.
private ViewGroup mContentParent;
//設置標題
@Override
public void setTitle(CharSequence title) {
if (mTitleView != null) {
mTitleView.setText(title);
}
mTitle = title;
}
//設置背景圖片
@Override
public final void setBackgroundDrawable(Drawable drawable) {
if (drawable != mBackgroundDrawable || mBackgroundResource != 0) {
mBackgroundResource = 0;
mBackgroundDrawable = drawable;
if (mDecor != null) {
mDecor.setWindowBackground(drawable);
}
}
}
//...
}
DecorView類
說明:該類是FrameLayout的子類,并且是PhoneWindow的內(nèi)部類。該類就是對普通的FrameLayout進行功能的擴展,更確切點可以說是修飾(Decor的英文全稱是Decoration,即“修飾”的意思),比如說添加TitleBar(標題欄),以及 TitleBar上的滾動條等 。**最重要的一點是,它是所有應用窗口的根View 。
如下所示:
**DecorView根視圖結(jié)構(gòu)**
** DecorView 根視圖形式**
源文件(部分)如下:
private final class DecorView extends FrameLayout {
//...
//觸摸事件處理
@Override
public boolean onTouchEvent(MotionEvent event) {
return onInterceptTouchEvent(event);
}
//...
}
打個不恰當比喻吧,Window類相當于一幅畫(抽象概念,什么畫我們未知) ,PhoneWindow為一副齊白石先生的山水畫(具體概念,我們知道了是誰的、什么性質(zhì)的畫),DecorView則為該山水畫的具體內(nèi)容(有山、有水、有樹,各種界面)。DecorView呈現(xiàn)在PhoneWindow上。
當系統(tǒng)(一般是ActivityManagerService)配置好啟動一個Activity(activity對象和window對象信息)的相關(guān)參數(shù)后,就會調(diào)用Activity的onCreate()方法,在其中通過設置setContentView()方法設置該Activity的顯示界面,整個調(diào)用環(huán)節(jié)由此鋪開。setContentView()的三個構(gòu)造方法調(diào)用流程本質(zhì)上一樣,我們分析其中一個setContentView(int resId)方法。
###Step1、Activity.setContentView(int resId)該方法在Activity類中,該方法只是簡單的回調(diào)Window對象,具體為PhoneWindow對象的setContentView()方法實現(xiàn) 。
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
}
public Window getWindow() {
return mWindow; //Window對象,本質(zhì)上是一個PhoneWindow對象
}
###Step2、PhoneWindow.setContentView() 該方法在PhoneWindow類中
@Override
public void setContentView(int layoutResID) {
//是否是第一次調(diào)用setContentView方法, 如果是第一次調(diào)用,則mDecor和mContentParent對象都為空
if (mContentParent == null) {
installDecor();
} else {
mContentParent.removeAllViews();
}
mLayoutInflater.inflate(layoutResID, mContentParent);
final Callback cb = getCallback();
if (cb != null) {
cb.onContentChanged();
}
}
首先判斷是否已經(jīng)由setContentView()獲取了mContentParent(即View)對象,即是否第一次調(diào)用PhoneWindow對象的setContentView()方法。 如果第一次調(diào)用,則調(diào)用installDecor()方法,否則移除該View內(nèi)的所有子View。最后將我們的資源文件(一般為XML文件)通過LayoutInflater對象轉(zhuǎn)化成View樹,并且添加到mContentParent視圖中。
###Step3、PhoneWindow.installDecor()
private void installDecor() {
if (mDecor == null) {
//mDecor為空,則創(chuàng)建一個Decor對象
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
}
if (mContentParent == null) {
//generateLayout()方法會根據(jù)窗口的風格修飾,選擇對應的修飾布局文件
//并且將id為content(android:id="@+id/content")的FrameLayout賦值給mContentParent
mContentParent = generateLayout(mDecor);
//...
}
首先判斷mDecor對象是否為空,不為空,則調(diào)用generateDecor()創(chuàng)建一個DecorView(該類是FrameLayout子類,即一個ViewGroup視圖)
generateDecor()方法原型:
proteced DecorView generateDecor(){
return new DecorView(getContext(), -1);
}
其次繼續(xù)判斷mContentParent對象是否為空,如果不為空,則調(diào)用generateLayout()方法去創(chuàng)建mContentParent對象。
generateLayout()方法:
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
//...1、根據(jù)requestFreature()和Activity節(jié)點的android:theme="" 設置好 features值
//2 根據(jù)設定好的 features值,即特定風格屬性,選擇不同的窗口修飾布局文件
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) {
layoutResource = com.android.internal.R.layout.dialog_title_icons;
} else {
layoutResource = com.android.internal.R.layout.screen_title_icons;
}
// System.out.println("Title Icons!");
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 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!");
}
//...
//3 選定了窗口修飾布局文件 ,添加至DecorView對象里,并且指定mcontentParent值
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
ProgressBar progress = getCircularProgressBar(false);
if (progress != null) {
progress.setIndeterminate(true);
}
}
//...
return contentParent;
}
該方法會做如下事情:
1、根據(jù)窗口的風格修飾類型為該窗口選擇不同的窗口布局文件(根視圖)。這些窗口修飾布局文件指定一個用來存放Activity自定義布局文件的ViewGroup視圖,一般為FrameLayout 其id 為:
android:id="@android:id/content"
例如窗口修飾類型包括FullScreen(全屏)、NoTitleBar(不含標題欄)等。選定窗口修飾類型有兩種:
①、指定requestFeature()窗口修飾符,PhoneWindow對象調(diào)用getLocalFeature()方法獲取值;
②、為我們的Activity配置相應屬性,即android:theme=“”,PhoneWindow對象調(diào)用getWindowStyle()方法獲取值。
舉例如下,隱藏標題欄有如下方法:requestWindowFeature(Window.FEATURE_NO_TITLE);或者 為Activity配置xml屬性:android:theme=”@android:style/Theme.NoTitleBar”。
PS:因此,在Activity中必須在setContentView之前調(diào)用requestFeature()方法。
確定好窗口風格之后,選定該風格對應的布局文件,這些布局文件位于 frameworks/base/core/res/layout/ ,
典型的窗口布局文件有:
R.layout.dialog_titile_icons
R.layout.screen_title_icons
R.layout.screen_progress
R.layout.dialog_custom_title
R.layout.dialog_title
R.layout.screen_title// 最常用的Activity窗口修飾布局文件
R.layout.screen_simple//全屏的Activity窗口布局文件
** 分析Activity最常用的一種窗口布局文件,R.layout.screen_title :**
android:orientation="vertical"
android:fitsSystemWindows="true">
android:layout_width="match_parent"
android:layout_height="?android:attr/windowTitleSize"
style="?android:attr/windowTitleBackgroundStyle">
style="?android:attr/windowTitleStyle"
android:background="@null"
android:fadingEdge="horizontal"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
該布局文件很簡單,一個LinearLayout下包含了兩個子FrameLayout視圖,第一個FrameLayout用來顯示標題欄(TitleBar),該TextView 視圖id為title(android:id="@android:id/title");第二個FrameLayout用來顯示我們Activity的布局文件的父視圖,該FrameLayoutid為content(android:id="@android:id/content") 。
全屏的窗口布局文件 R.layout.screen_simple:\
enabled.
-->
android:id="@android:id/content"
android:fitsSystemWindows="true"
android:foregroundInsidePadding="false"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
該布局文件只有一個FrameLayout,用來顯示我們Activity的布局文件,該FrameLayoutid為 android:id="@android:id/content"
2、前面我們確定窗口修飾布局文件后,mDecor作為根視圖將窗口布局對應的視圖(view)添加進去,并且獲取id為content的View,將其賦值給mContentParent對象,即我們前面講的第二個FrameLayout.
###At Last、產(chǎn)生了mDecor和mContentParent對象后,將Activity布局文件直接添加到mContentParent父View中。即step2代碼中的如下所示:
mLayoutInflater.inflate(layoutResID, mContentParent);
###整理:整個過程主要是如何把Activity的布局文件添加至窗口中(即PhoneWindow的DecorView中),上面過程概括為:
1、創(chuàng)建一個DecorView對象,作為應用窗口的根View。
2、創(chuàng)建不同的窗口修飾布局文件,并且獲取Activity的布局文件該存放的地方,由該窗口修飾布局文件內(nèi)id為content的FrameLayout指定 。
3、將Activity的布局文件加至id為content的FrameLayout內(nèi)。
4、最后,當ActivityManagerService準備resume一個Activity時,會回調(diào)該Activity的handleResumeActivity()方法, 該方法會調(diào)用Activity的makeVisible方法 ,顯示我們剛才創(chuàng)建的mDecor 視圖族。
//系統(tǒng)resume一個Activity時,調(diào)用此方法
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) {
ActivityRecord r = performResumeActivity(token, clearHide);
//...
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible();
}
}
handleResumeActivity()方法原型如下:位于ActivityThread類中
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager(); // 獲取WindowManager對象
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE); //使其處于顯示狀況
}
接下來就是,如何把我們已經(jīng)創(chuàng)建好的窗口通知給WindowManagerService ,以便它能夠把這個窗口顯示在屏幕上。關(guān)于這方面內(nèi)容大家可以去看鄧凡平老師的這篇博客《Android深入淺出之Surface[1]》
總結(jié)
以上是生活随笔為你收集整理的android 代码加view,Android中将View添加至窗口的源码分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: android列表滑到底部,Androi
- 下一篇: android edittext ema