Android Layout Resource分析

1 概述

layout资源文件定义了Activity或者某个组件的用户界面结构。

a:layout资源文件位置

res/layout/filename.xml

filename 将会被用做资源ID.

b:被编译的layout资源类型

layout资源文件的节点是View类型或者View子类。

c: layout资源的引用

In Java: R.layout.filename

In XML: @[package:]layout/filename

d:语法

<?xml version="1.0" encoding="utf-8"?>
<ViewGroup xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@[+][package:]id/resource_name"
    android:layout_height=["dimension" | "fill_parent" | "wrap_content"]
    android:layout_width=["dimension" | "fill_parent" | "wrap_content"]
    [ViewGroup-specific attributes] >
    <View
        android:id="@[+][package:]id/resource_name"
        android:layout_height=["dimension" | "fill_parent" | "wrap_content"]
        android:layout_width=["dimension" | "fill_parent" | "wrap_content"]
        [View-specific attributes] >
        <requestFocus/>
    </View>
    <ViewGroup >
        <View />
    </ViewGroup>
    <include layout="@layout/layout_resource"/>
</ViewGroup>

注:根元素可以是ViewGroup某些子类、View或者<merge>元素,但每个layout文件必须只有一个根元素,并且根元素必须包含用来描述Android命名空间的属性xmlns:Android。


2 layout资源文件中的资源类型

可以作为根元素的资源类型有很多,可以再eclipse创建layout文件时看见。

只可以作为根元素的资源类型是<merge>元素。

不可以作为根元素的资源类型是<include>元素、<requestFocus>元素或者<ViewStub>元素。


2.1 ViewGroup

作为其他View元素的容器。ViewGroup有很多子类,例如LinearLayout、RelativeLayout 和 FrameLayout,每一个子类都可以指定其子元素以特定方式排列。

并不是所有的ViewGroup派生的子类都可以嵌套视图,一些ViewGroup实现了

AdapterView类,从而决定它的子元素只能来自于Adapter。


2.2 View

一个单独的用户界面组件,通常被称为“widget”。不同的View对象包括TextView,、Button 和 CheckBox.


2.3 <requestFocus>元素

任何代表视图对象的元素都可以包含<requestFocus>元素(该元素是没有任何属性,就是空元素),它可以使它的父元素得到焦点,每一个layout文件只可以包含一个<requestFocus>元素。


2.4 <include>元素

用来在某个layout文件中包含另一个layout文件,从而达到layout代码的重用和模块化。

属性:

a: layout

    Layout 资源.用来包含某个layout资源文件.

b: android:id

    资源 ID. 覆盖掉被包含layout资源文件根元素的ID。 

c: android:layout_height

只有被包含layout资源文件根元素的android:layout_width属性被声明,该属性才会有效果(即覆盖掉被包含layout资源文件根元素的android:layout_width属性的值),否者该属性没有效果。

d: android:layout_width

只有被包含layout资源文件根元素的android:layout_height属性被声明,该属性才会有效果(即覆盖掉被包含layout资源文件根元素的android:layout_height属性的值),否者该属性没有效果。

只要被包含layout资源文件根元素支持某个layout属性,你就可以在<include>元素中添加该layout属性,被添加到<include>元素中的这些属性会覆盖掉被包含layout资源文件根元素对应的属性。


注意:属性覆盖规则(A代表include中的属性,B代表被包含layout资源文件根元素的属性)

a:ID属性覆盖规则

B中有则覆盖,B中无则添加。

b:android:layout_height和android:layout_width属性覆盖规则

B中有则覆盖,B中无则添加。

c:除了b中提到的两个layout属性(以layout_为前缀的属性)外,其他layout属性的覆盖规则

前提:必须先覆盖android:layout_height和android:layout_width属性,否者没有效果。

B中有则覆盖,B中无则添加,A中无而B中有则B中无效。


举例如下:

activity_include.xml文件:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <include
        android:id="@+id/include_import_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        <span style="color:#ff0000;">layout="@layout/import_layout_include"</span> />

</FrameLayout>

import_layout_include.xml文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/layout_root"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical" >
    <Button android:id="@+id/test_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/include_button"/>

</LinearLayout>

IncludeActivity.java文件:

package com.cytmxk.testoptimizationlayout;

import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class IncludeActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		// TODO Auto-generated method stub
		super.onCreate(savedInstanceState);
		
		setContentView(R.layout.activity_include);
		Button testButton = (Button) findViewById(R.id.test_button);
		testButton.setText("chenyang");
		View view = findViewById(R.id.include_import_layout);
		view.setBackgroundColor(Color.GREEN);
	}
}

注意:上面红色部分千万不要写成android:layout="@layout/import_layout_include",而在ViewStub标签中却要写成android:layout="@layout/import_layout_include"。

运行结果如下图所示:


由运行结果可知:

a:要想获取被包含布局的子元素,直接通过ID就可以获取。

b: 要想获取被包含布局的根元素,使用的是include元素中的id获取(include元素中的id没有被设置时,就要用被包含布局的根元素的id获取),从而证明了上面的ID属性覆盖规则


