Paint画笔

一、概念

画笔,保存了绘制几何图形、文本和位图的样式和颜色信息

二、常用API

通过ALT+7查看Paint类结构图,发现有大量的get、set方法。里面还存有很多的native方法, 实际上当我们调用Paint类中的方法时,实际上是间接调用了native方法。以setSubpixelText方法为例它就直接调用native方法nSetSubpixelText():

public void setSubpixelText(boolean subpixelText) {
    nSetSubpixelText(mNativePaint, subpixelText);
}

nSetSubpixelText()就是一个native方法:

@CriticalNative
private static native void nSetSubpixelText(long paintPtr, boolean subpixelText);

这需要我们掌握Paint类中常用的一些API方法:

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

String str="Android高级开发工程师";
Rect rect = new Rect();
mPaint.getTextBounds(str,0,str.length(),rect);//测量文本大小,将文本大小信息存放在rect中
mPaint.measureText(str);//获取文本的宽
mPaint.getFontMetrics();//获取文体度量对象
  • 1.setColor(int Color)参数具体的颜色纸,16进制数值,0xFFFF0000
  • 2.setARGB(int a,int r,int g,int b)参数:分别透明度、红、绿、蓝。0-255数值
  • 3.setShader(Shader shader)参数着色器对象,一般使用shader的几个子类
    • LinearGradient:线性渲染
    • RadialGradient:环形渲染
    • SweepGradient:扫描渲染
    • BitmapShader:位图渲染
    • ComposeShader:组合渲染,只能2个组合。例如LinearGradient+BitmapShader
  • 4.setColorFilter(ColorFilter colorFilter)设置颜色过滤。一般使用ColorFilter三个子类:
    • PorterDuffColorFilter:指定一个颜色和一种PorterDuff.Mode与绘制对象进行合成
    • ColorMatrixColorFilter:使用一个ColorMatrix来对颜色进行处理
    • LightingColorFilter光照效果

接下来重点看一下渲染器。

三、渲染器

3.1 LinearGradient线性渲染

构造方法:

public 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】,作用是指定某个位置的颜色值。
  • title:用于指定控件区域大于指定的渐变区域时,空白区域的颜色填充方法。端点范围之外的着色规则,类型是TitleMode

对于position,new float[]{0.5f,1} 影响了渐变的效果,是相对位置。即50%~100%才开始渐变 。可为null,null就表示渐变为线性变化:

//如果positions不为空且其长度与颜色长度不相等,就抛出异常
if (positions != null && colors.length != positions.length) {
    throw new IllegalArgumentException("color and position arrays must be of equal length");
} 

使用:

		mBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
        mPaint = new Paint();//初始化
        mPaint.setAntiAlias(true);//抗锯齿
        mPaint.setStyle(Paint.Style.FILL);
        //1.线性渲染
        mShader = new LinearGradient(0,0,500,500,new int[]{Color.RED,Color.BLUE},new float[]{0.5f,1}, Shader.TileMode.CLAMP);
        mPaint.setShader(mShader);
        canvas.drawCircle(250,250,250,mPaint);

效果:

由红向蓝渐变,如果圆不明显,可以改成矩形
在这里插入图片描述

3.2 环形渲染RadialGradient

构造方法:

public RadialGradient(float centerX, float centerY, float radius,
        @NonNull @ColorInt int colors[], @Nullable float stops[],
        @NonNull TileMode tileMode)

参数:

  • centerX,CenterY:辐射中心的坐标
  • radius:辐射半径
  • centerColor:辐射中心的颜色
  • edgeColor:辐射边缘的颜色
  • colors:渐变颜色数组
  • stops:渐变位置数组,类似扫描渐变的position数组,取值【0,1】中心点为0,半径到达位置为1.0f
  • tileMode:辐射范围之外的着色规则,类型是TileMode。shader表示未覆盖以外的 填充方式

使用:

		//2.环形渲染
        mPaint.setShader(new RadialGradient(250,250,500,new int[]{Color.RED,Color.BLUE,Color.YELLOW},null, Shader.TileMode.CLAMP));
        canvas.drawCircle(250,250,250,mPaint);

效果:

由圆心开始向外环形渲染
在这里插入图片描述

3.3 扫描渲染SweepGradient

构造方法:

