Android自定义View基础

1,414 阅读7分钟

Android自定义控件绘图基础

首先继承View,并进行相关初始化操作。

对于DrawAllocation的Lint警告提示:

不要在View绘制和做布局操作的时候实例化数据,将创建对象等这些分配内存资源和会引起垃圾回收机制的操作在onDraw/onLayout之前进行,例如设置为全局变量,提取一个init()方法来实例化对象。

因为在View及其子类的onDraw(Canvas canvas)方法,会实时调用以更新界面,会频繁的创建对象和进行垃圾回收,垃圾回收的GC线程会抢占CPU资源影响UI的显示性能,这样一个显示很顺畅的用户界面就会因对象分配引起的一些垃圾回收机制进行短暂的停滞。

public class BasisView extends View {
    private Paint mPaint;
    private RectF mRectF;
    private Rect mRect;
    private Bitmap mBitmap;
    private Path mPath;

    public BasisView(Context context) {
        this(context, null);
    }

    public BasisView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);

    }

    public BasisView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    /**
     * Lint警告:
     * Avoid object allocations during draw/layout operations (preallocate and reuse instead)
     * Issue: Looks for memory allocations within drawing code
     * Id: DrawAllocation
     * You should avoid allocating objects during a drawing or layout operation. These are called frequently, so a smooth UI can be interrupted by garbage collection pauses caused by the object allocations.
     * The way this is generally handled is to allocate the needed objects up front and to reuse them for each drawing operation.
     * Some methods allocate memory on your behalf (such as Bitmap.create), and these should be handled in the same way.
     */
    private void init() {
        mPaint = new Paint();
        mRectF = new RectF();
        mRect = new Rect();
        mBitmap = getBitmap(getContext(), R.mipmap.ic_launcher);
        mPath = new Path();
    }
    
    @Override
 protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

 }
}

0、画笔Paint

文本相关

setColor(@ColorInt int color) 设置画笔颜色 
setStrokeWidth(float width) 设置画笔粗细 
setTextSkewX(float f) 设置倾斜,负右斜,正为左 
setARGB(int a, int r, int g, int b) 设置颜色,a为透明度 
setTextSize(float textSize) 设置绘制文字大小 
setFakeBoldText(boolean fakeBoldText) 是否粗体 
setTextAlign(Paint.Align align) 设置文字对齐方式,LEFT,CENTER,RIGHT setUnderlineText(boolean underlineText) 设置下划线 
setStyle(Style style) 设置画笔样式,FILL,STROKE,FILL_AND_STROKE setTypeface(Typeface typeface) 设置Typeface对象,即字体风格,包括粗体,斜体以及衬线体,非衬线体等

位图相关

setDither(boolean dither) 设置抖动处理 
setAlpha(int a) 设置透明度 
setAntiAlias(boolean aa) 是否开启抗锯齿 
setFilterBitmap() 是否开启优化Bitmap 
setColorFilter(ColorFilter filter) 设置颜色过滤 
setMaskFilter(MaskFilter maskfilter) 设置滤镜的效果 
setShader(Shader shader) 设置图像渐变效果 
setStrokeJoin(Paint.Join join) 设置图像结合方式 
setXfermode(Xfermode xfermode) 设置图像重叠效果 
setPathEffect(PathEffect effect) 设置路径效果 reset() 恢复默认设置

1、路径

moveTo()绘制起始的点
lineTo()绘制连接的点
close()形成闭环
arcTo()弧线路径
参数:生成椭圆的矩形、弧线开始的角度、弧线扫描过的角度、是否强制地将弧线的起始点作为绘制起始位置
Region 区域,一块任意形状的封闭图形。使用RegionIterator,用于区域相交填充操作。

2、画布Canvas

2.1、清除画布

/**
 * 画布清屏
 */
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);

2.2、绘制画布背景

/**
 * 绘制画布背景
 */
//绘制画布背景,argb
canvas.drawARGB(99, 255, 0, 255);
canvas.drawRGB(255, 0, 255);
canvas.drawColor(Color.LTGRAY);

2.3、绘制圆形

