Paint常用的方法

先将所有常用的方法列举出来,然后在一 一测试

Paint的三个构造方法
Paint()
// 使用指定flag创建画笔,可以使用setFlags()去修改flag
Paint(int flags)
Paint(Paint paint)

常用的一些方法
// 初始化画笔
paint = new Paint();
// 设置颜色
paint.setColor(Color.RED);
// 设置 Paint对象颜色,范围为0~255
paint.setARGB(255, 255, 255, 0);
// 设置alpha不透明度,范围为0~255
paint.setAlpha(200);
// 抗锯齿
paint.setAntiAlias(true);
//描边效果
paint.setStyle(Paint.Style.FILL);
//描边宽度
paint.setStrokeWidth(4);
//圆角效果
paint.setStrokeCap(Paint.Cap.ROUND);
//拐角风格
paint.setStrokeJoin(Paint.Join.MITER);
//设置环形渲染器
paint.setShader(new SweepGradient(200, 200, Color.BLUE, Color.RED));
//设置图层混合模式
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DARKEN));
//设置颜色过滤器
paint.setColorFilter(new LightingColorFilter(0x00ffff, 0x000000));
//设置双线性过滤
paint.setFilterBitmap(true);
//设置画笔遮罩滤镜 ,传入度数和样式
paint.setMaskFilter(new BlurMaskFilter(10, BlurMaskFilter.Blur.NORMAL));
// 设置文本缩放倍数
paint.setTextScaleX(2);
// 设置字体大小
paint.setTextSize(38);
//对其方式
paint.setTextAlign(Paint.Align.LEFT);
// 设置下划线
paint.setUnderlineText(true);

String str = "Android高级工程师";
Rect rect = new Rect();
//测量文本大小,将文本大小信息存放在rect中
paint.getTextBounds(str, 0, str.length(), rect);
//获取文本的宽
paint.measureText(str);
//获取字体度量对象
paint.getFontMetrics();

在测试之前我们先了解一下Android的坐标系,帮助理解绘制的一些坐标参数。

在 Android 里,每个 View 都有一个自己的坐标系,彼此之间是不影响的。这个坐标系的原点是 View 左上角的那个点;水平方向是 x 轴,右正左负竖直方向是 y 轴,下正上负(注意,是下正上负,不是上正下负,和我们读书那会学的是相反的)。

如下图所示

android 画笔画梯形 安卓画笔_android 画笔画梯形

  1. 设置画笔颜色
paint.setColor(Color.RED);
paint.setColor(Color.parseColor("#ecab50"));
paint.setColor(ContextCompat.getColor(context,R.color.colorAccent))
// ARGB值的范围是0~255
paint.setARGB(255, 255, 255, 0);

android 画笔画梯形 安卓画笔_Android_02

  1. 抗锯齿,下面方法就是开启抗锯齿
paint.setAntiAlias(true);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);

android 画笔画梯形 安卓画笔_View基础_03


可以看到没有开启抗锯齿的时候有一个毛边效果,我们需要知道:

锯齿现象的发生,只是由于图形分辨率过低,导致人眼察觉出了画面中的像素颗粒而已。换句话说,就算不开启抗锯齿,图形的边缘也已经是最完美的了,而并不是一个粗略计算的粗糙版本。

抗锯齿的原理:

修改图形边缘处的像素颜色,从而让图形在肉眼看来具有更加平滑的感觉。

android 画笔画梯形 安卓画笔_android 画笔画梯形_04

  1. 设置画笔描边的样式
paint.setStyle(Paint.Style.FILL);

三种类型:FILL、STROKE、FILL_AND_STROKE
后面两种涉及到边框的样式,我们结合设置描边宽度的方法一起讲解

paint.setStrokeWidth(10);

我们具体看下三种效果:

android 画笔画梯形 安卓画笔_View基础_05

  1. 渲染,介绍我们常用的渲染器

LinearGradient 线性渲染(渐变)
RadialGradient 环形渲染(渐变)
SweepGradient 扫描渲染(渐变)
BitmapShader 位图渲染
ComposeShader 组合渲染,就是组合多个渲染器

线性渲染

/*
 * 1.线性渲染,LinearGradient(float x0, float y0, float x1, float y1, @NonNull @ColorInt int colors[], @Nullable float positions[], @NonNull TileMode tile)
 * (x0,y0):渐变起始点坐标
 * (x1,y1):渐变结束点坐标
 * color0:渐变开始点颜色,16进制的颜色表示,必须要带有透明度
 * color1:渐变结束颜色
 * colors:渐变数组
 * positions:位置数组,position的取值范围[0,1],作用是指定某个位置的颜色值,如果传null,渐变就线性变化。
 * tile:用于指定控件区域大于指定的渐变区域时,空白区域的颜色填充方法,着色模式
 */
shader = new LinearGradient(-250, -250, 250, 250, new int[]{Color.RED, Color.GREEN}, null, Shader.TileMode.CLAMP);
//shader = new LinearGradient(0, 0, 500, 500, new int[]{Color.RED, Color.BLUE, Color.GREEN}, new float[]{0.f,0.7f,1}, Shader.TileMode.REPEAT);
paint.setShader(shader);

android 画笔画梯形 安卓画笔_Paint基础_06


环形渲染

我们需要注意,我们如果需要给渐变位置数组赋值,数组长度一定要等于颜色素组长度,因为一个位置需要对应一个颜色,否则会抛出异常。
/*
 * 环形渲染,RadialGradient(float centerX, float centerY, float radius, @ColorInt int colors[], @Nullable float stops[], TileMode tileMode)
 * centerX ,centerY:shader的中心坐标,开始渐变的坐标
 * radius:渐变的半径
 * centerColor,edgeColor:中心点渐变颜色,边界的渐变颜色
 * colors:渐变颜色数组
 * stoops:渐变位置数组,类似扫描渐变的positions数组,取值[0,1],中心点为0,半径到达位置为1.0f
 * tileMode:shader未覆盖以外的填充模式。
 */
shader = new RadialGradient(0, 0, 300, new int[]{Color.GREEN, Color.MAGENTA, Color.RED}, null, Shader.TileMode.CLAMP);

android 画笔画梯形 安卓画笔_View基础_07

扫描渲染

/*
 * 扫描渲染,SweepGradient(float cx, float cy, @ColorInt int color0,int color1)
 * cx,cy 渐变中心坐标
 * color0,color1:渐变开始结束颜色
 * colors,positions:类似LinearGradient,用于多颜色渐变,positions为null时,根据颜色线性渐变
 */
shader = new SweepGradient(0, 0, new int[]{Color.MAGENTA, Color.GREEN, Color.YELLOW}, null);

android 画笔画梯形 安卓画笔_View基础_08

位图渲染

着色模式:
REPEAT, 绘制区域超过渲染区域的部分,重复排版
CLAMP, 绘制区域超过渲染区域的部分,会以最后一个像素拉伸排版
MIRROR, 绘制区域超过渲染区域的部分,镜像翻转排版

/*
* 位图渲染,BitmapShader(@NonNull Bitmap bitmap, @NonNull TileMode tileX, @NonNull TileMode tileY)
* Bitmap:构造shader使用的bitmap
* tileX:X轴方向的TileMode
* tileY:Y轴方向的TileMode
	 REPEAT, 绘制区域超过渲染区域的部分,重复排版
	 CLAMP, 绘制区域超过渲染区域的部分,会以最后一个像素拉伸排版
	 MIRROR, 绘制区域超过渲染区域的部分,镜像翻转排版
*/
shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);

三种着色器模式对比,看到的效果

android 画笔画梯形 安卓画笔_View基础_09


组合渲染

/*
 * 组合渲染,
 * ComposeShader(@NonNull Shader shaderA, @NonNull Shader shaderB, Xfermode mode)
 * ComposeShader(@NonNull Shader shaderA, @NonNull Shader shaderB, PorterDuff.Mode mode)
 * shaderA,shaderB:要混合的两种shader
 * Xfermode mode: 组合两种shader颜色的模式
 * PorterDuff.Mode mode: 组合两种shader颜色的模式
 */
BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
LinearGradient linearGradient = new LinearGradient(0, 0, 500, 500, new int[]{Color.RED, Color.GREEN, Color.BLUE}, null, Shader.TileMode.CLAMP);
shader = new ComposeShader(bitmapShader, linearGradient, PorterDuff.Mode.MULTIPLY);

组合渲染最后决定出来效果长什么样子的,取决于PorterDuff.Mode这个参数,用来指定画笔的绘制策略。

android 画笔画梯形 安卓画笔_Paint基础_10

PorterDuff.Mode图层混合模式

将所要绘制的图形像素和Canvas中对应位置的像素按照一定规则进行混合,形成新的像素值,从而更新Canvas中最终的像素颜色值。

PorterDuff.Mode 在 Paint 一共有三处 API ,它们的工作原理都一样,只是用途不同:

API

用途

ComposeShader

混合两个Shadr

PorterDuffColorFilter

增加一个单色的ColorFilter

setXfermode

设置绘制内容和View中已有内容的混合模式

PorterDuff.Mode总共有18种,分为两类:

  • Alpha 合成 (Alpha Compositing)
    Alpha 合成,其实就是 「PorterDuff」 这个词所指代的算法。「PorterDuff」并不是一个具有实际意义的词组,而是发表了这篇论文的两个人的名字,描述了 12 种将两个图像共同绘制的操作(即算法),都是关于Alpha(透明度)通道计算的,所以将这类计算叫Alpha合成。
  • 混合 (Blending)
    操作的是颜色本身而不是 Alpha 通道,并不属于 Alpha 合成。所以和 Porter 与 Duff 这两个人也没什么关系,不过为了使用的方便,它们同样也被 Google 加进了 PorterDuff.Mode 里。