public SweepGradient(float cx, float cy, @ColorInt int color0, @ColorInt int color1)

参数:

  • cx,cy:扫描的中心
  • color0:扫描的起始位置
  • color1:扫描的终止位置

使用:

		//3.扫描渲染
        mPaint.setShader(new SweepGradient(250,250,Color.RED,Color.BLUE));
        canvas.drawCircle(250,250,250,mPaint);

效果:

从x轴正方向,沿着顺时针方向选择360°
在这里插入图片描述

3.4 位图渲染BitmapShader

构造方法:

public BitmapShader(@NonNull Bitmap bitmap, @NonNull TileMode tileX, @NonNull TileMode tileY)

参数:

  • bitmap:用来做模板的Bitmap对象
  • tileX:X轴方向的着色规则,类型是TileMode
  • tileY:Y轴方向的着色规则,类型是TileMode

使用:

		//4.位图渲染
        mPaint.setShader(new BitmapShader(mBitmap,Shader.TileMode.CLAMP,Shader.TileMode.CLAMP));
//        canvas.drawRect(0,0,mBitmap.getWidth(),mBitmap.getHeight(),mPaint);
        canvas.drawCircle(250,250,250,mPaint);

效果:
在水平、垂直方向的最后一个像素,有一个拉伸填充效果。这个效果源自参数TileMode:

  • CLAMP 当绘制的区域超出图片自身的区域,会以最后一个像素进行拉伸、填充
  • REPEAT 绘制区域超出渲染区域的部分,重复排版–平铺效果,也就是copy
  • MIRROR 绘制区域超出渲染区域的部分,镜像填充

3.5 组合渲染ComposeShader

构造方法:

public ComposeShader(@NonNull Shader shaderA, @NonNull Shader shaderB,
        @NonNull PorterDuff.Mode mode)
        
public ComposeShader(@NonNull Shader shaderA, @NonNull Shader shaderB, @NonNull Xfermode mode)

参数:

  • shaderA,shaderB:要混合的两种shader
  • xfermode mode:组合两种shader颜色的模式
  • porterDuff.Mode mode:组合两种shader颜色的模式

使用:

		//5.组合渲染
        mPaint.setShader(new ComposeShader(new BitmapShader(mBitmap,Shader.TileMode.REPEAT,Shader.TileMode.REPEAT),
                new LinearGradient(0,0,1000,1600,new int[]{Color.RED,Color.GREEN,Color.BLUE},null,Shader.TileMode.CLAMP),
                PorterDuff.Mode.MULTIPLY ));
        canvas.drawCircle(250,250,250,mPaint);

效果:

绘制区域远大于位图,重复排版,同时线性渲染也加持了。
PorterDuff.Mode.MULTIPLY:两个渲染器进行渲染的图层混合规则

四、PorterDuff.Mode图层混合模式

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

一共有18种模式:

  • CLEAR
  • SRC
  • DST
  • SRC_OVER
  • DST_OVER
  • SRC_IN
  • DST_IN
  • SRC_OUT
  • DST_OUT
  • SRC_ATOP
  • DST_ATOP
  • XOR
  • DARKEN
  • LIGHTEN
  • MULTIPLY
  • SCREEN
  • ADD
  • OVERLAY

每一种模式都代表一个规则,图层混合后的效果,是通过规则计算alpha通道值和颜色值C。src:原图像;dst:目标图像

这里可以参考google的官方demo!

4.1 离屏绘制

画笔Paint.setXfermode(),首先要禁止硬件加速(因为图层混合有些api是不支持硬件加速的,但是系统默认开启)

关于离屏绘制,首先开启离屏绘制:

	//禁止硬件加速,图层混合有些api不支持,但是系统默认开启
        setLayerType(View.LAYER_TYPE_SOFTWARE,null);

        setBackgroundColor(Color.GRAY);//给自定义view加一个背景
        //离屏绘制
        int layerId=canvas.saveLayer(0,0,getWidth(),getHeight(),mPaint);

        //目标图
        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(layerId);

在这里插入图片描述然后关闭离屏绘制:

        //禁止硬件加速,图层混合有些api不支持,但是系统默认开启
        setLayerType(View.LAYER_TYPE_SOFTWARE,null);

        setBackgroundColor(Color.GRAY);//给自定义view加一个背景
        //离屏绘制
