android 首頁布局變換,Android XML布局與View之間的轉換

Android的布局方式有兩種,一種是通過xml布局,一種是通過java代碼布局,兩種布局方式各有各的好處,當然也可以相互混合使用。很多人都習慣用xml布局,那xml布局是如何轉換成view的呢?本文從源碼的角度來簡單分析下整個過程。

首先,創建一個新的項目,默認生成一個activity,其中xml布局很簡單,就一個RelativeLayout套了一個ImageView,代碼及效果如下:

public class MainActivity extends Activity {

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

}

}

b081e3fbe4ee

界面1

其中關鍵之處在于調用了父類Activity的setContentView方法:

/**

* 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.

*/

public void setContentView(int layoutResID) {

getWindow().setContentView(layoutResID);

}

getWindow返回的是PhoneWindow實例,那我們直接來看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) {

cb.onContentChanged();

}

}

我們知道每個activity實際都對應一個PhoneWindow,擁有一個頂層的DecorView,DecorView繼承自FrameLayout,作為根View,其中包含了一個標題區域和內容區域,這里的mContentParent就是其內容區域。關于PhoneWindow和DecorView的具體內容,讀者可自行查閱。這段代碼的意思很簡單,如果DecorView的內容區域為null,就先初始化,否則就先把內容區域的子View全部移除,最后再引入layout布局,所以,關鍵在于mLayoutInflater.inflate(layoutResID, mContentParent); 代碼繼續往下看:

public View inflate(int resource, ViewGroup root) {

return inflate(resource, root, root != null);

}

public View inflate(int resource, ViewGroup root, boolean attachToRoot) {

if (DEBUG) System.out.println("INFLATING from resource: " + resource);

XmlResourceParser parser = getContext().getResources().getLayout(resource);

try {

return inflate(parser, root, attachToRoot);

} finally {

parser.close();

}

}

這里首先根據layout布局文件的Id生成xml資源解析器,然后再調用inflate(parser, root, attachToRoot)生成具體的view。XmlResourceParser是繼承自XmlPullParser和AttributeSet的接口,這里的parser其實是XmlBlock的內部類Parser的實例。

public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {

synchronized (mConstructorArgs) {

final AttributeSet attrs = Xml.asAttributeSet(parser);

Context lastContext = (Context)mConstructorArgs[0];

mConstructorArgs[0] = mContext;

View result = root;

try {

// Look for the root node.

int type;

while ((type = parser.next()) != XmlPullParser.START_TAG &&

type != XmlPullParser.END_DOCUMENT) {

// Empty

}

if (type != XmlPullParser.START_TAG) {

throw new InflateException(parser.getPositionDescription()

+ ": No start tag found!");

}

final String name = parser.getName();

if (DEBUG) {

System.out.println("**************************");

System.out.println("Creating root view: "

+ name);

System.out.println("**************************");

}

if (TAG_MERGE.equals(name)) {

if (root == null || !attachToRoot) {

throw new InflateException(" can be used only with a valid "

+ "ViewGroup root and attachToRoot=true");

}

rInflate(parser, root, attrs);

} else {

// Temp is the root view that was found in the xml

View temp = createViewFromTag(name, attrs);

ViewGroup.LayoutParams params = null;

if (root != null) {

if (DEBUG) {

System.out.println("Creating params from root: " +

root);

}

// 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);

}

}

if (DEBUG) {

System.out.println("-----> start inflating children");

}

// Inflate all children under temp

rInflate(parser, temp, attrs);

if (DEBUG) {

System.out.println("-----> done inflating children");

}

// We are supposed to attach all the views we found (int temp)

// to root. Do that now.

if (root != null && attachToRoot) {

root.addView(temp, params);

}

// Decide whether to return the root that was passed in or the

// top view found in xml.

if (root == null || !attachToRoot) {

result = temp;

}

}

} catch (XmlPullParserException e) {

InflateException ex = new InflateException(e.getMessage());

ex.initCause(e);

throw ex;

} catch (IOException e) {

InflateException ex = new InflateException(

parser.getPositionDescription()

+ ": " + e.getMessage());

ex.initCause(e);

throw ex;

} finally {

// Don't retain static reference on context.

mConstructorArgs[0] = lastContext;

mConstructorArgs[1] = null;

}

return result;

}

}