所有的模式的用法Google官方Demo都有讲解,后面会贴上我们改写后的demo,
帮助我们理解这所有的模式的用法,关键时候帮助我们正确使用混合模式
// Alpha合成
PorterDuff.Mode.CLEAR 
PorterDuff.Mode.SRC
PorterDuff.Mode.DST
PorterDuff.Mode.SRC_OVER
PorterDuff.Mode.DST_OVER
PorterDuff.Mode.SRC_IN
PorterDuff.Mode.DST_IN
PorterDuff.Mode.SRC_OUT
PorterDuff.Mode.DST_OUT
PorterDuff.Mode.SRC_ATOP
PorterDuff.Mode.DST_ATOP
PorterDuff.Mode.XOR


// 混合,操作的是颜色本身而不是 Alpha 通道,并不属于 Alpha 合成
PorterDuff.Mode.DARKEN
PorterDuff.Mode.LIGHTEN
PorterDuff.Mode.MULTIPLY
PorterDuff.Mode.SCREEN
PorterDuff.Mode.OVERLAY
PorterDuff.Mode.ADD

离屏缓冲

使用离屏缓冲是为了保证绘制结果的正确性,让源图和目标图进行图层混合之后再绘制到Canvas上。

我们看一下对比的结果:

未使用离屏缓冲

跟我们本来的效果相去甚远,按照我们的想法,第二步绘制圆的时候,跟它共同计算的应该是第一步绘制的方形。但实际上,却是整个 View 的显示区域都在画圆的时候参与计算,并且 View 自身的底色并不是默认的透明色,而且是遵循一种迷之逻辑,导致不仅绘制的是整个圆的范围,而且在范围之外都变成了黑色。

android 画笔画梯形 安卓画笔_View基础_11


使用离屏缓冲

通过使用离屏缓冲,把要绘制的内容单独绘制在缓冲层,然后在将共同的结果再绘制到Canvas上, Xfermode 的使用就正确了。

android 画笔画梯形 安卓画笔_View基础_12


所以我们在进行图层混合操作的时候,通过使用离屏缓冲,将需要绘制的内容单独绘制在缓冲区,保证Xfermode的使用不会出错。

使用离屏缓冲有两种方式:

  • Canvas.saveLayer:可以做短时间的离屏缓冲,在绘制之前保存,绘制之后恢复
int layer = canvas.saveLayer(0, 0, mWidth, mHeight, mPaint, Canvas.ALL_SAVE_FLAG);     
canvas.restoreToCount(layer);
  • View.setLayerType():直接把整个View都绘制在离屏缓冲中。
使用GPU来缓冲
setLayerType(LAYER_TYPE_HARDWARE, mPaint);
使用Bitmap来缓冲
setLayerType(LAYER_TYPE_SOFTWARE, mPaint);
  • 使用图层混合的时候,我们需要关闭硬件加速,从3.0(api = 11)之后加入了硬件加速,在4.0(api = 14)之后默认开启了硬件加速。

开闭硬件加速:

1、在清单文件的Application中设置,true:开启  false:关闭
<application android:hardwareAccelerated="false" >

2、在清单文件中的Activity标签中直接设置
<activity android:hardwareAccelerated="false" />  

3、在Window层级下开启硬件加速,Window层级不支持关闭硬件加速
getWindow().setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,  WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); 

4、在View中关闭硬件加速
setLayerType(LAYER_TYPE_SOFTWARE, null);

5、还可以在布局文件中来开闭硬件加速
android:layerType="software"

来一个上面离屏缓冲图中的例子:

// 关闭硬件加速
setLayerType(LAYER_TYPE_SOFTWARE, null);


int layer = canvas.saveLayer(0, 0, mWidth, mHeight, mPaint, Canvas.ALL_SAVE_FLAG);

// 目标图
canvas.drawBitmap(createRectBitmap(mWidth, mHeight), 0, 0, mPaint);
// 设置混合模式
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));

// 源图
canvas.drawBitmap(createCircleBitmap(mWidth, mHeight), 0, 0, mPaint);

// 清除混合模式
mPaint.setXfermode(null);

canvas.restoreToCount(layer);

涉及到的目标图和源图的绘制

//画矩形Dst
public Bitmap createRectBitmap(int width, int height) {
	Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
	Canvas canvas = new Canvas(bitmap);
	Paint dstPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
	dstPaint.setColor(0xFF66AAFF);
	canvas.drawRect(new Rect(width / 20, height / 3, 2 * width / 3, 19 * height / 20), dstPaint);
	return bitmap;
}

//画圆src
public Bitmap createCircleBitmap(int width, int height) {
	Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
	Canvas canvas = new Canvas(bitmap);
	Paint scrPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
	scrPaint.setColor(0xFFFFCC44);
	canvas.drawCircle(width * 2 / 3, height / 3, height / 4, scrPaint);
	return bitmap;
}