/**
 * 绘制圆形
 */
//设置画笔的基本属性
//设置画笔颜色
mPaint.setColor(Color.RED);
//设置画笔填充样式
//仅填充内部
mPaint.setStyle(Paint.Style.FILL);
//填充内部和描边
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
//仅填充描边
mPaint.setStyle(Paint.Style.STROKE);
//设置描边宽度,单位px,当填充样式是FILL_AND_STROKE时候有效
mPaint.setStrokeWidth(20);
//使用画布画圆
canvas.drawCircle(200, 200, 150, mPaint);
mPaint.setColor(Color.BLUE);
canvas.drawCircle(200, 200, 130, mPaint);

2.4、绘制点

/**
 * 绘制点
 */
//点的大小
mPaint.setStrokeWidth(20);
//绘制点,x坐标、y坐标
canvas.drawPoint(340, 340, mPaint);

2.5、绘制直线

/**
 * 绘制直线
 */
//直线粗细
mPaint.setStrokeWidth(20);
//绘制直线起点x坐标、起点y坐标、终点x坐标、终点y坐标
canvas.drawLine(360, 360, 660, 660, mPaint);

2.6、绘制

/**
 * 绘制矩形
 */
//矩形的边框粗细
mPaint.setStrokeWidth(5);
//仅填充矩形描边
mPaint.setStyle(Paint.Style.STROKE);
//绘制矩形,RectF是保存float类型的矩形,Rect是保存int类型的矩形,左上右下
mRectF.set(400F, 400F, 450F, 450F);
mRect.set(500, 500, 550, 550);
canvas.drawRect(mRectF, mPaint);
canvas.drawRect(mRect, mPaint);

2.7、绘制扇形

/**
 * 绘制扇形
 */
mRectF.set(600, 600, 700, 700);
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
//椭圆、开始角度、扇形扫描过的角度、是否有焦点圆心,画笔,停止角度=开始角度+扇形扫描过的角度
//true,绘制全部扇形
//false,绘制开始点到结束点之间的区域,也就是扇形去除三角形后的区域
canvas.drawArc(mRectF, -45, 180, true, mPaint);

2.8、绘制位图

/**
 * 绘制位图,bitmap,左,上,画笔
 *
 setDither(boolean dither) 设置抖动处理
 setAlpha(int a) 设置透明度
 setAntiAlias(boolean aa) 是否开启抗锯齿
 setFilterBitmap() 是否开启优化Bitmap
 setColorFilter(ColorFilter filter) 设置颜色过滤
 setMaskFilter(MaskFilter maskFilter) 设置滤镜的效果
 setShader(Shader shader) 设置图像渐变效果
 setStrokeJoin(Paint.Join join) 设置图像结合方式
 setXfermode(Xfermode xfermode) 设置图像重叠效果
 setPathEffect(PathEffect effect) 设置路径效果 reset() 恢复默认设置
 */
canvas.drawBitmap(mBitmap, 90, 90, mPaint);

其中位图的获取需要进行版本适配: 其中R.mipmap.ic_launcher是一个vector图片,在5.0及以上的系统会出现空指针,原因在于此版本BitmapFactory.decodeResource方法不能将vector转化为bitmap。

/**
 * BitmapFactory.decodeResource为null的处理方法。
 *
 * @param context
 * @param vectorDrawableId
 * @return
 */
private static Bitmap getBitmap(Context context, int vectorDrawableId) {
    Bitmap bitmap;
    if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
        Drawable vectorDrawable = context.getDrawable(vectorDrawableId);
        bitmap = Bitmap.createBitmap(vectorDrawable.getIntrinsicWidth(),
                vectorDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        vectorDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
        vectorDrawable.draw(canvas);
    } else {
        bitmap = BitmapFactory.decodeResource(context.getResources(), vectorDrawableId);
    }
    return bitmap;
}

2.9、绘制文字

