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

Android中View绘制流程

2016-05-09 17:08 525 查看
在实际开发中经常需要自定义View,所以必须要掌握View的绘制流程,在View的绘制过程中主要要掌握的方法有:onMeasure、onSizeChanged、onLayout、onDraw;

一、onMeasure(int widthMeasureSpec, int heightMeasureSpec) :

onMeasure方法在控件的父元素正要放置它的子控件时调用。它会问一个问题,“你想要用多大地方啊?”,然后传入两个参数——widthMeasureSpec和heightMeasureSpec。
它们指明控件可获得的空间以及关于这个空间描述的元数据。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
int width = 0;
int height = 0;
// 记录每一行的宽度与高度
int lineWidth = 0;
int lineHeight = 0;

// 得到内部元素的个数
int cCount = getChildCount();

for (int i = 0; i < cCount; i++) {
View child = getChildAt(i);
// 测量子View的宽和高
measureChild(child, widthMeasureSpec, heightMeasureSpec);
// 得到LayoutParams
MarginLayoutParams lp = (MarginLayoutParams) child
.getLayoutParams();

// 子View占据的宽度
int childWidth = child.getMeasuredWidth() + lp.leftMargin
+ lp.rightMargin;
child.setLayoutParams(lp);
// 子View占据的高度
int childHeight = child.getMeasuredHeight() + lp.topMargin
+ lp.bottomMargin;

// 换行
if (lineWidth + childWidth > sizeWidth - getPaddingLeft()
- getPaddingRight()) {
// 对比得到最大的宽度
width = Math.max(width, lineWidth);
// 重置lineWidth
lineWidth = childWidth;
// 记录行高
height += lineHeight;
lineHeight = childHeight;
} else
// 未换行
{
// 叠加行宽
lineWidth += childWidth;
// 得到当前行最大的高度
lineHeight = Math.max(lineHeight, childHeight);
}
// 最后一个控件
if (i == cCount - 1) {
width = Math.max(lineWidth, width);
height += lineHeight;
}
}

setMeasuredDimension(
//
modeWidth == MeasureSpec.EXACTLY ? sizeWidth : width
+ getPaddingLeft() + getPaddingRight(),
modeHeight == MeasureSpec.EXACTLY ? sizeHeight : height
+ getPaddingTop() + getPaddingBottom()//
);

}


int specMode = MeasureSpec.getMode(measureSpec);

int specSize = MeasureSpec.getSize(measureSpec);

依据specMode的值,如果是AT_MOST,specSize 代表的是最大可获得的空间;如果是EXACTLY,specSize 代表的是精确的尺寸;如果是UNSPECIFIED,对于控件尺寸来说,没有任何参考意义。

当以EXACT方式标记测量尺寸,父元素会坚持在一个指定的精确尺寸区域放置View。在父元素问子元素要多大空间时,AT_MOST指示者会说给我最大的范围。在很多情况
下,你得到的值都是相同的。

在两种情况下,你必须绝对的处理这些限制。在一些情况下,它可能会返回超出这些限制的尺寸,在这种情况下,你可以让父元素选择如何对待超出的View,使用裁剪还是滚动等技术。

二、onSizeChanged(int w, int h, int oldw, int oldh):

这个是系统回调方法,在这个View的大小发生改变的时候会被系统调用,我们要监控view的大小变化,重写这个方法就可以了。

三、onLayout(boolean changed, int l, int t, int r, int b):

主要作用
:为将整个根据子视图的大小以及布局参数将View树放到合适的位置上

/**
* 存储所有的View
*/
private List<List<View>> mAllViews = new ArrayList<List<View>>();
/**
* 每一行的高度
*/
private List<Integer> mLineHeight = new ArrayList<Integer>();

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
mAllViews.clear();
mLineHeight.clear();

// 当前ViewGroup的宽度
int width = getWidth();

int lineWidth = 0;
int lineHeight = 0;

List<View> lineViews = new ArrayList<View>();

int cCount = getChildCount();

for (int i = 0; i < cCount; i++) {
View child = getChildAt(i);
MarginLayoutParams lp = (MarginLayoutParams) child
.getLayoutParams();

int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();

// 如果需要换行
if (childWidth + lineWidth + lp.leftMargin + lp.rightMargin > width
- getPaddingLeft() - getPaddingRight()) {
// 记录LineHeight
mLineHeight.add(lineHeight);
// 记录当前行的Views
mAllViews.add(lineViews);

// 重置我们的行宽和行高
lineWidth = 0;
lineHeight = childHeight + lp.topMargin + lp.bottomMargin;
// 重置我们的View集合
lineViews = new ArrayList<View>();
}
lineWidth += childWidth + lp.leftMargin + lp.rightMargin;
lineHeight = Math.max(lineHeight, childHeight + lp.topMargin
+ lp.bottomMargin);
lineViews.add(child);

}// for end
// 处理最后一行
mLineHeight.add(lineHeight);
mAllViews.add(lineViews);

// 设置子View的位置

int left = getPaddingLeft();
int top = getPaddingTop();

// 行数
int lineNum = mAllViews.size();

for (int i = 0; i < lineNum; i++) {
// 当前行的所有的View
lineViews = mAllViews.get(i);
lineHeight = mLineHeight.get(i);

for (int j = 0; j < lineViews.size(); j++) {
View child = lineViews.get(j);
// 判断child的状态
if (child.getVisibility() == View.GONE) {
continue;
}

MarginLayoutParams lp = (MarginLayoutParams) child
.getLayoutParams();

int lc = left + lp.leftMargin;
int tc = top + lp.topMargin;
int rc = lc + child.getMeasuredWidth();
int bc = tc + child.getMeasuredHeight();

// 为子View进行布局
child.layout(lc, tc, rc, bc);

left += child.getMeasuredWidth() + lp.leftMargin
+ lp.rightMargin;
}
left = getPaddingLeft();
top += lineHeight;
}

}


具体的调用链如下:
host.layout()开始View树的布局,继而回调给View/ViewGroup类中的layout()方法。具体流程如下

1 、layout方法会设置该View视图位于父视图的坐标轴,即mLeft,mTop,mLeft,mBottom(调用setFrame()函数去实现)

接下来回调onLayout()方法(如果该View是ViewGroup对象,需要实现该方法,对每个子视图进行布局) ;

2、如果该View是个ViewGroup类型,需要遍历每个子视图chiildView,调用该子视图的layout()方法去设置它的坐标值

child.layout(lc, tc, rc, bc);来对子View的位置进行绘制,相信可以看代码实现

四、onDraw:

绘图时在onDraw中绘制,如果是GONE状态下,View的绘制周期会在onLayout的时候停止,不会执行这个方法

五、 invalidate()方法 :

这个方法可以刷新View

说明:请求重绘View树,即draw()过程,假如视图发生大小没有变化就不会调用layout()过程,并且只绘制那些“需要重绘的”

视图,即谁(View的话,只绘制该View ;ViewGroup,则绘制整个ViewGroup)请求invalidate()方法,就绘制该视图。

一般引起invalidate()操作的函数如下:

1、直接调用invalidate()方法,请求重新draw(),但只会绘制调用者本身。

2、setSelection()方法 :请求重新draw(),但只会绘制调用者本身,listview.setselection(position),表示将列表移动到指定的Position处。

3、setVisibility()方法 : 当View可视状态在INVISIBLE转换VISIBLE时,会间接调用invalidate()方法,

继而绘制该View。

4 、setEnabled()方法 : 请求重新draw(),但不会重新绘制任何视图包括该调用者本身。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: