Android Layout与 View机制分析
本文就下面几个方面进行分析layout与view机制:
1、res\layout的形式及使用
2、layout是如何加载和使用的,与view有什么关系。
一、 layout形式和使用
Ø layout形式
在android应用res/layout目录下存放着所有布局文件,都是xml形式。布局通过字面意思理解就是把各种组件按需要摆放好,一个布局文件中包含了各种控件,也可以嵌入各种布局。Android有下面几种布局:
1. 线性布局(LinearLayout):按照垂直或者水平方向布局组件。
2. 帧布局(FrameLayout): 组件从屏幕的左上角坐标布局组件。
3. 表格布局(TableLayout):按照行列方式布局组件。
4. 相对布局(RelativeLayout):相对其他组件的布局方式。
5. 绝对布局(AbsoluteLayout):按照绝对坐标来布局组件。
<?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">
<com.example.android.jetboy.JetBoyView android:id="@+id/JetBoyView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<TextView android:id="@+id/text" android:text="@string/helpText"
style="@style/helpText" android:visibility="invisible"
android:layout_width="300px" android:layout_height="300px"
android:background="#7Fffffff" android:layout_gravity="center" />
<Button android:id="@+id/Button01" android:text="@string/start"
style="@style/ButtonText" android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|bottom" />
</FrameLayout>
上面是一种帧布局(FrameLayout),对3个控件进行布局。其中的第一个控件是代码中实现的,TextView和Button使用android框架类实现。
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="center_vertical"
android:drawablePadding="14dip"
android:paddingLeft="15dip"
android:paddingRight="15dip" />
上面这个布局对应的就是一个控件。
<com.android.launcher2.Folder
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@drawable/portal_container_holo">
上面这个布局直接在代码中自定义实现。
Ø Layout使用
直接使用布局资源文件:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
R.layout.main对应res/layout/main.xml布局文件;
代码中动态创建布局:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new MyView(this));
}
//内部类
class MyView extends SurfaceView implements SurfaceHolder.Callback{
二、 Layout代码实现机制
我们在Activity中调用setContentView时有下面3中方式:
voidsetContentView(int layoutResID); 从layout资源文件设置显示view
voidsetContentView(View view); 从已经创建好的view设置
voidsetContentView(View view, ViewGroup.LayoutParams params); 从已经创建好的view设置,并制定布局。
最终会调用到PhoneWindow.java中如下3个方法:
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else {
mContentParent.removeAllViews();
}
mLayoutInflater.inflate(layoutResID, mContentParent);
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
@Override
public void setContentView(View view) {
setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
if (mContentParent == null) {
installDecor();
} else {
mContentParent.removeAllViews();
}
mContentParent.addView(view, params);
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
从上面可见,如果是创建好的view,则直接添加到private ViewGroupmContentParent;
从layout资源创建最终执行到:
frameworks\base\core\java\android\view\LayoutInflater.java
inflate(XmlPullParser parser, ViewGrouproot, boolean attachToRoot) 方法;
// Temp is the root view that was found in the xml
View temp;
if (TAG_1995.equals(name)) {
temp = new BlinkLayout(mContext, attrs);
} else {
temp = createViewFromTag(root, name, attrs);
}
// to root. Do that now.
if (root != null && attachToRoot) {
root.addView(temp, params);
}
会根据资源layout id加载对应的xml资源,然后解析生成一个view, 最终加入到root中,上面的root就是PhoneWindow中ViewGroup mContentParent;
下面来看layout资源文件创建view的过程:
// Temp is the root view that was found in the xml
View temp;
if (TAG_1995.equals(name)) {
temp = new BlinkLayout(mContext, attrs);
} else {
temp = createViewFromTag(root, name, attrs);
}
Root为PhoneWindow中 ViewGroup mContentParent
Name为layout布局文件的第一个tag.
Attrs布局文件内容
最后在下面函数通过反射加载类
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
Constructor<? extends View> constructor = sConstructorMap.get(name);
Class<? extends View> 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).asSubclass(View.class);
Object[] args = mConstructorArgs;
args[1] = attrs;
final View view = constructor.newInstance(args);
执行构造函数。
================================
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
最终实例化android.widget.TextView创建view
注意:layout第一个tag是TextView, ImageView这类型时,会依次拼接下面前缀在尝试进行实例化,实例化成功后不再立即返回。
"android.widget.",
"android.webkit."
"android.view."
=================================
<com.android.launcher2.Folder
xmlns:android="http://schemas.android.com/apk/res/android"
最终实例化com.android.launcher2.Folder创建view, 执行到如下构造函数。
public Folder(Context context, AttributeSet attrs) {
super(context, attrs);
setAlwaysDrawnWithCacheEnabled(false);
===================================
最终实例化android.widget.FrameLayout创建view
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
总结:
1、 每个layout使用时,都会创建出一个view或者ViewGroup
2、 layout资源文件中第一个tag就是对应实例化view或者ViewGroup的类
3、 activity,window,layout,view,viewGroup关系如下。