最终的结果:

android 画笔画梯形 安卓画笔_android 画笔画梯形_13


public class PaintView3 extends View {
    private Context mContext;
    private Paint mPaint;
    private static final int W = 200;
    private static final int H = 200;
    private static final int ROW_MAX = 4;   // number of samples per row

    private Bitmap mSrcB;
    private Bitmap mDstB;
    private Shader mBG;     // background checker-board pattern

    private static final Xfermode[] sModes = {
            new PorterDuffXfermode(PorterDuff.Mode.CLEAR),
            new PorterDuffXfermode(PorterDuff.Mode.SRC),
            new PorterDuffXfermode(PorterDuff.Mode.DST),
            new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER),
            new PorterDuffXfermode(PorterDuff.Mode.DST_OVER),
            new PorterDuffXfermode(PorterDuff.Mode.SRC_IN),
            new PorterDuffXfermode(PorterDuff.Mode.DST_IN),
            new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT),
            new PorterDuffXfermode(PorterDuff.Mode.DST_OUT),
            new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP),
            new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP),
            new PorterDuffXfermode(PorterDuff.Mode.XOR),
            new PorterDuffXfermode(PorterDuff.Mode.DARKEN),
            new PorterDuffXfermode(PorterDuff.Mode.LIGHTEN),
            new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY),
            new PorterDuffXfermode(PorterDuff.Mode.SCREEN),

            //取两图层全部区域,交集部分饱和度相加
            new PorterDuffXfermode(PorterDuff.Mode.ADD),
            //取两图层全部区域,交集部分叠加
            new PorterDuffXfermode(PorterDuff.Mode.OVERLAY)
    };

    private static final String[] sLabels = {
            "Clear", "Src", "Dst", "SrcOver",
            "DstOver", "SrcIn", "DstIn", "SrcOut",
            "DstOut", "SrcATop", "DstATop", "Xor",
            "Darken", "Lighten", "Multiply", "Screen", "Add", "Overlay"
    };


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

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

    public PaintView3(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.mContext = context;

        init();
    }

    private void init() {
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

        //mPaint.setXfermode()

        mSrcB = makeSrc(W, H);
        mDstB = makeDst(W, H);

        // make a ckeckerboard pattern
        Bitmap bm = Bitmap.createBitmap(new int[]{0xFFFFFFFF, 0xFFCCCCCC,
                        0xFFCCCCCC, 0xFFFFFFFF}, 2, 2,
                Bitmap.Config.RGB_565);
        mBG = new BitmapShader(bm,
                Shader.TileMode.REPEAT,
                Shader.TileMode.REPEAT);
        Matrix m = new Matrix();
        m.setScale(6, 6);
        mBG.setLocalMatrix(m);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);


    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        Paint labelP = new Paint(Paint.ANTI_ALIAS_FLAG);
        labelP.setTextAlign(Paint.Align.CENTER);
        labelP.setTextSize(24);

        Paint paint = new Paint();
        paint.setFilterBitmap(false);

        canvas.translate(15, 35);

        int x = 0;
        int y = 0;

        for (int i = 0; i < sModes.length; i++) {
            // draw the border
            paint.setStyle(Paint.Style.STROKE);
            paint.setShader(null);
            canvas.drawRect(x - 0.5f, y - 0.5f,
                    x + W + 0.5f, y + H + 0.5f, paint);

            // draw the checker-board pattern
            paint.setStyle(Paint.Style.FILL);
            paint.setShader(mBG);
            canvas.drawRect(x, y, x + W, y + H, paint);


            // draw the src/dst example into our offscreen bitmap
            int sc = canvas.saveLayer(x, y, x + W, y + H, null, Canvas.ALL_SAVE_FLAG);
            canvas.translate(x, y);
            canvas.drawBitmap(mDstB, 0, 0, paint);
            paint.setXfermode(sModes[i]);
            canvas.drawBitmap(mSrcB, 0, 0, paint);
            paint.setXfermode(null);
            canvas.restoreToCount(sc);

            // draw the label
            canvas.drawText(sLabels[i],
                    x + W / 2, y - labelP.getTextSize() / 2, labelP);

            x += W + 10;

            // wrap around when we've drawn enough for one row
            if ((i % ROW_MAX) == ROW_MAX - 1) {
                x = 0;
                y += H + 30;
            }
        }
    }


    // create a bitmap with a circle, used for the "dst" image
    private Bitmap makeDst(int w, int h) {
        Bitmap bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        Canvas c = new Canvas(bm);
        Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);

        p.setColor(0xFFFFCC44);
        c.drawOval(new RectF(0, 0, w * 3 / 4, h * 3 / 4), p);
        return bm;
    }

    // create a bitmap with a rect, used for the "src" image
    private Bitmap makeSrc(int w, int h) {
        Bitmap bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        Canvas c = new Canvas(bm);
        Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);

        p.setColor(0xFF66AAFF);
        c.drawRect(w / 3, h / 3, w * 19 / 20, h * 19 / 20, p);
        return bm;
    }
}

