您的位置:首页 > 移动开发 > Android开发

android初级学习之深入了解View绘制流程(一)

2016-11-09 13:44 645 查看
  View是android开发中必不可少的,之前也只是停留在简单使用状态上,如使用setContentView()设置布局呀,但背后原理还没深入了解。知其然而不知其所以然乃技术学习之大忌。趁有空,稍微整理一下,也稍微深究一下,并记录下来,聊以不时之需。

LayoutInflater

  View的绘制首先得从LayoutInflater说起:

  老样子,先应用再深究。

  LayoutInflater的用法比较简单,先获取LayoutInflater实例:

  

LayoutInflater mInflater = LayoutInflater.from(context);


或:
LayoutInflater mInflater = (LayoutInflater) context

.getSystemService(Context.LAYOUT_INFLATER_SERVICE);


  其实第一种写法就是第二种的写法的一个简单写法,这里android为我们做了一个简单的封装。

  获取实例后就是通过inflate(resourceId, root)方法来加载布局了。

  

mInflater.inflate(resourceId, root);


第一个参数就是要加载的布局id,第二个参数是指给该布局的外部再嵌套一层父布局,如果不需要就直接传null。这样就成功创建了一个布局的实例,之后再将它添加到指定的位置就可以显示出来了。
下面是一个简单的实例,通过LayoutInflater来在一个空布局上加载一个TextView并显示出来:
首先创建一个空布局文件activity_inflate.xml:


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >

</LinearLayout>


接着是创建一个TextView的简单布局layout_tv:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="通过LayoutInflate加载控件布局"
android:textSize="50sp" >

</TextView>


最后就是写相关逻辑代码了:

package com.yixingu.demo.view;

/*
* 演示LayoutInflater加载控件布局
* */

import android.app.Activity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;

import com.yixingu.demo.R;

public class LayoutInflaterDemo extends Activity {
private LinearLayout mLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_inflater);
mLayout = (LinearLayout)findViewById(R.id.main_layout);
LayoutInflater mInflate = LayoutInflater.from(this);
View mTextViewLayout = mInflate.inflate(R.layout.layout_tv, null);
mLayout.addView(mTextViewLayout);
}
}


运行结果:



  这是一个比较简单的实例,但也比较好的诠释了LayoutInflate的使用。接下来,我们一起来探究一下其背后的“阴谋”。

  翻看源码发现,无论你调用哪个inflate()方法,最终都将调用
inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)
这个方法,请看:







接着查看最终调用的:











  原来LayoutInflate是使用xmlpullparser来解析xml文件的。这里,我们主要看一下482这行。这行调用了createViewFromTag(root, name, attrs, false)方法,根据方法名大概可以猜出该方法用于根据节点名来创建View对象的。确实如此,在createViewFromTag()方法的内部又会去调用createView()方法,然后使用反射的方式创建出View的实例并返回。到此,已经创建了一个根实例。

  接下来看504行。这里调用了rInflate()方法。该方法是用来循环遍历这个根布局下的子元素。





  在第806行同样是createViewFromTag()方法来创建View的实例,然后还会在第809行递归调用rInflate()方法来查找这个View下的子元素,每次递归完成后则将这个View添加到父布局当中,最终完成将控件加载的任务。

inflate()的参数含义

  这里需要注意的是
inflate(int resource, ViewGroup root, boolean attachToRoot)
的三个参数,根据字面意思大概可以猜出resource代表要加载的布局文件资源,root代表父布局,attachToRoot代表是否和父布局建立联系。那么他么之间有什么联系呢?

  1. 如果root为null,attachToRoot将失去作用,设置任何值都没有意义。

2. 如果root不为null,attachToRoot设为true,则会给加载的布局文件的指定一个父布局,即root。

3. 如果root不为null,attachToRoot设为false,则会将布局文件最外层的所有layout属性进行设置,当该view被添加到父view当中时,这些layout属性会自动生效。

4. 在不设置attachToRoot参数的情况下,如果root不null,attachToRoot参数默认为true。

  这里对我们的TextView稍微做一下改变,使其变高或变小,为方便区分,将其背景颜色设置一下。

  layout_tv.xml:

  

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffff00"
android:text="通过LayoutInflate加载控件布局"
android:textSize="50sp" >

</TextView>


运行结果:



  哎哎哎,我明明改变了TextView中的layout_width和layout_height,可是为什么显示却没任何反应?先别急。其实主要是因为layout_width和layout_height失去其作用了。layout_width和layout_height这两个属性是用来表示控件在布局中的大小的,这和表示控件的大小有质的区别!!!这由于我们的TextView只是一个控件布局文件,并没有在任何布局中,自然layout_width和layout_height这两个属性就失去其作用了。大家是不是又有疑问了。我们明明指定控件文件也是可以显示的呀,不信你看:

  

package com.yixingu.demo.view;

/*
* 演示LayoutInflater加载控件布局
* */

import android.app.Activity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;

import com.yixingu.demo.R;

public class LayoutInflaterDemo extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_tv);
}
}


运行结果:



  这不就发挥其作用了吗?

  其实,调用setContentView()时,系统就默认为我们嵌套了一个Framelayout。故而,TextView中的layout_width和layout_height能发挥作用,有兴趣的可以获取当前布局的父布局并在locat中打印出来就可以看到是一个Framelayout了。这其中涉及到android的控件架构问题。

android控件架构

  我们先看一下android界面的架构图:



  每个Activity都包含一个Window对象,在android中通常由PhoneWindow来实现。而PhoneWindow将DecorView设置为整个应用窗口的根View。DecorView作为窗口界面的顶层视图,并封装了一些窗口操作的通用方法。可以说,DecorView将要显示的内容呈现在PhoneWindow上,这里面的View监听事件,都通过WindowMannagerService来进行接收。在显示上,它将屏幕分为两部分,TitleView和ContenView。ContenView就是一个ID为content的Framelayout,每当代用setContentView时,就将布局文件加载在一个这样的Framelayout中。据此,我们可以建立一颗View视图树:



  这也可以解释为什么requestWindowFeature()为什么要在setContenView()之前调用了。

  其实,其中还封装了一个方法onReasume(),每当我们在onCreate()中调用setContenView()时,ActivityManagerService会回调onReasume()方法,这样系统才会把整个DecorView添加到PhoneWindow中,并让其显示出来,从而完成界面的绘制。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