Android 自定义View

Android 自定义View

一 简单的自定义View

Android View 的绘制流程主要有 onMeasure, onLayout, onDraw. UI 内容的绘制主要在onDraw 中完成。

1.1 首先定义View类MyView

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"/>

1.2 重写onDraw 方法

在这里重写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);
    }

1.3 自定义属性

View 可以通过xml直接设置属性,MyView 实现一个属性dy_draw,表示是动态画一个圆还是静态画一些图形。

res/valus/attrs.xml 中定义属性:

format 表示类型:有integer boolean fraction refence color等类型。

<?xml version="1.0" encoding="utf-8"?>
<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();
    }

显示效果
这里写图片描述

二 android.graphics 概念

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<Paint*>(paintHandle);
    Typeface* typeface = reinterpret_cast<Typeface*>(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<Canvas*>(canvasHandle);
}

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<jlong>(Canvas::create_canvas(bitmap));
}

Canvas

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

Bitmap 就是描述绘制内容的的内存,用来保存像素。

Paint 类

Paint 表示绘制用的画笔,比如可以设置绘制文字的大小,颜色等。
自定义控件三部曲之绘图篇(七)——Paint之函数大汇总

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值