android 画笔画梯形 安卓画笔_Android_14

  • 改写官方demo,下面是测试demo,一定要看注释,各个模式的解释都在注释中
public class PaintView4 extends View {
    private static int W = 250;
    private static int H = 250;

    // 最多四列
    private static final int ROW_MAX = 4;

    private Bitmap mSrcB;
    private Bitmap mDstB;
    // 背景的Shader
    private Shader mBG;

    //其中Sa全称为Source alpha表示源图的Alpha通道;Sc全称为Source color表示源图的颜色;
    // Da全称为Destination alpha表示目标图的Alpha通道;Dc全称为Destination color表示目标图的颜色,
    // [...,..]前半部分计算的是结果图像的Alpha通道值,“,”后半部分计算的是结果图像的颜色值。
    //效果作用于src源图像区域
    private static final Xfermode[] sModes = {
            //所绘制不会提交到画布上
            new PorterDuffXfermode(PorterDuff.Mode.CLEAR),
            //显示上层绘制的图像
            new PorterDuffXfermode(PorterDuff.Mode.SRC),
            //显示下层绘制图像
            new PorterDuffXfermode(PorterDuff.Mode.DST),
            //正常绘制显示,上下层绘制叠盖
            new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER),

            //上下层都显示,下层居上显示
            new PorterDuffXfermode(PorterDuff.Mode.DST_OVER),
            //取两层绘制交集,显示上层
            new PorterDuffXfermode(PorterDuff.Mode.SRC_IN),
            //取两层绘制交集,显示下层
            new PorterDuffXfermode(PorterDuff.Mode.DST_IN),
            //取上层绘制非交集部分,交集部分变成透明
            new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT),

            //取下层绘制非交集部分,交集部分变成透明
            new PorterDuffXfermode(PorterDuff.Mode.DST_OUT),
            //取上层交集部分与下层非交集部分
            new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP),
            //取下层交集部分与上层非交集部分
            new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP),
            //去除两图层交集部分
            new PorterDuffXfermode(PorterDuff.Mode.XOR),

            //取两图层全部区域,交集部分颜色加深
            new PorterDuffXfermode(PorterDuff.Mode.DARKEN),
            //取两图层全部区域,交集部分颜色点亮
            new PorterDuffXfermode(PorterDuff.Mode.LIGHTEN),
            //取两图层交集部分,颜色叠加
            new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY),
            //取两图层全部区域,交集部分滤色
            new PorterDuffXfermode(PorterDuff.Mode.SCREEN),

            //取两图层全部区域,交集部分饱和度相加
            new PorterDuffXfermode(PorterDuff.Mode.ADD),
            //取两图层全部区域,交集部分叠加
            new PorterDuffXfermode(PorterDuff.Mode.OVERLAY)
    };

    private static final String[] sLabels = {
            "Clear", "Src", "Dst", "SrcOver",
            "DstOver", "SrcIn", "DstIn", "SrcOut",
            "DstOut", "SrcATop", "DstATop", "Xor",
            "Darken", "Lighten", "Multiply", "Screen", "Add", "Overlay"
    };

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

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

    public PaintView4(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        init();
    }

    private void init() {
        //Resources.getSystem().getDisplayMetrics().widthPixels
        //getContext().getResources().getDisplayMetrics().widthPixels

        WindowManager windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
        if (windowManager != null) {
            DisplayMetrics display = new DisplayMetrics();
            // 获取窗口相关的一些值
            windowManager.getDefaultDisplay().getMetrics(display);
            //得到矩形
            W = H = (display.widthPixels - 64) / ROW_MAX;
        }

        //1,API 14之后,有些函数不支持硬件加速,需要禁用
        setLayerType(View.LAYER_TYPE_SOFTWARE, null);

        mSrcB = makeSrc(W, H);
        mDstB = makeDst(W, H);

        //根据width和height创建空位图,然后用指定的颜色数组colors来从左到右从上至下一次填充颜色
        //make a ckeckerboard pattern
        Bitmap bm = Bitmap.createBitmap(new int[]{0xFFFFFFFF, 0xFFCCCCCC, 0xFFCCCCCC, 0xFFFFFFFF}, 2, 2, Bitmap.Config.RGB_565);
        mBG = new BitmapShader(bm, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
        Matrix m = new Matrix();
        m.setScale(6, 6);
        mBG.setLocalMatrix(m);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        canvas.drawColor(Color.WHITE);

        Paint labelP = new Paint(Paint.ANTI_ALIAS_FLAG);
        labelP.setTextAlign(Paint.Align.CENTER);

        Paint paint = new Paint();
        paint.setFilterBitmap(false);

        canvas.translate(15, 35);

        int x = 0;
        int y = 0;

        for (int i = 0; i < sModes.length; i++) {
            // draw the border
            paint.setStyle(Paint.Style.STROKE);
            paint.setShader(null);
            canvas.drawRect(x - 0.5f, y - 0.5f, x + W + 0.5f, y + H + 0.5f, paint);

            // draw the checker-board pattern
            paint.setStyle(Paint.Style.FILL);
            paint.setShader(mBG);
            canvas.drawRect(x, y, x + W, y + H, paint);

            // 使用离屏绘制
            int sc = canvas.saveLayer(x, y, x + W, y + H, null, Canvas.ALL_SAVE_FLAG);
            canvas.translate(x, y);
            canvas.drawBitmap(makeDst(2 * W / 3, 2 * H / 3), 0, 0, paint);
            // 设置混合模式
            paint.setXfermode(sModes[i]);
            canvas.drawBitmap(makeSrc(2 * W / 3, 2 * H / 3), W / 3, H / 3, paint);
            // 清除混合模式
            paint.setXfermode(null);
            // 回滚离屏绘制
            canvas.restoreToCount(sc);

            // draw the label
            labelP.setTextSize(24);
            canvas.drawText(sLabels[i], x + W / 2, y - labelP.getTextSize() / 2, labelP);

            x += W + 10;

            // wrap around when we've drawn enough for one row
            if ((i % ROW_MAX) == ROW_MAX - 1) {
                x = 0;
                y += H + 30;
            }
        }
    }

    // create a bitmap with a circle, used for the "dst" image
    // 绘制一个完整的圆
    private Bitmap makeDst(int w, int h) {
        Bitmap bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        Canvas c = new Canvas(bm);
        Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);

        p.setColor(0xFFFFCC44);
        c.drawOval(new RectF(0, 0, w, h), p);
        return bm;
    }

    // create a bitmap with a rect, used for the "src" image
    // 矩形右下角留有透明间隙
    private Bitmap makeSrc(int w, int h) {
        Bitmap bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        Canvas c = new Canvas(bm);
        Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);

        p.setColor(0xFF66AAFF);
        c.drawRect(0, 0, w * 19 / 20, h * 19 / 20, p);
        return bm;
    }
}