/**
 * 绘制文字
 * setColor(@ColorInt int color) 设置画笔颜色
 * setStrokeWidth(float width) 设置画笔粗细
 * setTextSkewX(float f) 设置倾斜,负右斜,正为左
 * setARGB(int a, int r, int g, int b) 设置颜色,a为透明度
 * setTextSize(float textSize) 设置绘制文字大小
 * setFakeBoldText(boolean fakeBoldText) 是否粗体
 * setTextAlign(Paint.Align align) 设置文字对齐方式,LEFT,CENTER,RIGHT setUnderlineText(boolean underlineText) 设置下划线
 * setStyle(Style style) 设置画笔样式,FILL,STROKE,FILL_AND_STROKE setTypeface(Typeface typeface) 设置Typeface对象,即字体风格,包括粗体,斜体以及衬线体,非衬线体等
 */
mPaint.setTextSize(90f);
canvas.drawText("Android Stack", 200, 1000, mPaint);

2.10、绘制路径

/**
 * 绘制路径
 * moveTo()绘制的起始点
 * lineTo()连接的点
 */
mPath.moveTo(600, 400);
mPath.lineTo(700, 300);
mPath.lineTo(700, 400);
mPath.lineTo(600, 500);
//闭环
mPath.close();
mPaint.setStrokeWidth(5);
mPaint.setStyle(Paint.Style.STROKE);
canvas.drawPath(mPath, mPaint);

2.11、绘制圆角矩形

/**
 * 绘制圆角矩形
 */
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(5);
mRectF.set(100, 850, 850, 1100);
canvas.drawRoundRect(mRectF, 200f, 200f, mPaint);

2.12、绘制椭圆

/**
 * 绘制椭圆
 */
mRectF.set(100, 1200, 300, 1300);
canvas.drawOval(mRectF, mPaint);

2.13、绘制路径上的文本

/**
 * 在路径上绘制文本
 */
mPaint.setTextSize(60f);
canvas.drawTextOnPath("android stack", mPath, 0f, -20f, mPaint);

3、画布操作

当每次调用Canvas的drawXXX系列函数来绘图时,都会产生一个全新的Canvas透明图层。 调用Canvas的平移、旋转等函数对Canvas进行的操作是不可逆的,操作后调用Canvas的drawXXX系列函数产生的画布最新位置,就是操作后的坐标系位置。 在Canvas图层与屏幕合成时,超出屏幕范围的图像是不会显示出来的。

3.1、平移

坐标系的位置会随着Canvas左上角点的移动而移动,但仅仅只是坐标系移动了而已,已经绘制的内容不会跟着移动。

/**
 * 平移操作
 * 水平方向向平移动距离
 * 垂直方向向下平移距离
 * 坐标系的位置会随着Canvas左上角点的移动而移动
 */
canvas.translate(10F, 10F);

3.2、裁剪画布clip

利用clip系列函数,通过与Rect、Path、Region取交、并、差等集合运算来获取最新的画布形状,且需要禁用硬件加速功能。

setLayerType(LAYER_TYPE_SOFTWARE, null);

除了调用save()和restore()函数之外,此操作不可逆,一旦Canvas被裁剪,不可恢复。

3.3、画布的保存和恢复

3.3.1、保存

save()

底层调用的是native函数:

    public int save() {
        return nSave(mNativeCanvasWrapper, MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG);
    }
    @CriticalNative
    private static native int nSave(long canvasHandle, int saveFlags);

每次调用此函数,会先保存当前画布状态,将其放入特定栈中。

3.3.2、恢复

restore()

底层调用的是native函数:

    public void restore() {
        if (!nRestore(mNativeCanvasWrapper)
                && (!sCompatibilityRestore || !isHardwareAccelerated())) {
            throw new IllegalStateException("Underflow in restore - more restores than saves");
        }
    }
    @CriticalNative
    private static native boolean nRestore(long canvasHandle);

每次调用此函数,都会在栈中顶层的画布状态取出来,并按照这个状态恢复当前的画布,然后在这个画布上作画。

4、源码位置

android.graphics包下的类对应的源码位置是: https://cs.android.com/android/platform/superproject/+/master:frameworks/base/graphics/

4、效果图及DEMO

DEMO地址:

https://github.com/chaozhouzhang/CustomProgressView

欢迎关注微信公众号,Android技术堆栈: