自定义 view 的最后一步是绘制,使用 Canvas 对象绘制出我们想要的效果。
绘制用到的Canvas
draw 方法中的参数都有个 Canvas 对象,先来了解一下这个东西。
The Canvas class holds the "draw" calls. To draw something, you need 4 basic components: A Bitmap to hold the pixels, a Canvas to host the draw calls (writing into the bitmap), a drawing primitive (e.g. Rect, Path, text, Bitmap), and a paint (to describe the colors and styles for the drawing).
想要绘制一些东西,需要4个基础组件:
- 一个Bitmap,用来持有像素
- 一个Canvas画布,用来写入bitmap
- 一个绘制的图元,如(Rect,Path,text,Bitmap)
- 一个画笔,用来描述绘图的颜色和样式
view中的draw方法不需要我们自己创建一个bitmap,系统已经为我们创建好了。
给ImageView设置bitmap
private void drawBitmap(){
Bitmap bitmap = Bitmap.createBitmap(800, 400, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawColor(Color.GREEN);
Paint paint = new Paint();
paint.setColor(Color.RED);
paint.setTextSize(60);
canvas.drawText("hello, everyone",150,200,paint);
mImageView.setImageBitmap(bitmap);
}
在此处为canvas设置一个Bitmap,然后利用canvas画了一小段文字,最后使用ImageView显示了Bitmap。
在onDraw里面绘制不同的图形
@Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 绘制白色矩形
mPaint.reset();
mPaint.setColor(Color.WHITE);
canvas.drawRect(0, 0, 800, 800, mPaint);
// 绘制直线
mPaint.reset();
mPaint.setColor(Color.RED);
mPaint.setStrokeWidth(10);
canvas.drawLine(450, 30, 570, 170, mPaint);
//-------->绘制带边框的矩形
mPaint.reset();
mPaint.setStrokeWidth(10);
mPaint.setARGB(150, 90, 255, 0);
mPaint.setStyle(Paint.Style.STROKE);
@SuppressLint("DrawAllocation") RectF rectF1 = new RectF(30, 60, 350, 350);
canvas.drawRect(rectF1, mPaint);
//-------->绘制实心圆
mPaint.reset();
mPaint.setStrokeWidth(14);
mPaint.setColor(Color.GREEN);
mPaint.setAntiAlias(true);
canvas.drawCircle(670, 300, 70, mPaint);
//-------->绘制椭圆
mPaint.reset();
mPaint.setColor(Color.YELLOW);
RectF rectF2 = new RectF(200, 430, 600, 600);
canvas.drawOval(rectF2, mPaint);
//-------->绘制文字
mPaint.reset();
mPaint.setColor(Color.BLACK);
mPaint.setTextSize(60);
mPaint.setUnderlineText(true);
canvas.drawText("Hello Android", 150, 720, mPaint);
}
对canvas的操作
- canvas.translate
- canvas.rotate
- canvas.clipRect
- canvas.save和canvas.restore
- PorterDuffXfermode
- Bitmap和Matrix
- Shader
- PathEffect
显示圆角图片
/**
* @param bitmap 原图
* @param pixels 角度
* @return 带圆角的图
*/
public Bitmap getRoundCornerBitmap(Bitmap bitmap, float pixels) {
int width=bitmap.getWidth();
int height=bitmap.getHeight();
Bitmap roundCornerBitmap = Bitmap.createBitmap(width,height,Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(roundCornerBitmap);
Paint paint = new Paint();
paint.setColor(Color.BLACK);
paint.setAntiAlias(true);
Rect rect = new Rect(0, 0, width, height);
RectF rectF = new RectF(rect);
canvas.drawRoundRect(rectF, pixels, pixels, paint);
PorterDuffXfermode xfermode=new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
paint.setXfermode(xfermode);
canvas.drawBitmap(bitmap, rect, rect, paint);
return roundCornerBitmap;
}
draw
view的draw()方法:
有2个draw()方法,分别是:
void draw(Canvas canvas)boolean draw(Canvas canvas, ViewGroup parent, long drawingTime)
源码中提及到draw有6个过程:
- Draw the background
- If necessary, save the canvas' layers to prepare for fading(如有必要,保存画布层以准备褪色)
- Draw view's content
- Draw children
- If necessary, draw the fading edges and restore layers(如有必要,绘制褪色边缘并恢复图层)
- Draw decorations (scrollbars for instance)(绘制装饰(例如滚动条))
一般情况下可以忽略第2步和第5步。
绘制第2步和第5步要相对耗时一点。
draw(canvas)源码解析
/**
* 手动的把当前的view以及它的所有子view渲染到画布上。
* 在调用此方法之前必须完成完整的layout过程。实现一个view的时候,
* 实现{@link #onDraw(android.graphics.Canvas)}而不是覆盖此方法。
* 覆盖这个方法的时候一定要先调用父类super.draw(canvas)方法。
*
* @param canvas 渲染视图的画布。
*/
@CallSuper public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE && (
mAttachInfo == null
|| !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
// 第1步:绘制背景
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
// 一般情况下省略第2步和第5步
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// 第3步:绘制内容
if (!dirtyOpaque) onDraw(canvas);
// 第4步:绘制子view
dispatchDraw(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// 第6步:绘制decoration
onDrawForeground(canvas);
// 结束
return;
}
/*
* 下面是少数情况下的完整的流程
*/
// Step 2, save the canvas' layers
....省略第2步的代码
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 5, draw the fade effect and restore layers
....省略第5步的代码
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
}
draw(Canvas canvas, ViewGroup parent, long drawingTime)
ViewGroup.drawChild()调用这个方法来让子view绘制自身,这里View根据图层类型和硬件加速特性来渲染。
onDraw
View 中的 onDraw() 方法:
/**
* Implement this to do your drawing.
*
* @param canvas the canvas on which the background will be drawn
*/
protected void onDraw(Canvas canvas) {
}
子类实现这个方法来绘制,参数canvas用来绘制背景画布。
dispatchDraw
/**
* Called by draw to draw the child views. This may be overridden
* by derived classes to gain control just before its children are drawn
* (but after its own view has been drawn).
* @param canvas the canvas on which to draw the view
*/
protected void dispatchDraw(Canvas canvas) {
}
绘制自身之后,绘制子view之前可以调用这个方法。
ViewGroup 中的 drawChild()方法
/**
* 绘制其中一个子view
*
* @param canvas 绘制子view的canvas
* @param child 子view
* @param drawingTime draw发生的时间
* @return 如果是调用invalidate()绘制的话就返回true
*/
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}