android 画笔画梯形 安卓画笔_android 画笔画梯形_15

  • 玩一个例子,刮奖小时候大家应该都玩过(刮开灰色的蒙层,然后里面一般都是谢谢惠顾,O(∩_∩)O哈哈~),下面就是刮刮彩的示例。
public class PaintView5 extends View {
    private Context mContext;
    private Paint mPaint;
    private Bitmap mDstBmp, mSrcBmp, mTxtBmp;
    private Path mPath;

    private float mEventX, mEventY;


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

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

    public PaintView5(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        this.mContext = context;
        init();
    }

    private void init() {
        //初始化画笔
        mPaint = new Paint();
        mPaint.setColor(Color.RED);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(80);
        mPaint.setAntiAlias(true);
        mPaint.setStrokeCap(Paint.Cap.ROUND);

        //禁用硬件加速
        setLayerType(View.LAYER_TYPE_SOFTWARE, null);

        // 结果Bitmap,就是刮开之后显示的文字
        mTxtBmp = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.result);
        // 源位图(蒙层)
        mSrcBmp = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.eraser);
        // 目标位图(跟蒙图进行混合的位图)
        mDstBmp = Bitmap.createBitmap(mSrcBmp.getWidth(), mSrcBmp.getHeight(), Bitmap.Config.ARGB_8888);

        //路径(贝塞尔曲线)
        mPath = new Path();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        // 绘制刮奖结果
        canvas.drawBitmap(mTxtBmp, 0, 0, mPaint);

        // 使用离屏缓冲
        int layerId = canvas.saveLayer(new RectF(0, 0, getWidth(), getHeight()), mPaint, Canvas.ALL_SAVE_FLAG);

        //先将路径绘制到bitmap上(在目标位图绘制曲线)
        Canvas dstCanvas = new Canvas(mDstBmp);
        dstCanvas.drawPath(mPath, mPaint);
        // 不要直接在View的canvas上绘制,因为会出现绘制到刮奖区域之外的地方
        // canvas.drawPath(mPath, mPaint);

        // 绘制目标图像
        canvas.drawBitmap(mDstBmp, 0, 0, mPaint);
        //设置 模式 为 SRC_OUT, 擦橡皮区域为交集区域需要清掉像素
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT));
        //绘制源图像
        canvas.drawBitmap(mSrcBmp, 0, 0, mPaint);

        mPaint.setXfermode(null);


        canvas.restoreToCount(layerId);
    }

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mEventX = event.getX();
                mEventY = event.getY();
                // 移动path的绘制起始点到手指按下的地方
                mPath.moveTo(mEventX, mEventY);
                break;
            case MotionEvent.ACTION_MOVE:
                float endX = (event.getX() - mEventX) / 2 + mEventX;
                float endY = (event.getY() - mEventY) / 2 + mEventY;
                //float endX = event.getX();
                //float endY = event.getY();
                // 绘制二阶贝塞尔曲线
                mPath.quadTo(mEventX, mEventY, endX, endY);
                mEventX = event.getX();
                mEventY = event.getY();

                //mEventX = endX;
                //mEventY = endY;
                break;
        }
        invalidate();
        // 消费触摸事件
        return true;
    }
}

android 画笔画梯形 安卓画笔_View基础_16

颜色滤镜

为绘制设置颜色过滤。颜色过滤的意思,就是为绘制的内容设置一个统一的过滤策略,然后 Canvas.drawXXX() 方法会对每个像素都进行过滤后再绘制出来。

用Paint的Paint.setColorFilter(ColorFilter filter)来设置颜色过滤,常用的颜色过滤包括三种:LightingColorFilterPorterDuffColorFilterColorMatrixColorFilter

  • LightingColorFilter
    LightingColorFilter 的构造方法是 LightingColorFilter(int mul, int add) ,参数里的 mul 和 add 都是和颜色值格式相同的 int 值,其中 mul 用来和目标像素相乘,add 用来和目标像素相加。
/*
 * LightingColorFilter(int mul, int add)
 * 计算公式如下
 * R' = R * mul.R / 0xff + add.R
 * G' = G * mul.G / 0xff + add.G
 * B' = B * mul.B / 0xff + add.B
 */
// 原图
// LightingColorFilter lightingColorFilter = new LightingColorFilter(0xffffff,0x000000);
// 绿色加深
LightingColorFilter lightingColorFilter = new LightingColorFilter(0xffffff,0x003000);
// 改变红色
//LightingColorFilter lightingColorFilter = new LightingColorFilter(0x11ffff,0x000000);
mPaint.setColorFilter(lightingColorFilter);

android 画笔画梯形 安卓画笔_Android_17

  • PorterDuffColorFilter
    使用一个指定的颜色和一种指定的 PorterDuff.Mode 来与绘制对象进行合成。
// PorterDuffColorFilter porterDuffColorFilter = new PorterDuffColorFilter(Color.parseColor("#ff0000"), PorterDuff.Mode.DARKEN);
// 设置颜色的时候,需要设置Alpha通道,混合模式就是去修改Alpha通道的值,否则有的模式不会有效果的改变
PorterDuffColorFilter porterDuffColorFilter = new PorterDuffColorFilter(0xaaffff00, PorterDuff.Mode.SCREEN);
mPaint.setColorFilter(porterDuffColorFilter);

android 画笔画梯形 安卓画笔_Android_18

  • ColorMatrixColorFilter
    ColorMatrixColorFilter有两个构造方法:
    1、ColorMatrixColorFilter(@NonNull ColorMatrix matrix)
          其实ColorMatrix 这个类,内部也是维护的一个 4x5 的矩阵:
    2、ColorMatrixColorFilter(@NonNull float[] array)
          直接传递一个4x5的矩阵(就是数组)
[ a, b, c, d, e,
  f, g, h, i, j,
  k, l, m, n, o,
  p, q, r, s, t ]

ColorMatrix 可以把要绘制的像素进行转换。对于颜色 [R, G, B, A] ,转换算法是这样的:

R’ = a*R + b*G + c*B + d*A + e;  
G’ = f*R + g*G + h*B + i*A + j;  
B’ = k*R + l*G + m*B + n*A + o;  
A’ = p*R + q*G + r*B + s*A + t;

示例代码

// 原图
/*float[] colorMatrix = {
		// 最后一列是偏移量
		1, 0, 0, 0, 0, // red
		0, 1, 0, 0, 0, // green
		0, 0, 1, 0, 0, // blue
		0, 0, 0, 1, 0  // alpha
};*/

// 如果我们需要改变各种效果,直接修改矩阵中的值
/*float[] colorMatrix = {
		// 最后一列是偏移量
		1, 1, 0, 0, 0, // red
		0, 2, 0, 0, 0, // green
		0, 1, 1, 0, 0, // blue
		0, 0, 0, 1, 0  // alpha
};*/

ColorMatrix colorMatrix = new ColorMatrix();
// 调节亮度,刚好改变的原图中矩阵中对角线的值
//colorMatrix.setScale(1.2f,1f,1.5f,2);

// 调节饱和度 0-无色彩 1-默认效果 >1-饱和度增强
//colorMatrix.setSaturation(2);

//色度调节
colorMatrix.setRotate(1,50);

ColorMatrixColorFilter colorMatrixColorFilter = new ColorMatrixColorFilter(colorMatrix);
mPaint.setColorFilter(colorMatrixColorFilter);

android 画笔画梯形 安卓画笔_View基础_19


