Android View的layout_width屬性是如何解析的
一 引言
在開發 Android UI 介面時,一般都會在 layout 目錄下新建一個XML檔案,用於編寫佈局檔案。下面是一個簡單的佈局檔案:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
從上面的檔案的佈局檔案可以看到,控制元件由各個屬性組成,如id、layout_width、layout_height等,這些屬性最終都會經系統解析,從而在 View 在measure、layout 及 draw的過程中使用。
那麼問題來了
layout目錄下佈局檔案各View的屬性是如何解析的?
View的屬性很多,下面以 layout_width 屬性的解析過程做簡單的分析。
主要分兩個步驟:
1. 將佈局檔案的屬性解析到 AttributeSet 中
2. 將 AttributeSet 中 layout_width屬性解析到 LayoutParams 的 width域中。
二 layout/layout_name.xml -> AttributeSet
簡單說下AttributeSet,它的作用從名字可以看出了,其實就是屬性的集合,它包含了 View 設定的所有屬性。詳細見 官網 說明。
我們知道,在 Activity onCreate方法 中,將佈局檔案資源id作為引數傳入 setContentView(int layoutResID) 方法中,從而展示佈局。也有通過 adapter 的 getView() 方法中通過 LayoutInflater 將佈局resID顯式 inflate 進去。其實,setContentView最終也是通過 LayoutInflater 將佈局檔案資訊填充到對應的 View 中。原始碼如下:
原始碼路徑: frameworks/base/core/java/android/app/Activity.java
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
activity setContentView方法 通過 getWindow() 獲得 Window 例項,window的實現類是 PhoneWindow, 來看下其對 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();
} 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);
}
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
從上面程式碼可知,當hasFeature(FEATURE_CONTENT_TRANSITIONS)
返回為false時,會呼叫 LayoutInflater 例項的inflate方法,其中引數 layoutResID 即是我們傳入的佈局資源id。
接下來,我們就看下 LayoutInflater 類的 inflate 方法是如何解析佈局資源的。
public View inflate(int resource, ViewGroup root) {
return inflate(resource, root, root != null);
}
inflate 方法呼叫了同名方法,跟進:
public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ Integer.toHexString(resource) + ")");
}
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
首先,通過 getResources 方法獲取 Resource 例項,然後將 resID 傳入該例項的 getLayout 方法,從而返回一個 XmlResourceParser 例項,它是對已經編譯過的xml檔案的封裝,接下來將該 parser 作為引數傳入到 inflate,看下 這個inflate 方法實現邏輯。
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
...
final AttributeSet attrs = Xml.asAttributeSet(parser);
...
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, attrs, false);
ViewGroup.LayoutParams params = null;
if (root != null) {
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
return result;
}
}
這裡的 inflate 方法實現邏輯較長,我們擷取了部分程式碼。重點關注 下final AttributeSet attrs = Xml.asAttributeSet(parser);
到這裡,,AttributeSet 出現了!
Xml將已經封裝的 XmlResourceParser 例項轉化為我們想要的 AttributeSet 物件。
三 將 AttributeSet 中 layout_width屬性解析為LayoutParams 的 width
接下來父View通過 AttributeSet 解析其子View的layout_width屬性,從而將 生成該父View 的LayoutParams width的值。
root.generateLayoutParams(attrs)
其中 attrs 引數是由 Xml 的 asAttributeSet 方法將之前已經封裝的 XmlResourceParser 例項解析而來。root是該我們先前在activity傳入的資源佈局id所對應view的父容器。由其負責解析其子view的 LayoutParams。程式碼如下:
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
繼續跟進 LayoutParams 類的建構函式實現
public LayoutParams(Context c, AttributeSet attrs) {
TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout);
setBaseAttributes(a,
R.styleable.ViewGroup_Layout_layout_width,
R.styleable.ViewGroup_Layout_layout_height);
a.recycle();
}
首先看建構函式體裡面的第一行程式碼
TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout);
obtainStyledAttributes 方法的目的是為了獲取屬性,關於該方法有許多過載方法,如下:
public final TypedArray obtainStyledAttributes(int[] attrs)
用於從系統主題中獲取 attrs 中的屬性
public final TypedArray obtainStyledAttributes(int resid, int[] attrs)
用於從資原始檔定義的 style 中讀取屬性
public final TypedArray obtainStyledAttributes(AttributeSet set, int[] attrs)
從 layout 設定的屬性集中獲取 attrs 中的屬性
這裡用到的是第三個方法,它有兩個引數。第一個引數為 AttributeSet 引用,它是資料來源,表示屬性從哪裡來的,第二個引數 attrs 表示需要獲取哪些屬性。第二個引數傳入的是 R.styleable.ViewGroup_Layout
,它定義在
frameworks/base/core/res/res/values/attrs.xml 檔案中,如下:
<declare-styleable name="ViewGroup_Layout">
<attr name="layout_width" format="dimension">
<enum name="fill_parent" value="-1" />
<enum name="match_parent" value="-1" />
<enum name="wrap_content" value="-2" />
</attr>
<attr name="layout_height" format="dimension">
<enum name="fill_parent" value="-1" />
<enum name="match_parent" value="-1" />
<enum name="wrap_content" value="-2" />
</attr>
</declare-styleable>
在這裡說明下,通過在 attr 檔案中定義styleable,編譯後在 R 檔案中自動生成一個int[],陣列裡面的值就是定義在 styleable 裡面的 attr 的id。下面分析
TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout);
的解析過程:
首先 attrs(屬性源) 引數是對我們一開始就傳入到 Activity setContentView 方法裡面的 layout 檔案id進行解析得到的屬性集合,由 LayoutInflater 載入得到的,R.styleable.ViewGroup_Layout(要獲取的屬性) 就是一個int[],裡面包含了宣告的 attr id。我們知道了屬性源,也知道了需要獲取的屬性集合,通過 obtainStyledAttributes 方法最終得到一個 TypedArray。
TypedArray是什麼鬼?
TypedArray的存在簡化了我們解析屬性的步驟。比如解析 android:text="@string/my_label
,text的屬性值是引用型別。如果直接使用 AttributeSet 解析該屬性,需要兩步:1. 獲取text屬性引用值的id;2. 根據該id去獲取text對應的String值。而有了TypedArray,這些工作交給它就行了。
好了,我們繼續跟著剛才 LayoutParams 建構函式裡的實現,我們進入
protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
width = a.getLayoutDimension(widthAttr, "layout_width");
height = a.getLayoutDimension(heightAttr, "layout_height");
}
到這裡,layout_width 屬性值最終賦給了width。其中widthAttr 的引數值是我們剛剛傳入的R.styleable.ViewGroup_Layout_layout_width。TypedArray根據名稱索引到對應的值。
四 總結
屬性解析的過程說的有些泛,只是瞭解瞭解析的流程,有些內容還需要後面繼續深入瞭解。View屬性的解析設計到兩個類:AttributeSet 和 TypedArray,前者將View設定的所有屬性做了彙集和封裝,後者提供瞭解析屬性值的方法。
相關文章
- Android View動畫和屬性動畫簡單解析:AndroidView動畫
- Android自定義View 屬性新增AndroidView
- Android解析WindowManager(二)Window的屬性Android
- 屬性動畫:如何自定義View動畫View
- Android 自定義View:屬性動畫(六)AndroidView動畫
- iOS動畫 屬性屬性解析iOS動畫
- 如何理解Android屬性動畫Android動畫
- view向全屏延伸時的屬性設定View
- C#中屬性的解析C#
- 這可能是第二好的自定義 View 教程之屬性動畫View動畫
- Android 動畫詳解:屬性動畫、View 動畫和幀動畫Android動畫View
- Android 自定義View:深入理解自定義屬性(七)AndroidView
- Android 自定義view中的屬性,名稱空間,以及tools標籤AndroidView
- 深入解析 Android 中 View 的工作原理AndroidView
- Android View 原始碼解析(三) – View的繪製過程AndroidView原始碼
- 【Android】神奇的android:clipChildren屬性Android
- Android屬性動畫完全解析(中),ValueAnimator和ObjectAnimator的高階用法Android動畫Object
- Android開發 - 檢視佈局屬性解析Android
- android view 的建立解析,攔截view的建立並進行操作(一)AndroidView
- 自定義View:Paint的常用屬性介紹及使用ViewAI
- 新提案,初識CSS的object-view-box屬性CSSObjectView
- js如何判斷物件的屬性值是物件還是陣列JS物件陣列
- js如何獲取給定屬性的屬性值JS
- 給自定義View新增xml屬性ViewXML
- Android 樣式屬性的使用Android
- SAP ABAP CDS view裡的註解在ABAP後臺是如何被解析的?View
- HenCoder Android 自定義 View 1-6: 屬性動畫(上手篇)AndroidView動畫
- 獲取物件屬性型別、屬性名稱、屬性值的研究:反射和JEXL解析引擎物件型別反射
- iOS開發 Runtime是如何實現weak屬性的?iOS
- JavaScript物件屬性是有序的嗎?JavaScript物件
- 什麼是 cookie 的 httponly 屬性CookieHTTP
- Android View的Measure測量流程全解析AndroidView
- js如何判斷屬性是本身具有還是繼承別人的JS繼承
- 如何理解vue的key屬性Vue
- 【朝花夕拾】Android自定義View篇之(四)自定義View的三種實現方式及自定義屬性詳解AndroidView
- Android控制元件的fitSystemWindows屬性Android控制元件Windows
- Android UI系列-----ImageView的scaleType屬性AndroidUIView
- GAME FREAK是如何保持寶可夢的屬性和數值設定平衡的?GAM