Android View 的绘制流程主要有 onMeasure, onLayout, onDraw. UI 内容的绘制主要在onDraw 中完成。
MyView 继承View, 实现两个构造方法。MyView(Context context, @Nullable AttributeSet attrs)是在XML中定义时解析时使用的构造方法。XML中的属性被解析为AttributeSet类。
public class MyView extends View {
public MyView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public MyView(Context context) {
super(context);
}
}
MyView 有View的基本属性,所以在layout 中可以配置View的基本属性:
<com.dawang.androidexample.view.MyView
android:layout_height="match_parent"
android:layout_width="match_parent"/>
在这里重写onDraw 方法,使用Canvas 类完成图形的绘制.
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if(mDyDraw) {
drawDynamicCircle(canvas);
} else {
drawText(canvas);
drawLine(canvas);
drawRoundRect(canvas);
drawCircleStroke(canvas);
drawCircleFill(canvas);
drawPath(canvas);
drawOval(canvas);
drawArc(canvas);
}
}
在onDraw 方法中,绘制了各种图形,主要的内容涉及 Canvas 类和Paint 类。方法如下
void drawText(Canvas canvas){
mPaint.setTextSize(30);
mPaint.setShadowLayer(10, 55, 22, Color.GREEN);
canvas.drawText("View Hello World", 0, 30, mPaint);
}
void drawLine(Canvas canvas){
mPaint.setColor(0xffffff00);
mPaint.setStrokeWidth(10);
canvas.drawLine(0, 60, 200, 60, mPaint);
}
void drawRoundRect(Canvas canvas){
mPaint.setARGB(255, 255, 0, 255);
Rect rect = new Rect(10, 60, 100, 100);
mPaint.setShadowLayer(30, 5, 2, Color.DKGRAY);
canvas.drawRoundRect(new RectF(rect), 10, 10, mPaint);
}
void drawCircleStroke(Canvas canvas){
mPaint.setAntiAlias(true);
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(5);
mPaint.setShadowLayer(5, 15, 15, Color.LTGRAY);
// canvas.drawRGB(255, 255,255);
canvas.drawCircle(150, 200, 100, mPaint);
}
void drawCircleFill(Canvas canvas){
mPaint.setAntiAlias(true);
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setStrokeWidth(5);
canvas.drawCircle(380, 200, 100, mPaint);
}
void drawPath(Canvas canvas){
mPaint.reset();
mPaint.setColor(0xff00ff00);
mPaint.setStrokeWidth(10);
mPaint.setStyle(Paint.Style.STROKE);
Path path = new Path();
path.moveTo(0, 500);
path.lineTo(450, 200);
path.quadTo(0, 500, 40, 570);
canvas.drawPath(path, mPaint);
}
void drawOval(Canvas canvas){
Rect rect = new Rect(100, 500, 400, 600);
canvas.drawRect(rect, mPaint);
mPaint.setColor(Color.BLUE);
canvas.drawOval(new RectF(rect), mPaint);
}
void drawArc(Canvas canvas){
// Rect rect = new Rect(400, 500, 600, 600);
Rect rect = new Rect(100, 500, 400, 600);
mPaint.setColor(Color.RED);
canvas.drawArc(new RectF(rect), 0, 90, false, mPaint);
}
void drawDynamicCircle(Canvas canvas){
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(10);
int width = getWidth();
int height = getHeight();
int min = width < height ? width:height;
Rect rect = new Rect(width/2 - min/2, (height-min)/2, width/2+min/2, (height+min)/2);
if(mDyCircle % 4 == 0) {
mPaint.setColor(Color.RED);
canvas.drawArc(new RectF(rect), 0, 90, false, mPaint);
} else if(mDyCircle % 4 == 1) {
mPaint.setColor(Color.BLUE);
canvas.drawArc(new RectF(rect), 90, 90, false, mPaint);
} else if(mDyCircle % 4 == 2) {
mPaint.setColor(Color.DKGRAY);
canvas.drawArc(new RectF(rect), 180, 90, false, mPaint);
} else if(mDyCircle % 4 == 3) {
mPaint.setColor(Color.YELLOW);
canvas.drawArc(new RectF(rect), 270, 90, false, mPaint);
}
mDyCircle ++;
postInvalidateDelayed(1000);
}
View 可以通过xml直接设置属性,MyView 实现一个属性dy_draw,表示是动态画一个圆还是静态画一些图形。
format 表示类型:有integer boolean fraction refence color等类型。
<resources>
<declare-styleable name="MyView" >
<attr name="dy_draw" format="boolean" />
declare-styleable>
resources>
在构造函数中获取属性
public MyView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MyView);
mDyDraw = array.getBoolean(R.styleable.MyView_dy_draw, false);
array.recycle();
}
Canvas 和 Paint 概念涉及Android 图形知识。Android 的二维图形绘制上基于Skia 完成。Skia 是一个基于C++开发的图形库。Android Java 层的图形绘制概念基本上来自于Skia. Canvas Paint Bitmap native等都是对Skia中各个 概念的继承, 了解Skia API 对理解view的绘制有很大帮助。在 android.graphics包中,这个包的注释中:
Provides low level graphics tools such as canvases, color filters, points, and
rectangles that let you handle drawing to the screen directly.
java 代码:frameworks/base/graphics/java/android/graphics/
jni 代码: frameworks/base/core/jni/android/graphics/
frameworks/base/core/jni/android_graphics_*.cpp
以Canvas drawText 方法为例,看下具体的实现:
Canvas#drawText 通过Jni 调用drawTextString,然后通过get_canvas 把canvasHandle 获取到native Canvas, Jni native canvasHandle 对应于java 的mNativeCanvasWrapper
public void drawText(@NonNull String text, float x, float y, @NonNull Paint paint) {
native_drawText(mNativeCanvasWrapper, text, 0, text.length(), x, y, paint.mBidiFlags,
paint.getNativeInstance(), paint.mNativeTypeface);
}
native_drawText:
static void drawTextString(JNIEnv* env, jobject, jlong canvasHandle, jstring text,
jint start, jint end, jfloat x, jfloat y, jint bidiFlags,
jlong paintHandle, jlong typefaceHandle) {
Paint* paint = reinterpret_cast(paintHandle);
Typeface* typeface = reinterpret_cast(typefaceHandle);
const int count = end - start;
const jchar* jchars = env->GetStringChars(text, NULL);
get_canvas(canvasHandle)->drawText(jchars + start, 0, count, count, x, y,
bidiFlags, *paint, typeface);
env->ReleaseStringChars(text, jchars);
}
static Canvas* get_canvas(jlong canvasHandle) {
return reinterpret_cast
Java 的 mNativeCanvasWrapper 在Canvas 构造的视同通过initRaster获取,initRaster又是一个Jni调用,最终通过Canvas::create_canvas 得到真正的Canvas, 饶了一圈回到了native。
public Canvas() {
if (!isHardwareAccelerated()) {
// 0 means no native bitmap
mNativeCanvasWrapper = initRaster(null);
mFinalizer = NoImagePreloadHolder.sRegistry.registerNativeAllocation(
this, mNativeCanvasWrapper);
} else {
mFinalizer = null;
}
}
static jlong initRaster(JNIEnv* env, jobject, jobject jbitmap) {
SkBitmap bitmap;
if (jbitmap != NULL) {
GraphicsJNI::getSkBitmap(env, jbitmap, &bitmap);
}
return reinterpret_cast(Canvas::create_canvas(bitmap));
}
Android 对绘制要求四个基本的元素,Canvas Bitmap primitive(Rect, Path, text, Bitmap) Paint.
对Canvas的定义, Canvas 拥有draw 方法,可以绘制各种文字,图片 Path等。
/**
* 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).
*
Bitmap 就是描述绘制内容的的内存,用来保存像素。
Paint 表示绘制用的画笔,比如可以设置绘制文字的大小,颜色等。
自定义控件三部曲之绘图篇(七)——Paint之函数大汇总