转载请声明出处:http://blog.csdn.net/andrexpert/article/details/77511996
在Android开发中,当我们需要显示用户交互界面时,通常的做法是创建一个继承Activity的类并重写它的onCreate()方法,再在该方法中调用setContentView()方法将布局界面显示出来。那么问题来了,setContentView方法具体做了些什么呢?基于此,本文将详细讲解Activity窗口机制原理、View的绘制流程及其源码分析,然后实现一个CircleProgressView控件以剖析自定义view的基本思路。
一、Activity窗口机制原理
1. UI界面架构
Activity是Android四大组件之一,是与用户交互的窗口。Activity类负责创建一个窗口,即Window对象,每个Activity都包含一个Windows对象,然后在该窗口中使用setContentView方法来放置需要显示的UI。Window类是一个抽象类,它封装了与顶层可见窗口和行为策略相关方法接口,其具体的实现交给PhoneWindow类来完成。PhoneWindow类是Window具体实现类,它内部包含了一个DecorView对象并且将该对象设置为整个应用窗口的根View。DecorView是PhoneWindow的内部类,继承于FrameLayout,它作为窗口界面的顶层视图,封装了一些窗口操作的通用方法,并将要显示的具体内容呈现在PhoneWindow上。
2. View树结构
在Android中,所有的视图控件都是View或ViewGroup的子类,ViewGroup是View的子类。每个ViewGroup作为父控件,可以包含多个View,但是View不能包含View或ViewGroup。在布局中,View和ViewGroup之间的关系可以用数据结构中的树来描述,即ViewGroup通常作为父结点存在,而View作为叶子节点存在,它们以树的形式构成最终的布局界面,并由上层控件负责下层子控件的测量和绘制,然后传递交互事件。在每棵树的顶部,都有一个ViewParent对象,这是整棵树的控制核心,所有的交互管理事件都由它来统一调度和分配,从而可以对整个视图进行整体控制。
二、View绘制流程分析
View的绘制过程主要经历三个阶段,即测量(Measure)、布局(Layout)、绘制(draw),其中,Measure的作用是测量要绘制View的大小;Layout的作用是明确要绘制View的具体位置;draw的作用就是绘制View。
1. measure流程
由View的源码可知,View的测量过程通过onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法实现的,并在该方法中调用setMeasuredDimension()方法来最终确定View的具体大小。View的测量包括两部分内容,即测量模式和具体大小的确定,不同的测量模式,其数值的设置方式是不同的,这可以借助MeasureSpec类从widthMeasureSpec和heightMeasureSpec来提取测量模式和具体数值。widthMeasureSpec、heightMeasureSpec本身是被MeasureSpec类处理过的,是一个32位的int值,它的高2位为测量模式,低30位为测量的大小。
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec);
View测量模式:
(1) EXACTLY,精确模式
当控件的layout_width属性或layout_height属性指定为具体值时,系统使用的就是精确模式。在自定义View时,如果不重写onMeasure方法,系统默认使用的就是EXACTLY模式,如果我们将layout_width属性或layout_height属性设置值为wrap_content,那么系统就不知道到底该绘制多大。
(2) AT_MOST,最大模式
当View的layout_width属性或layout_height属性指定为wrap_content时,对于View来说,View的大小随着其内容的变化而变化,对于ViewGroup来说,ViewGroup的大小随着其子控件变化而变化。
(3) UNSPECIFIED
这个模式通常只有系统才会使用,可以无需理会。
2. layout流程
View绘制的layout过程通过调用onLayout(boolean changed,int l, int t, int r, int b)方法实现,调用该方法需要传入放置View的矩形空间左上角left、top和右下角right、bottom,它们均是相对父控件而言的。需要注意的是,对于View来说,onLayout方法没有做任何事情,所以可以不用理会;对于ViewGroup来说,onLayout())是一个抽象方法,它将由继承于ViewGroup的子类实现,用来实现获取所有子View的实例,然后调用子View的layout(int l, int t, int r, int b)方法决定子View在父布局中的位置,其中l、r、r、b参数均是相对父控件而言的。自定义ViewGroup举例:
public class CustomLayout extends ViewGroup {
// 子View的垂直间隔
private final static int padding = 20;
public CustomLayout (Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
for (int i = 0, size = getChildCount(); i < size; i++) {
// 获取第i个子View实例
View view = getChildAt(i);
// 放置子View,宽高都是50
// left=0 ; top=0 ; right=50 ; bottom=50
view.layout(l, t, l + 50, t + 50);
t += 50+ padding;
}
}
}
3. draw流程
View绘制经历测量、布局过程后,接下来就是在指定的位置绘制指定大小的图形了,这个过程由onDraw(Canvas canvas