//        int layerId=canvas.saveLayer(0,0,getWidth(),getHeight(),mPaint);

        //目标图
        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(layerId);

在这里插入图片描述发现灰色背景不见了,它直接参与了图层混合的计算。不使用离屏绘制,直接考虑canvas的绘制,导致计算结果不正确。

使用离屏绘制(或者叫离屏缓冲),先创建一个图层,将两个图层混合后的结果绘制到画布上。 通过使用离屏绘制,把要绘制的内容单独绘制在缓冲层,保证Xfermode的使用不会出现任何错误。

4.2 使用离屏绘制

4.2.1 Canvas.saveLayer()

Canvas.saveLayer()可以做短时的离屏缓冲,在绘制之前保存,绘制之后恢复

int layerId=canvas.saveLayer(0,0,getWidth(),getHeight(),mPaint);
//画方
canvas.drawBitmap(createRectBitmap(mWidth, mHeight), 0, 0, mPaint);
//设置Xfermode
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
//画圆
canvas.drawBitmap(createCircleBitmap(mWidth, mHeight), 0, 0, mPaint);
//用完及时清除Xfermode
mPaint.setXfermode(null);
//进行图层恢复
canvas.restoreToCount(layerId);

4.2.2 View.setLayerType()

View.setLayerType()直接把整个View都绘制在离屏缓冲中

//使用GPU来缓冲
setLayerType(LAYER_TYPE_HARDWARE);
//使用一个Bitmap来缓冲
setLayerType(LAYER_TYPE_SOFTWARE);

图层混合模式只作用于src源图像区域。每一种模式都有对应的公式来计算各自的alpha通道值、颜色值。以DARKEN为例:

//alpha通道值:源图像的alpha通道值+目标图像的alpha通道值-源图像的alpha通道值*目标图像的alpha通道值
<p>\(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\)</p>
//颜色的计算方式:(1-目标图像的alpha通道值)* 源图像的alpha颜色值 + (1-源图像的alpha通道值)* 目标图像的颜色值 + min(源图像的颜色值,目标图像的颜色值)
<p>\(C_{out} = (1 - \alpha_{dst}) * C_{src} + (1 - \alpha_{src}) * C_{dst} + min(C_{src}, C_{dst})\)</p>

4.3 案例-幸运刮刮卡

这里用到了触摸,在onTouchEvent方法中处理,绘制贝塞尔曲线,最后调用invalidate()方法,绘是的onDraw方法被调用。

onDraw方法中首先绘制结果,然后进行离屏绘制,将我们手势的二阶 贝塞尔曲线绘制到bitmap上,然后再绘制目标图像。之后设置图层混合模式为SRC_OUT(清除相交的区域),最后绘制源图像、清除图层混合模式、图层恢复。

package com.example.admin.uimaster.xfermode;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

import com.example.admin.uimaster.R;


public class XfermodeEraserView extends View {
    private Paint  mPaint;
    private Bitmap mDstBmp, mSrcBmp, mTxtBmp;
    private Path mPath;

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

    public XfermodeEraserView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public XfermodeEraserView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

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

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

        //初始化图片对象
        mTxtBmp = BitmapFactory.decodeResource(getResources(), R.drawable.result);
        mSrcBmp = BitmapFactory.decodeResource(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(0, 0, getWidth(), getHeight(), mPaint, Canvas.ALL_SAVE_FLAG);

        //先将路径绘制到 bitmap上
        Canvas dstCanvas = new Canvas(mDstBmp);
        dstCanvas.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);
    }

    private float mEventX, mEventY;

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mEventX = event.getX();
                mEventY = event.getY();
                mPath.moveTo(mEventX, mEventY);
                break;
            case MotionEvent.ACTION_MOVE:
                float endX = (event.getX() - mEventX) / 2 + mEventX;
                float endY = (event.getY() - mEventY) / 2 + mEventY;
                //画二阶贝塞尔曲线
                mPath.quadTo(mEventX, mEventY, endX, endY);
                mEventX = event.getX();
                mEventY = event.getY();
                break;
        }
        invalidate();
        return true; //消费事件
    }
}
发布了197 篇原创文章 · 获赞 245 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/qq_36299025/article/details/103254516
今日推荐