第21行,獲取xml根節點名:

final String name = parser.getName();

第39行根據節點名創建臨時View(temp),這個臨時view(temp)也是xml布局的根view:

View temp = createViewFromTag(name, attrs);

第61行,在臨時view(temp)的節點下創建所有子View,顯然這個方法里是通過遍歷xml所有子view節點,調用createViewFromTag方法生成子view并加載到根view中:

rInflate(parser, temp, attrs);

第68到76行,則是判斷,如果inflate方法有父view,則把臨時view(temp)加載到父view中再返回,如果沒有,則直接返回臨時view(temp),我們這里調用inflate方法的時候顯然有父view,即mContentParent,也就是最頂層view DecorView的內容區域。這里最關鍵有兩個方法,一個是createViewFromTag,另一個是rInflate,現在來逐一分析:createViewFromTag實際最終調用的是createView方法:

public final View createView(String name, String prefix, AttributeSet attrs)

throws ClassNotFoundException, InflateException {

Constructor constructor = sConstructorMap.get(name);

Class clazz = null;

try {

if (constructor == null) {

// Class not found in the cache, see if it's real, and try to add it

clazz = mContext.getClassLoader().loadClass(

prefix != null ? (prefix + name) : name);

if (mFilter != null && clazz != null) {

boolean allowed = mFilter.onLoadClass(clazz);

if (!allowed) {

failNotAllowed(name, prefix, attrs);

}

}

constructor = clazz.getConstructor(mConstructorSignature);

sConstructorMap.put(name, constructor);

} else {

// If we have a filter, apply it to cached constructor

if (mFilter != null) {

// Have we seen this name before?

Boolean allowedState = mFilterMap.get(name);

if (allowedState == null) {

// New class -- remember whether it is allowed

clazz = mContext.getClassLoader().loadClass(

prefix != null ? (prefix + name) : name);

boolean allowed = clazz != null && mFilter.onLoadClass(clazz);

mFilterMap.put(name, allowed);

if (!allowed) {

failNotAllowed(name, prefix, attrs);

}

} else if (allowedState.equals(Boolean.FALSE)) {

failNotAllowed(name, prefix, attrs);

}

}

}

Object[] args = mConstructorArgs;

args[1] = attrs;

return (View) constructor.newInstance(args);

} catch (NoSuchMethodException e) {

InflateException ie = new InflateException(attrs.getPositionDescription()

+ ": Error inflating class "

+ (prefix != null ? (prefix + name) : name));

ie.initCause(e);

throw ie;

} catch (ClassNotFoundException e) {

// If loadClass fails, we should propagate the exception.

throw e;

} catch (Exception e) {

InflateException ie = new InflateException(attrs.getPositionDescription()

+ ": Error inflating class "

+ (clazz == null ? "" : clazz.getName()));

ie.initCause(e);

throw ie;

}

}

其實這個方法很簡單,就是通過xml節點名,通過反射獲取view的實例再返回,其中先去map中查詢構造函數是否存在,如果存在則直接根據構造函數創建實例,這樣做的好處是不用每次都通過class去獲取構造函數再創建實例,我們看第18行通過類實例獲取構造函數:

constructor = clazz.getConstructor(mConstructorSignature);

其中mConstructorSignature定義如下:

private static final Class[] mConstructorSignature = new Class[] {

Context.class, AttributeSet.class};

很顯然,這里用的是帶有Context和AttributeSet兩個參數的構造函數,這也就是為什么,自定義view一定要重載這個構造函數的原因。最后就是rInflate方法:

private void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs)

throws XmlPullParserException, IOException {

final int depth = parser.getDepth();

int type;

while (((type = parser.next()) != XmlPullParser.END_TAG ||

parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

if (type != XmlPullParser.START_TAG) {

continue;

}

final String name = parser.getName();

if (TAG_REQUEST_FOCUS.equals(name)) {

parseRequestFocus(parser, parent);

} else if (TAG_INCLUDE.equals(name)) {

if (parser.getDepth() == 0) {

throw new InflateException(" cannot be the root element");

}

parseInclude(parser, parent, attrs);

} else if (TAG_MERGE.equals(name)) {

throw new InflateException(" must be the root element");

} else {

final View view = createViewFromTag(name, attrs);

final ViewGroup viewGroup = (ViewGroup) parent;

final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);

rInflate(parser, view, attrs);

viewGroup.addView(view, params);

}

}

parent.onFinishInflate();

}

實這個方法也很簡單,就是通過parser解析xml節點再生成對應View的過程。

XML轉換成View的過程就是這樣了,如有錯誤之處,還望指正,回到本文開頭,其實我們還可以這樣寫:

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

View content = LayoutInflater.from(this).inflate(R.layout.activity_main, null);

setContentView(content);

}

b081e3fbe4ee

界面2

大家發現問題沒,相較于本文開頭的寫法,后面的灰色布局變成全屏了,我們來看看xml代碼:

xmlns:tools="http://schemas.android.com/tools"

android:layout_width="300dip"

android:layout_height="300dip"

android:background="#888888"

tools:context=".MainActivity" >

android:layout_width="200dip"

android:layout_height="200dip"

android:background="#238712"

android:contentDescription="@null" />

我明明設置了RelativeLayout的寬度和高度分別為300dip,但為什么全屏了?這是因為layout_width和layout_height是相對于父布局而言的,我們這里inflate的時候設置的父布局為null,所以這個屬性設置也就無效了,指定一個父布局就可以了,例如:

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

RelativeLayout rootView = new RelativeLayout(this);

View content = LayoutInflater.from(this).inflate(R.layout.activity_main, rootView);

setContentView(content);

}

現在,界面顯示效果就和“界面1”相同了。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/270350.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/270350.shtml
英文地址,請注明出處:http://en.pswp.cn/news/270350.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

C++的ORM工具比較

用過Java的都知道SSH框架,特別對于數據庫開發,Java領域有無數的ORM框架,供數據持久層調用,如Hibernate,iBatis(現在改名叫MyBatis),TopLink,JDO,JPA……非常方便實用。用過C#的同學們…

電腦技巧:Win10自帶存儲感知功能給電腦磁盤瘦身

今天給大家分享Win10自帶存儲感知功能給電腦磁盤瘦身功能,希望對大家能有所幫助!1、什么是存儲感知Win10存儲感知功能屬于Win10操作系統的一大亮點,自帶有AI的存儲感知功能發揮其磁盤清理功能,它可以在操作系統需要的情況下清理不…

線程的優先級

setPriority(); 設置線程的優先級Thread類里面的 MIN_PRIORITY 1 表示最小優先級 NORM_PRIORITY 5 表示默認優先級 MAX_PRIORITY 10 表示最大優先級

電腦存儲:A盤、B盤知識介紹,為何總是電腦磁盤從C盤開始

??作者主頁:IT技術分享社區 ??作者簡介:大家好,我是IT技術分享社區的博主,從事C#、Java開發九年,對數據庫、C#、Java、前端、運維、電腦技巧等經驗豐富。 ??個人榮譽: 數據庫領域優質創作者🏆&#x…

使用axis發送xml報文,返回并解析報文實例

前段時間剛好學了一點webservice,因此想和大家分享一下我的成果,因為能力原因,只能做個小實例,望大家見諒! 此實例的思路是:(1)用String類型構造好需要發送的報文;(2)使用axis調用服…

某游戲在華為鴻蒙,華為鴻蒙系統運行安卓游戲出現新狀況!安卓換皮論被徹底打臉?...

雖然華為鴻蒙系統已經經過幾輪的測試,準備在6月份大規模推送正式版本了,但現在依然還有一些雜音不絕于耳。最主要的爭議點還是老生常談的”鴻蒙系統到底是不是安卓系統的換皮“。支持鴻蒙系統安卓換皮的一方拿出過不少證據,比如安裝包還是APK…

java為什么還需要分布式鎖?

什么是鎖? 在單進程的系統中,當存在多個線程可以同時改變某個變量(可變共享變量)時,就需要對變量或代碼塊做同步,使其在修改這種變量時能夠線性執行消除并發修改變量。 而同步的本質是通過鎖來實現的。為了…

電腦技巧:電腦插上U盤就死機或重啟原因和解決辦法

