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.mzph.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…

电脑技巧:电脑插上U盘就死机或重启原因和解决办法

大家平时将u盘或其他可移动设备连接电脑插口的时候,不少电脑小白都碰到过操作系统自动重启甚至出现电脑死机的尴尬情况。针对u盘等外部设备连接引起的电脑死机问题,我们应该根据具体问题具体分析,找到原因就可以解决掉。接下来小编带大家看看…

使用UGUI绘制自定义几何图形

本文展示了如何使用UGUI绘制矩形,同理可绘制其他几何图形。 UGUI的渲染体系,简单来说所有的控件和可显示的元素都是Graphic。Graphic持有一个CanvasRenderer,通过SetVertices设置顶点,最终完成绘制。 举例来说,Image控…

电脑技巧:分享七个解决烦人的弹窗广告的小技巧

目录 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。 选择…

java中线程的6种状态

java中线程的状态分为6种。 1. 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。 2. 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。 线程对…

软件推荐:微软桌面助手软件上手体验

今天给大家分享微软桌面助手这款软件的使用体验,感兴趣的朋友可以下载体验一下!一、软件介绍微软桌面助手是微软官方发布的一款桌面分区应用,其特色是支持自动移动并根据分区来整理桌面文件,比如在默认设置情况下,可以…