View视图安卓应用中非常重要的组成部分,它不仅是构成应用界面的基本单元,还是与用户交互的最直接对象。视图View作为界面的基本元素,是由View System进行管理的。

在Android中,视图控件主要被分成两大类,一类是单一控件View,另外一类是可以包含并管理子控件的ViewGroup,在一个界面布局中,ViewGroup与View的嵌套与组合,就构成了一棵控件树,经过一系列流程绘制在屏幕上,形成完整的用户界面。

View是最基本的UI类,几乎所有的控件都继承于View,广义的View指的是除了ViewGroup外的如TextView,CheckBox、Button、EditText、RadioButton等的单一控件,它们管理自身的绘制以及对事件的处理。而ViewGroup是View 的子类,同时作为View控件的容器而存在,ViewGroup可以控制各个子View 的显示层次与位置,同时可以对焦点获取、触摸等交互事件进行处理、拦截或分发。

视图的绘制主要分为三个步骤,分别为Measure,Layout跟Draw。

在Measure操作中,测量的操作的分发由ViewGroup来完成,ViewGroup通过对子节点进行遍历并分发测量操作,在具体的测量过程中,根据父节点的MeasureSpec以及子节点的LayoutParams等信息计算子节点的宽高,最终整理成父容器的宽高,具体的测量方式,我们也可以通过重写onMeasure方法来设定。

紧接着,通过Layout过程,来确定每一个View在父容器中的具体位置,同样的,我们也可以通过onLayout方法来自定义具体的布局流程。

最后进入Draw的绘制流程,根据前两步所获得的具体布局参数,在draw函数中对各控件进行绘制,绘制的顺序为背景-控件内容-子控件绘制-绘制边缘以及滚动条等装饰物。

总体来说,控件的绘制都是在控件树上进行的,由ViewGroup分发给子View各自完成自身的测量与布局操作,最后由根节点开始进行绘制,最终形成完整的界面。

最后选择一个具体的例子来观察View的绘制流程吧。

首先我们看一下TextView的具体绘制流程:

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

int widthMode = MeasureSpec.getMode(widthMeasureSpec);

int heightMode = MeasureSpec.getMode(heightMeasureSpec);

int widthSize = MeasureSpec.getSize(widthMeasureSpec);

int heightSize = MeasureSpec.getSize(heightMeasureSpec);

int width;

int height;

......

setMeasuredDimension(width, height);

}

传入的MeasureSpec是一个32位的整数,高两位表示测量模式,低30位表示规格大小,这个整数指定了控件将如何进行具体的测量。最后利用更新后的width跟height设置测量结果的宽高。这个函数的主要作用是计算是否有margin/padding等造成额外的宽高,在必要时预留空间,并确定view的大小。

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

super.onLayout(changed, left, top, right, bottom);

if (mDeferScroll >= 0) {

int curs = mDeferScroll;

mDeferScroll = -1;

bringPointIntoView(Math.min(curs, mText.length()));

}

}



在这里调用了父类的onLayout函数

/**

* Called from layout when this view should

* assign a size and position to each of its children.

*

* Derived classes with children should override

* this method and call layout on each of

* their children.

* @param changed This is a new size or position for this view

* @param left Left position, relative to parent

* @param top Top position, relative to parent

* @param right Right position, relative to parent

* @param bottom Bottom position, relative to parent

*/

protected void onLayout

(boolean changed, int left, int top, int right, int bottom) {

}



可以看到View中该函数的注释,可以知道这个函数的主要作用是对view的位置与大小进行赋值,以便下一步进行绘制。

@Override

protected void onDraw(Canvas canvas) {

restartMarqueeIfNeeded();


// Draw the background for this view

super.onDraw(canvas);


......


mTextPaint.setColor(color);

mTextPaint.drawableState = getDrawableState();


......


layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);

......


}



onDraw函数在该view需要进行渲染时调用,设置了具体的画笔等参数,并调用draw进行具体绘制。


/**

* Draw this Layout on the specified canvas, with the highlight path drawn

* between the background and the text.

*

* @param canvas the canvas

* @param highlight the path of the highlight or cursor; can be null

* @param highlightPaint the paint for the highlight

* @param cursorOffsetVertical the amount to temporarily translate the

* canvas while rendering the highlight

*/

public void draw(Canvas canvas, Path highlight, Paint highlightPaint,

int cursorOffsetVertical) {

final long lineRange = getLineRangeForDraw(canvas);

int firstLine = TextUtils.unpackRangeStartFromLong(lineRange);

int lastLine = TextUtils.unpackRangeEndFromLong(lineRange);

if (lastLine < 0) return;


drawBackground(canvas, highlight, highlightPaint, cursorOffsetVertical,

firstLine, lastLine);

drawText(canvas, firstLine, lastLine);

}



在draw方法中分别调用对背景以及具体文字的绘制函数。

总而言之,我们可以重写这些方法,来完成我们的控件自定义操作。