大家平時將u盤或其他可移動設備連接電腦插口的時候,不少電腦小白都碰到過操作系統自動重啟甚至出現電腦死機的尷尬情況。針對u盤等外部設備連接引起的電腦死機問題,我們應該根據具體問題具體分析,找到原因就可以解決掉。接下來小編帶大家看看…

使用UGUI繪制自定義幾何圖形

本文展示了如何使用UGUI繪制矩形,同理可繪制其他幾何圖形。 UGUI的渲染體系,簡單來說所有的控件和可顯示的元素都是Graphic。Graphic持有一個CanvasRenderer,通過SetVertices設置頂點,最終完成繪制。 舉例來說,Image控…

android t類型參數,android – Kotlin屬性:“屬性的類型參數必須在其接收器類型中使用”...

我有以下簡單的Kotlin擴展功能:// Get the views of ViewGroupinline val ViewGroup.views: Listget() (0..childCount - 1).map { getChildAt(it) }// Get the views of ViewGroup of given typeinline fun ViewGroup.getViewsOfType() : List {return this.views…

電腦技巧:分享七個解決煩人的彈窗廣告的小技巧

目錄 1、及時卸載用不到的垃圾軟件 2、修改軟件設置(關閉不需要的資訊、廣告) 3、開機啟動項中禁用不需要的應用 4、刪除彈窗廣告程序 5、提高操作系統阻止級別 6、禁止彈窗廣告的任務計劃 7、安裝火絨殺毒軟件設置彈窗廣告攔截 最后總結 很多朋友經常會…

MySQL+Amoeba實現數據庫主從復制和讀寫分離

MySQL讀寫分離是在主從復制的基礎上進一步通過在master上執行寫操作,在slave上執行讀操作來實現的。通過主從復制,master上的數據改動能夠同步到slave上,從而保持了數據的一致性。實現數據的讀寫分離能帶來的好處有: 增加物理服務…

從操作系統層面描述線程的五種狀態

[初始狀態] 僅是在語言層面創建 了線程對象, 還未與操作系統線程關聯 [可運行狀態] (就緒狀態) 指該線程已經被創建(與操作系統線程關聯), 可以由CPU調度執行 [運行狀態] 指獲取了CPU時間片運行中的狀態 當CPU時間片用完,會從[運行狀態]轉…

html桌面圖標樣式,如何更改圖標樣式,換桌面圖標的方法

打開桌面,桌面上擺放了放多程序的快捷方式,我們每天打開電腦最先接觸的就是這些快捷方式圖標。時間長了,是不是感覺乏味了。可以換一換。比如,在我的電腦桌面上有這樣一個圖標,那怎樣更改桌面圖標?下面&…

電腦知識:筆記本電腦邊充電邊用,對電池有損害嗎?

使用筆記本的時候,你們有沒有這樣的習慣,就是插電使用,充滿到100%也不會拔掉充電頭。 有人說這種行為會對電腦的電池造成傷害,這是真的嗎?到底正確的使用是怎么樣的?今天就跟大家解答一下這個疑惑。 首先&a…

硬件知識:電源開關上的“1“和“0“分別是什么意思

幾乎所有的電器、燈具和插座上 只要帶有電源開關 必然會出現“|”和“O”兩個符號 如果只看符號判斷 “|”和“O”到底代表什么含義呢? 你又能分清哪個是電路聯通 哪個是電路斷開嗎? 很多人認為“O”是通電,“|”是斷電 因為英語里開是OPEN 很…

c# 正則表達式 html標簽,C#匹配HTML標簽,正則表達式誰會?

米脂JS:function StripHtml(html){var scriptregex ".]*>[sS]*?";var scripts new RegExp(scriptregex, "gim");html html.replace(scripts, " ");//Stripts the ";var styles new RegExp(styleregex , "gim");html htm…

Visual Studio 2013 添加一般應用程序(.ashx)文件到SharePoint項目

默認,在用vs2013開發SharePoint項目時,vs沒有提供一般應用程序(.ashx)的項目模板,本文解決此問題。 以管理員身份啟動vs2013,創建一個"SharePoint 2013 - 空項目",名稱我保持默認:SharePointProject2。 選擇…