android 画笔画梯形 安卓画笔_Paint常用方法_20


几个常见的滤镜矩阵

// 黑白
public static final float[] colormatrix_heibai = {
		0.8f, 1.6f, 0.2f, 0, -163.9f,
		0.8f, 1.6f, 0.2f, 0, -163.9f,
		0.8f, 1.6f, 0.2f, 0, -163.9f,
		0, 0, 0, 1.0f, 0};
// 怀旧
public static final float[] colormatrix_huajiu = {
		0.2f, 0.5f, 0.1f, 0, 40.8f,
		0.2f, 0.5f, 0.1f, 0, 40.8f,
		0.2f, 0.5f, 0.1f, 0, 40.8f,
		0, 0, 0, 1, 0};
		
// 胶片
public static final float[] colormatrix_fanse = {
		-1.0f, 0.0f, 0.0f, 0.0f, 255.0f,
		0.0f, -1.0f, 0.0f, 0.0f, 255.0f,
		0.0f, 0.0f, -1.0f, 0.0f, 255.0f,
		0.0f, 0.0f, 0.0f, 1.0f, 0.0f};
		
// 复古
public static final float[] colormatrix_fugu = {
		0.9f, 0.0f, 0.0f, 0.0f, 0.0f,
		0.0f, 0.8f, 0.0f, 0.0f, 0.0f,
		0.0f, 0.0f, 0.5f, 0.0f, 0.0f,
		0, 0, 0, 1.0f, 0};

其余更多滤镜的效果,有兴趣可以自己去尝试修改,文章最后也给了一个Github的例子参考,是参考文章中的StyleImageView。

  1. 线条形状
  • setStrokeCap(Paint.Cap cap)
    设置线头的形状。线头形状有三种:BUTT 平头、ROUND 圆头、SQUARE 方头。默认为 BUTT。
  • setStrokeJoin(Paint.Join join)
    设置拐角的形状。有三个值可以选择:MITER 尖角、 BEVEL 平角和 ROUND 圆角。默认为 MITER。
  • setStrokeMiter(float miter)
    这个方法是对于 setStrokeJoin() 的一个补充,它用于设置 MITER 型拐角的延长线的最大值。
    当线条拐角为 MITER 时,拐角处的外缘需要使用延长线来补偿,但是补偿也存在一个问题,如果拐角的角度太小,就有可能由于出现连接点过长的情况。
    为了避免意料之外的过长的尖角出现, MITER 型连接点有一个额外的规则:当尖角过长时,自动改用 BEVEL 的方式来渲染连接点。

使用setStrokeMiter(miter) 方法中的 miter 参数来进行限制。miter 参数是对于转角长度的限制,具体来讲,是指尖角的外缘端点和内部拐角的距离与线条宽度的比

android 画笔画梯形 安卓画笔_Paint常用方法_21


如果拐角的大小为 θ ,那么这个比值就等于 miter = 1 / sin ( θ / 2 )

  1. setDither(boolean dither)
    指把图像从较高色彩深度(即可用的颜色数)向较低色彩深度的区域绘制时,在图像中有意地插入噪点,通过有规律地扰乱图像来让图像对于肉眼更加真实的做法。
  2. setFilterBitmap(boolean filter)
    图像在放大绘制的时候,默认使用的是最近邻插值过滤,这种算法简单,但会出现马赛克现象;而如果开启了双线性过滤,就可以让结果图像显得更加平滑。
mPaint.setFilterBitmap(true)

android 画笔画梯形 安卓画笔_Paint基础_22

  1. 文字相关的方法
String str = "Android高级工程师";
Rect rect = new Rect();
//测量文本大小,将文本大小信息存放在rect中
paint.getTextBounds(str, 0, str.length(), rect);
//获取文本的宽
paint.measureText(str);
//获取字体度量对象,FontMetrics
paint.getFontMetrics();

讲解下FontMetrics,它里面有五个属性top,ascent,descent,bottom,leading。

android 画笔画梯形 安卓画笔_Paint常用方法_23


注意:文字的绘制跟我们平常的View的绘制有点不一样,它是从左下角开始绘制的。

文字的绘制方法

// 传入字符数组text[],从下标index开始,绘制count位
drawText(@NonNull char[] text, int index, int count, float x, float y,
            @NonNull Paint paint)

// 传入text字符串,指定文字绘制的起始点(x,y)坐标             
drawText(@NonNull String text, float x, float y, @NonNull Paint paint)

// 传入字符串,从start开始绘制,绘制范围[start, end),取左不取右
drawText(@NonNull String text, int start, int end, float x, float y,
            @NonNull Paint paint)

// CharSequence是String的父类,也可以看成是传入的字符串,绘制范围[start, end),起始坐标(x, y)
drawText(@NonNull CharSequence text, int start, int end, float x, float y,
            @NonNull Paint paint)

还有很多Paint其他的用法,建议去看参考文章中HenCoder的自定义View。