您的位置:首页 > 其它

View和ViewGroup的基本绘制流程

2016-09-19 14:42 141 查看

需要了解的

先来张图说明一下它们的关系



你还要知道ViewGroup之间是可以嵌套的.

View的绘制流程

不知道大家有没有这种疑惑, 为什么我们在写布局文件的时候, 一定要写layout_width和layout_height呢, 难道就没有默认值吗? 颜色, 背景, 等等其他的都有默认值, 为什么宽高就一定要我们手动写呢? 接下来就让我们一起来解答这个疑惑吧.

绘制流程的源码就不贴出来了, 有兴趣的可以打开View的源码对照着来看, 印象会更深刻, 当然, 不看源码, 理解以下的实例代码, 也不会影响你对整个流程的理解.

首先看一下View的绘制流程示例:

public class MyView extends View {

private static final String TAG = "MyView";

public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}

/**
* measure - > onMeasure ,view的源码中,measure会调用onMeasure
*/
@Override
protected void onMeasure(int widthMeasureSpec, int<
4000
/span> heightMeasureSpec) {  //测量, 表示这个view的大小, 在View的源码中, Measure是final修饰的, 我们只能重写onMeasure方法
// TODO Auto-generated method stub
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Log.d(TAG, "measure");
}

/**
* layout - > setFrame 和 onLayout
*/
@Override
public void layout(int l, int t, int r, int b) {  //布局,决定了摆放在父容器中的哪个位置
// TODO Auto-generated method stub
super.layout(l, t, r, b);
Log.d(TAG, "layout");
}

/**
* draw - > onDraw
*/
@Override
public void draw(Canvas canvas) {  //绘制
// TODO Auto-generated method stub
super.draw(canvas);
Log.d(TAG, "draw");
}
}


以上就是View绘制显示在屏幕上必定会调用的三个方法, 可能你还不太理解, 不过没关系, 先混个脸熟, 有个印象先, 下面我们一个个分析.

View的onMeasure

来看一段Demo

public class MyView extends View {

private static final String TAG = "MyView";

public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}

/**
* measure -> onMeasure
*
* 1.父容器拿到孩子的申请的宽高layout_width, layout_height封装成宽高的期望widthMeasureSpec和heightMeasureSpec
* 父容器Relativelayout(或者其他Linearlyout)
* 调用MyView的 measure(int widthMeasureSpec, int heightMeasureSpec)传入对孩子宽高的期望
* measure -> onMeasure(widthMeasureSpec, heightMeasureSpec)
*
* @param widthMeasureSpec 父容器(RelativeLaoyut)对孩子MyView的宽度期望, 跟layout_width相关
* @param heightMeasureSpec 父容器(RelativeLaoyut)对孩子MyView的高度期望, 跟layout_height相关
* 这是我们为什么一定要指定layout_width和layout_height的原因.
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
/**
* int widthMeasureSpec
* 32位二进制
* 前两位 是测量模式 mode
* public static final int UNSPECIFIED = 0 << MODE_SHIFT; 父容器对孩子没有任何的限制,孩子想多大多大
* public static final int EXACTLY     = 1 << MODE_SHIFT; 父容器对孩子有确切的大小要求,大小就会后30位
* public static final int AT_MOST     = 2 << MODE_SHIFT; 父容器对孩子的最大值有要求,大小就会后30位
* 后30位表示大小
*/
int mode = MeasureSpec.getMode(widthMeasureSpec);
int size = MeasureSpec.getSize(widthMeasureSpec);
Log.d(TAG, "onMeasure mode " + (mode>>30) + " " + size);

//super方法默认使用父容器对我的期望的宽高
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//我们也可以不调用super直接调用setMeasuredDimension(50, 50)来指定宽高
}

}


注释里的setMeasuredDimension(int measuredWidth, int measuredHeight)是一个重要的方法, 它是整个测量结束的标志, 只有这个方法调用了, 我们才能调用getMeasuredWidth()或者getMeasuredHeight()方法获得测量宽高(注意, 不是实际宽高, 实际宽高要在布局完成之后)。

自定义View如果要使用wrap_content属性的话,则需重写onMeasure方法。

View的layout

layout方法里面调用setFrame(),给View的上下左右四个位置mLeft, mTop,mRight, mBottom赋值,完成布局工作.

onLayout()是一个空的方法,说明具体的布局不应该由view来决定

我们的view并不需要关心layout方法, 布局的事应该交由父容器去处理, 让它决定它的孩子应该摆放在哪个地方.

在布局完成后, 我们才能调用getWidth()和getHeight获得实际的宽高.

getWidth()和getMeasuredWidth()的区别: getMeasureWidth()方法在measure()过程结束后就可以获取到了,而getWidth()方法要在layout()过程结束后才能获取到。另外,getMeasureWidth()方法中的值是通过setMeasuredDimension()方法来进行设置的,而getWidth()方法中的值则是通过视图右边的坐标减去左边的坐标计算出来的。

View的draw

view的绘制分为6步:

对视图的背景进行绘制

If necessary, save the canvas’ layers to prepare for fading (暂时忽略它)

对视图的内容进行绘制, 在onDraw(canvas)方法中完成

对当前视图的所有子视图进行绘制 ,调用dispatchDraw。

If necessary, draw the fading edges and restore layers (暂时忽略它)

绘制装饰品(如滚动条)任何一个视图都是有滚动条的,只是一般情况下我们都没有让它显示出来而已.

即我们关心四个步骤:

绘制背景

绘制内容

绘制孩子

绘制装饰

绘制需要两个类, 画布(Canvas)和画笔(Paint), 通过以下Demo通过onDraw方法利用画笔在画布上绘制我们的图案吧.

public class MyView extends View {

private Paint mPaint;
private Bitmap mBitmap;
private Path mPath;
private RectF mOval;

public MyView(Context context, AttributeSet attrs) {
super(context, attrs);

mPaint = new Paint();
//设置去锯齿
mPaint.setAntiAlias(true);
//配置画笔,画空心圆
mPaint.setStyle(Style.STROKE);
//设置画笔宽度
mPaint.setStrokeWidth(3);

//设置画笔颜色
mPaint.setColor(Color.BLUE);

//画图片时需要设置图片
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.haha);

//设置扇形的大小
mOval = new RectF(5, 5, 195, 195);

initPath();
}

private void initPath() {
mPath = new Path();
//确定三个点
int x1 = 100, y1 = 5;
int x2 = 195, y2 = 195;
int x3 = 5, y3 = 195;
//移动到第一个点
mPath.moveTo(x1, y1);
//链接第一个点和第二个点
mPath.lineTo(x2, y2);
//链接第二个点和第三个点
mPath.lineTo(x3, y3);
mPath.lineTo(x1, y1);

}

/**
* 不要在onDraw方法里面创建新的对象,因为onDraw方法可能会频繁调用
*/
@Override
protected void onDraw(Canvas canvas) {
//      6. 裁剪
//      canvas.clipPath(mPath);

//      1. 画直线
//      int startX =5, startY = 100;
//      int stopX = 195, stopY = 100;
//      canvas.drawLine(startX, startY, stopX, stopY, mPaint);
//      2. 画圆
//      int cx = 100,  cy = 100;
//      int radius = 80;
//      canvas.drawCircle(cx, cy, radius, mPaint);
//      3. 画空心圆

//      4. 画图片
//      canvas.drawBitmap(mBitmap, 0, 0, mPaint);
//      5. 画三角形
//      canvas.drawPath(mPath, mPaint);

//7.画扇形
int startAngle = -90; //开始的角度
int sweepAngle = 45;  //扫过的角度
boolean useCenter = false;//是否画出扇形的两边
canvas.drawArc(mOval, startAngle, sweepAngle, useCenter, mPaint);
}
}


View的重新绘制

invalidate(); //触发View的重新绘制 onDraw

postInvalidate(); //请求在主线程重新绘制控件 onDraw

ViewGroup的绘制流程

ViewGroup继承View,绘制流程跟View是一致

ViewGroup的测量

相同点:measure -> onMeasure

不同点:ViewGroup需要在onMeasure去测量孩子

自定义ViewGroup一定要重写onMeasure方法,如果不重写则子View获取不到宽和高。重写是在onMeasure方法中调用measureChildern()方法,遍历出所有子View并对其进行测量。

ViewGroup的布局

相同点:layout (父容器调用) -》 onLayout

不同点:ViewGroup需要实现onLayout方法去布局孩子,调用孩子的layout方法,指定孩子上下左右的位置

requestLayout();//请求重新布局 onLayout

ViewGroup的绘制

相同点:draw -> onDraw

不同点:ViewGroup一般不绘制自己,ViewGroup默认实现dispatchDraw去绘制孩子
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息