2.5 <ViewStub>元素

除了上面一种包含layout资源文件方式,<ViewStub>元素是另外一种方式,

Google对ViewStub给的说明:

A ViewStub is an invisible, zero-sized View that can be used to lazily inflate layout resources at runtime. When a ViewStub is made visible, or when inflate() is invoked, the layout resource is inflated. The ViewStub then replaces itself in its parent with the inflated View or Views. Therefore, the ViewStub exists in the view hierarchy until setVisibility(int) or inflate() is invoked. The inflated View is added to the ViewStub's parent with the ViewStub's layout parameters. Similarly, you can define/override the inflate View's id by using the ViewStub's inflatedId property.

与<include>元素的异同:

属性覆盖规则与<include>元素相同,唯一不同的地方是<ViewStub>元素有懒加载的特点(其实ViewStub就是一个宽高都为0的一个View,它默认是不可见的,只有通过调用setVisibility函数或者Inflate函数才会将其要装载的目标布局给加载出来,从而达到延迟加载的效果)。

举例如下:

activity_view_stub.xml如下:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <ViewStub
        android:id="@+id/stub_import_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:inflatedId="@+id/stub_import_layout_root"
        <span style="color:#ff0000;">android:layout="@layout/import_layout"</span> />

</FrameLayout>

import_layout.xml如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/layout_root"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical" >
    <Button android:id="@+id/test_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/include_button"/>

</LinearLayout>

ViewStubActivity.java如下:

package com.cytmxk.testoptimizationlayout;

import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.view.ViewStub;
import android.widget.Button;
import android.widget.LinearLayout;

public class ViewStubActivity extends Activity {
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		// TODO Auto-generated method stub
		super.onCreate(savedInstanceState);
		
		setContentView(R.layout.activity_view_stub);
		
		ViewStub view = (ViewStub) findViewById(R.id.stub_import_layout);
		view.setVisibility(View.VISIBLE);
		Button testButton = (Button) findViewById(R.id.test_button);
		testButton.setText("chenyang");
		
		LinearLayout linearLayout = (LinearLayout) findViewById(R.id.stub_import_layout_root);
		linearLayout.setBackgroundColor(Color.GREEN);
	}
}

注意:上面红色部分千万不要写成layout="@layout/import_layout",而在include标签中却要写成layout="@layout/import_layout"。


运行结果与上图一样。

由运行结果可知:

a:要想获取被包含布局的子元素,直接通过ID就可以获取。

b: 要想获取被包含布局的根元素,使用的是include元素中的id获取(include元素中的id没有被设置时,就要用被包含布局的根元素的id获取),从而证明了上面的ID属性覆盖规则

这是为什么呢 ?

看一下ViewStub源代码,就一目了然了:

    @SuppressWarnings({"UnusedDeclaration"})
public ViewStub(Context context, AttributeSet attrs, int defStyle) {
    TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.ViewStub,
            defStyle, 0);
    // 获取inflatedId属性
    mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
    mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);

    a.recycle();

    a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.View, defStyle, 0);
    mID = a.getResourceId(R.styleable.View_id, NO_ID);
    a.recycle();

    initialize(context);
}

private void initialize(Context context) {
    mContext = context;
    setVisibility(GONE);// 设置不可见
    setWillNotDraw(true);// 设置不绘制
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(0, 0);// 宽高都为0
}


@Override
public void setVisibility(int visibility) {
    if (mInflatedViewRef != null) {// 如果已经加载过则只设置Visibility属性
        View view = mInflatedViewRef.get();
        if (view != null) {
            view.setVisibility(visibility);
        } else {
            throw new IllegalStateException("setVisibility called on un-referenced view");
        }
    } else {// 如果未加载,这加载目标布局
        super.setVisibility(visibility);
        if (visibility == VISIBLE || visibility == INVISIBLE) {
            inflate();// 调用inflate来加载目标布局
        }
    }
}

/**
 * Inflates the layout resource identified by {@link #getLayoutResource()}
 * and replaces this StubbedView in its parent by the inflated layout resource.
 *
 * @return The inflated layout resource.
 *
 */
public View inflate() {
    final ViewParent viewParent = getParent();

    if (viewParent != null && viewParent instanceof ViewGroup) {
        if (mLayoutResource != 0) {
            final ViewGroup parent = (ViewGroup) viewParent;// 获取ViewStub的parent view,也是目标布局根元素的parent view
            final LayoutInflater factory = LayoutInflater.from(mContext);
            final View view = factory.inflate(mLayoutResource, parent,
                    false);// 1、加载目标布局
          // 2、如果ViewStub的inflatedId不是NO_ID则把inflatedId设置为目标布局根元素的id,即评论ListView的id
            if (mInflatedId != NO_ID) {
                view.setId(mInflatedId);
            }

            final int index = parent.indexOfChild(this);
            parent.removeViewInLayout(this);// 3、将ViewStub自身从parent中移除

            final ViewGroup.LayoutParams layoutParams = getLayoutParams();
            if (layoutParams != null) {
                parent.addView(view, index, layoutParams);// 4、将目标布局的根元素添加到parent中,有参数
            } else {
                parent.addView(view, index);// 4、将目标布局的根元素添加到parent中
            }

            mInflatedViewRef = new WeakReference<View>(view);

            if (mInflateListener != null) {
                mInflateListener.onInflate(this, view);
            }

            return view;
        } else {
            throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
        }
    } else {
        throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
    }
}


可以看到,其实最终加载目标布局的还是inflate()函数,在该函数中将加载目标布局,获取到根元素后,如果mInflatedId不为NO_ID则 把mInflatedId设置为根元素的id,这也是为什么我们在获取LinearLayout时会使用 findViewById(R.id.stub_import_layout_root)来获取,其中的stub_import_layout_root就是ViewStub的inflatedId。 当然如果你没有设置inflatedId的话还是可以通过的LinearLayout的id来获取的,例如findViewById(R.id. layout_root)。然 后就是ViewStub从parent中移除、把目标布局的根元素添加到parent中。最后会把目标布局的根元素返回,因此我们在调用 inflate()函数时可以直接获得根元素,省掉了findViewById的过程。


还有一种方式加载目标布局的就是直接调用ViewStub的inflate()方法,示例如下 :

package com.cytmxk.testoptimizationlayout;

import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.view.ViewStub;
import android.widget.Button;
import android.widget.LinearLayout;

public class ViewStub2Activity extends Activity {
	
	LinearLayout linearLayout = null;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		// TODO Auto-generated method stub
		super.onCreate(savedInstanceState);
		
		setContentView(R.layout.activity_view_stub);
		
		ViewStub view = (ViewStub) findViewById(R.id.stub_import_layout);
		if(null == linearLayout){
			linearLayout = (LinearLayout) view.inflate();
		}
		Button testButton = (Button) findViewById(R.id.test_button);
		testButton.setText("chenyang");
		linearLayout.setBackgroundColor(Color.GREEN);
	}
}

运行结果与上图一样。


2.6  <merge>元素

官方给的说明如下:

The <merge /> tag helps eliminate redundant view groups in your view hierarchy when including one layout within another. For example, if your main layout is a vertical LinearLayout in which two consecutive views can be re-used in multiple layouts, then the re-usable layout in which you place the two views requires its own root view. However, using another LinearLayout as the root for the re-usable layout would result in a vertical LinearLayout inside a vertical LinearLayout. The nested LinearLayout serves no real purpose other than to slow down your UI performance.

我的理解:

在A布局中通过<include>元素将以<merge>元素为根元素的B布局包含进来,<merge>元素会会被系统忽略,然后用<merge>元素的子元素替换掉<include>元素。

注意:ViewStub目前有个缺陷就是还不支持 <merge /> 标签。

举例说明:

activity_merge.xml文件:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <include
        android:id="@+id/merge_import_layout"
        layout="@layout/import_layout_merge" />

</FrameLayout>


import_layout_merge.xml文件:

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
    <Button android:id="@+id/test_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/include_button"/>

</merge>


MergeAcyivity.java文件:

package com.cytmxk.testoptimizationlayout;

import android.app.Activity;
import android.os.Bundle;
import android.widget.Button;

public class MergeAcyivity extends Activity {

	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		// TODO Auto-generated method stub
		super.onCreate(savedInstanceState);
		
		setContentView(R.layout.activity_merge);

		Button testButton = (Button) findViewById(R.id.test_button);
		testButton.setText("chenyang");
	}
}


<merge>元素如何实现的呢,我们还是看源码吧。相关的源码也是在LayoutInflater的inflate()函数中。

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();
               
               // m如果是erge标签,那么调用rInflate进行解析
               if (TAG_MERGE.equals(name)) {
                   if (root == null || !attachToRoot) {
                       throw new InflateException("<merge /> can be used only with a valid "
                               + "ViewGroup root and attachToRoot=true");
                   }
                   // 解析merge标签
                   rInflate(parser, root, attrs, false);
               } else {
                  // 代码省略
               }

           } catch (XmlPullParserException e) {
               // 代码省略
           } 

           return result;
       }
   }


      void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs,
           boolean finishInflate) 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("<include /> cannot be the root element");
               }
               parseInclude(parser, parent, attrs);
           } else if (TAG_MERGE.equals(name)) {
               throw new InflateException("<merge /> must be the root element");
           } else if (TAG_1995.equals(name)) {
               final View view = new BlinkLayout(mContext, attrs);
               final ViewGroup viewGroup = (ViewGroup) parent;
               final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
               rInflate(parser, view, attrs, true);
               viewGroup.addView(view, params);                
           } else { // 我们的例子会进入这里
               final View view = createViewFromTag(parent, name, attrs);
               // 获取merge标签的parent
               final ViewGroup viewGroup = (ViewGroup) parent;
               // 获取布局参数
               final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
               // 递归解析每个子元素
               rInflate(parser, view, attrs, true);
               // 将子元素直接添加到merge标签的parent view中
               viewGroup.addView(view, params);
           }
       }

       if (finishInflate) parent.onFinishInflate();
   }

从源码可以看出我的理解是正确的。


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值