Android VIew绘图机制

本篇博客是我对View的绘图方面的理解和归纳,也不会说的非常的细,只是类似于一个思维导图的作用,因为有人写得更好了,我就没必要写了,况且写了还不一定如别人的。

本文大纲如下:

Android VIew绘图机制_第1张图片
大纲
  • View形状绘制
  • View颜色绘制
  • View几何变换和裁剪
  • View的绘制顺序

一、绘制形状

1)、Paint类的基本用法

  • paint.setAntiAlias(true); //抗锯齿功能,一般都会加入的
  • paint.setColor( ); // 设置画笔颜色
  • paint.setARGB(int a, int r, int g, int b) // 设置三原色和透明通路
  • paint.setAlpha(int a) // 设置透明通道
  • paint.setStrokeWidth(int a);//设置画笔宽度
  • paint.setStyle(Style.FILL);//设置填充样式
  • paint.setTextAlign(Align.CENTER); //设置文字对齐方式,取值:Align.CENTER、Align.LEFT或Align.RIGHT
  • paint.setTextSize(12); //设置文字大小
  • paint.setUnderlineText(true); //设置下划线
  • paint.setStrikeThruText(true); //设置带有删除线效果
  • paint.setTextScaleX(2); //只会将水平方向拉伸,高度不会变
Paint.Style 属性 描述
Paint.Style.FILL 填充内部
Paint.Style.FILL_AND_STROKE 填充内部和描边
Paint.Style.STROKE 仅描边
Paint.Cap 属性 描述
Paint.Cap.BUTT 线头形状平头
Paint.Cap.ROUND 线头形状圆头
Paint.Cap.SQUARE 线头形状方头
Android VIew绘图机制_第2张图片
Cap形状
Paint.Align属性 描述
Paint.Align.LEFT 内容居左显示
Paint.Align.CENTER 内容居中显示
Paint.Align.RIGHT 内容居右显示
  • paint.setShadowLayer (float radius, float dx, float dy, int color) // 添加阴影,倾斜度,x轴偏离值,y轴偏离值,阴影颜色。如TextView等也可以在xml布局中属性有android:shadowDx等等,原理一致
  • paint.drawPath(@NonNull Path path, @NonNull Paint paint) //画条路径

2)、Canvas 绘制

①、通用函数
  • canvas.drawColor(Color.BLUE); // 设置画布颜色,即背景
  • canvas.drawRGB(255, 255, 0); // 设置画布颜色,即背景
  • canvas.drawLine (float startX, float startY, float stopX, float stopY, Paint paint) // 画一条直线
  • void drawLines (float[] pts, Paint paint) // 多条直线
  • void drawPoint (float x, float y, Paint paint) // 画条点
  • void drawPoints (float[] pts, Paint paint)
  • void drawRect (float left, float top, float right, float bottom, Paint paint) // 画矩形
  • void drawRect (RectF rect, Paint paint)
  • void drawRect (Rect r, Paint paint)
  • void drawRoundRect (RectF rect, float rx, float ry, Paint paint) // 圆角矩形
  • void drawCircle (float cx, float cy, float radius, Paint paint) // 圆形
  • void drawOval (RectF oval, Paint paint) // 椭圆
  • void drawArc (RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint) // 画条弧线
    注意:useCenter和Paint.Style搭配使用可以达到不同的效果。如扇形和弧线等。
②、文字相关
  • void drawText (String text, float x, float y, Paint paint)
  • void drawText (CharSequence text, int start, int end, float x, float y, Paint paint) // 画一个Text出来,start和end代表画那些字符,x代表x轴位置,y代表基线。
    基线算法:
       Paint.FontMetricsInt fm = paint.getFontMetricsInt();
       int dy = (fm.top + fm.bottom)/2;
       int baseLine = getHeight()/2 - dy;`
  • void drawText (String text, int start, int end, float x, float y, Paint paint)
  • void drawText (char[] text, int index, int count, float x, float y, Paint paint)
  • void drawPosText (String text, float[] pos, Paint paint) // 指定每个文字的位置
  • void drawTextOnPath (String text, Path path, float hOffset, float vOffset, Paint paint) // 沿着一个路径绘制

3)、Path基本用法

Path用法 描述
path.moveTo(10, 10); 设定起始点
path.lineTo(10, 100) 连接到下一个点
path.rMoveTo(10, 10); 以在上一个结束点为基准点,x轴偏移10像素,y轴偏移10像素
path.rLineTo(10, 100) 连接到下一个点,下个点是以在上一个结束点为基准点,x轴偏移10像素,y轴偏移10像素
path.close(); 形成一个闭环
path.addRect(RectF rect, Direction dir) 增加一个矩形路径,Direction枚举代表生成的方向
void addCircle (float x, float y, float radius, Path.Direction dir) 圆形路径
void addOval (RectF oval, Path.Direction dir) 椭圆路径
void addArc (RectF oval, float startAngle, float sweepAngle) 弧形路径
void quadTo 二阶贝塞尔曲线
void cubicTo 三阶贝塞尔曲线
Path.Direction属性 描述
CW 顺时针开始绘制
CCW 逆时针开始绘制
只有在两个图形相交的时候,才会用到FillType属性,用于判断相交的部分是否需要显示
Path.FillType属性 描述
WINDING 非零环绕数原则,默认为WINDING
EVEN_ODD 交叉填充,两图形奇偶原则。一点重合了偶数次就不画
INVERSE_WINDING 与WINDING完全相反
INVERSE_EVEN_ODD 与EVEN_ODD完全相反
Android VIew绘图机制_第3张图片
WINDING

对于上图,WINDING规则下,画的方向会直接影响多个图形的重合区域,对于一点顺时针则+1,逆时针-1,如果相加等于0则不画。

INVERSE_WINDING和WINDING画的是完全相反的。INVERSE_EVEN_ODD和EVEN_ODD是完全相反的。

二、绘制颜色

1)、基本颜色

  • canvas.drawColor(Color.BLUE); // 设置画布颜色,即背景
  • canvas.drawRGB(255, 255, 0); // 设置画布颜色,即背景
  • paint.setColor( ); // 设置画笔颜色
  • paint.setARGB(int a, int r, int g, int b) // 设置三原色和透明通路
  • setShader(Shader shader) 设置 Shader

Shader称之着色器和渲染器,可以实现一些渐变和渲染效果。它的子类包括:

  • BitmapShader : 位图Shader
  • LinearShader : 线性Shader
  • RadialShader : 光束Shader
  • SweepShader : 梯度Shader
  • ComposeShader : 混合Shader
BitmapShader

构造函数:
public BitmapShader(@NonNull Bitmap bitmap, TileMode tileX, TileMode tileY) ;
其中TileMode 是图像的填充模式

  • CLAMP : 拉伸模式,图片最后一个像素的颜色铺满剩余的X或者Y轴
  • REPETA : 重复模式
  • MIRROR : 镜像模式

看一个关于图像中间去一个圆形图像的例子

public class TelescopeView extends View {
    private Paint mPaint;
    private Bitmap mBitmap,mBitmapBG;
    private int mDx = -1, mDy = -1;
    public TelescopeView(Context context) {
        super(context);
        init();
    }

    public TelescopeView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public TelescopeView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    private void init() {
        mPaint = new Paint();
        mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.game);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mDx = (int) event.getX();
                mDy = (int) event.getY();
                postInvalidate();
                return true;
            case MotionEvent.ACTION_MOVE:
                mDx = (int) event.getX();
                mDy = (int) event.getY();
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                mDx = -1;
                mDy = -1;
                break;
        }

        postInvalidate();
        return super.onTouchEvent(event);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mBitmapBG == null){
            mBitmapBG = Bitmap.createBitmap(getWidth(),getHeight(), Bitmap.Config.ARGB_8888);
            Canvas canvasbg = new Canvas(mBitmapBG);
            canvasbg.drawBitmap(mBitmap,null,new Rect(0,0,getWidth(),getHeight()),mPaint);
            mPaint.setShader(new BitmapShader(mBitmapBG, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT));
        }

        if (mDx != -1 && mDy != -1) {
            canvas.drawCircle(mDx, mDy, 150, mPaint);
        }
    }
}

2)、使用ColorFilter

ColorFilter 是一个颜色过滤的基类,它的子类分别是

  • ColorMatrixColorFilter : 色彩矩阵过滤
  • LightingColorFilter : ColorMatrixColorFilter的简化版
  • PorterDuffColorFilter : 用单一颜色和特定的复合模式对源像素进行着色
①、ColorMatrixColorFilter

在此之前先说明ColorMatrix这个类。主要属性是一个大小为20的float数组,即45的举证,这是个45阶的矩阵,RGBA和一个哑元。哑元是一个附加值。增加一些分量。

如:
[ a, b, c, d, e,
  f, g, h, i, j,
  k, l, m, n, o,
  p, q, r, s, t ]

上面矩阵和RGBA这个矩阵相乘就会得到一个运算后的新的RGBA
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;

基本使用如下,对透明通道减半操作:

// 生成色彩矩阵
ColorMatrix colorMatrix = new ColorMatrix(new float[]{
        1, 0, 0, 0, 0,
        0, 1, 0, 0, 0,
        0, 0, 1, 0, 0,
        0, 0, 0, 0.5, 0,
});
mPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));

通过ColorMatrix矩阵的值的不同,得到的新的颜色也是不一致的。

1、过滤Blue通道的颜色
colorMatrix = new ColorMatrix(new float[]{
                0, 0, 0, 0, 0,
                0, 0, 0, 0, 0,
                0, 0, 1, 0, 0,
                0, 0, 0, 1, 0,
        })

2、颜色取反
 colorMatrix = new ColorMatrix(new float[]{
                -1,0,0,0,255,
                0,-1,0,0,255,
                0,0,-1,0,255,
                0,0,0,1,0
        });

3、色彩的缩放运算
colorMatrix = new ColorMatrix(new float[]{
                1.2f, 0, 0, 0, 0,
                0, 1.2f, 0, 0, 50,
                0, 0, 1.2f, 0, 0,
                0, 0, 0, 1.2f, 0,
        });

4、色彩反色 (红绿反色)
        colorMatrix = new ColorMatrix(new float[]{
                0,1,0,0,0,
                1,0,0,0,0,
                0,0,1,0,0,
                0,0,0,1,0
        });

ColorMatrix类也给出了一些基本的运算:

  • colorMatrix.setRotate(); : 设置旋转
  • colorMatrix.setSaturation(); : 设置饱和度,同时增加RGB值
  • colorMatrix.setScale(); : 色彩缩放

上面只给出了一些特定的矩阵效果,这个要求数学功底还可以,所以才有了LightingColorFilter

②、LightingColorFilter

唯一的构造方法 : LightingColorFilter(int mul, int add)// mul是用来相乘的,add运来相加
运算结果:

R = (r*mul.R+add.R)%255;
G = (g*mul.G+add.G)%255;
B = (b*mul.B+add.B)%255;

用法比较简单: 
mPaint.setColorFilter(new LightingColorFilter(0x00ff00, 0x000000));
③、PorterDuffColorFilter

这是个比较强大的类。和PhotoShop的颜色叠加理念是一致的。
构造方法:
public PorterDuffColorFilter(@ColorInt int color, @NonNull PorterDuff.Mode mode)
一个附加颜色和一个模式。
PorterDuff.Mode 模式种类

     Mode.SRC
     Mode.DST
     Mode.SRC_OVER
     Mode.DST_OVER
     Mode.SRC_IN
     Mode.DST_IN
     Mode.SRC_OUT  透明
     Mode.DST_OUT
     Mode.SRC_ATOP
     Mode.DST_ATOP

     Mode.CLEAR
     Mode.XOR

*    Mode.DARKEN     变暗
*    Mode.LIGHTEN    变亮
*    Mode.MULTIPLY   正片叠底
*    Mode.SCREEN     滤色
*    Mode.OVERLAY    叠加
*    Mode.ADD        饱和度相加

大体分为下面几种模式,源颜色为Color.RED举例

  • 清除模式 : Mode.CLEAR 和 Mode.XOR,什么都不会显示,Mode.XOR与透明度有关


    Android VIew绘图机制_第4张图片
  • 源模式: 前缀为Mode.SRC 显示后加颜色,除了Mode.SRC_OUT透明外,其他显示RED


    Android VIew绘图机制_第5张图片
  • 目标模式:前缀为Mode.DST,除了Mode.DST_OUT透明外,其他显示目标图


    Android VIew绘图机制_第6张图片
  • 其他为叠加模式


    Android VIew绘图机制_第7张图片

代码如下:

public class SelfViewTwo extends View {

    private Paint mPaint;
    private Bitmap mBmp;

    public SelfViewTwo(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mPaint = new Paint();

        mBmp = BitmapFactory.decodeResource(getResources(), R.drawable.game);
    }

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

        drawPorterDuffFilter5(canvas);
    }

    private void drawPorterDuffFilter(Canvas canvas){
        int width  = 300;
        int height = width * mBmp.getHeight()/mBmp.getWidth();

        mPaint.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.ADD));//饱和度相加
        canvas.drawBitmap(mBmp,null,new Rect(0,0,width,height),mPaint);


        canvas.translate(350,0);
        mPaint.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.DARKEN));//变暗
        canvas.drawBitmap(mBmp,null,new Rect(0,0,width,height),mPaint);

        canvas.translate(-350,350);
        mPaint.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.LIGHTEN));//变亮
        canvas.drawBitmap(mBmp,null,new Rect(0,0,width,height),mPaint);

        canvas.translate(350,0);
        mPaint.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.MULTIPLY));//正片叠底
        canvas.drawBitmap(mBmp,null,new Rect(0,0,width,height),mPaint);

        canvas.translate(-350,350);
        mPaint.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.OVERLAY));//叠加
        canvas.drawBitmap(mBmp,null,new Rect(0,0,width,height),mPaint);

        canvas.translate(350,0);
        mPaint.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SCREEN));//滤色
        canvas.drawBitmap(mBmp,null,new Rect(0,0,width,height),mPaint);
    }

    /**
     * 清空模式
     * PorterDuff.Mode.CLEAR 和 PorterDuff.Mode.XOR 不会显示的
     */
    private void drawPorterDuffFilter2(Canvas canvas){
        int width  = 500;
        int height = width * mBmp.getHeight()/mBmp.getWidth();

        canvas.drawBitmap(mBmp,null,new Rect(0,0,width,height),mPaint);

        canvas.translate(550,0);
        mPaint.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.CLEAR));
        canvas.drawBitmap(mBmp,null,new Rect(0,0,width,height),mPaint);

        canvas.translate(-550,550);
        mPaint.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.XOR));
        canvas.drawBitmap(mBmp,null,new Rect(0,0,width,height),mPaint);
    }


    /**
     * 目标图像模式
     * Mode.DST、Mode.DST_IN、Mode.DST_OUT、Mode.DST_OVER、Mode.DST_ATOP
     * 除了Mode.DST_OUT显示完全透明图片以外,其它全部显示目标图像;
     */
    private void drawPorterDuffFilter3(Canvas canvas){
        int width  = 500;
        int height = width * mBmp.getHeight()/mBmp.getWidth();

        canvas.drawBitmap(mBmp,null,new Rect(0,0,width,height),mPaint);

        canvas.translate(550,0);
        mPaint.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.DST));
        canvas.drawBitmap(mBmp,null,new Rect(0,0,width,height),mPaint);

        canvas.translate(-550,550);
        mPaint.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.DST_IN));
        canvas.drawBitmap(mBmp,null,new Rect(0,0,width,height),mPaint);

        canvas.translate(550,0);
        mPaint.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.DST_OUT));
        canvas.drawBitmap(mBmp,null,new Rect(0,0,width,height),mPaint);

        canvas.translate(-550,550);
        mPaint.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.DST_OVER));
        canvas.drawBitmap(mBmp,null,new Rect(0,0,width,height),mPaint);

        canvas.translate(550,0);
        mPaint.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.DST_ATOP));
        canvas.drawBitmap(mBmp,null,new Rect(0,0,width,height),mPaint);
    }

    /**
     * 源图模式
     * Mode.SRC、Mode.SRC_IN、Mode.SRC_OUT、Mode.SRC_OVER、Mode.SRC_ATOP
     * 除了Mode.SRC_OUT显示完全透明图片以外,其它全部显示源图像;
     */
    private void drawPorterDuffFilter4(Canvas canvas){
        int width  = 500;
        int height = width * mBmp.getHeight()/mBmp.getWidth();

        canvas.drawBitmap(mBmp,null,new Rect(0,0,width,height),mPaint);

        canvas.translate(550,0);
        mPaint.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC));
        canvas.drawBitmap(mBmp,null,new Rect(0,0,width,height),mPaint);

        canvas.translate(-550,550);
        mPaint.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_IN));
        canvas.drawBitmap(mBmp,null,new Rect(0,0,width,height),mPaint);

        canvas.translate(550,0);
        mPaint.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_OUT));
        canvas.drawBitmap(mBmp,null,new Rect(0,0,width,height),mPaint);

        canvas.translate(-550,550);
        mPaint.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_OVER));
        canvas.drawBitmap(mBmp,null,new Rect(0,0,width,height),mPaint);

        canvas.translate(550,0);
        mPaint.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_ATOP));
        canvas.drawBitmap(mBmp,null,new Rect(0,0,width,height),mPaint);
    }


    /**
     * SRC相关的模式,只有Mode.SRC_ATOP和SRC_IN能够实现SetTint的功能
     */
    private void drawPorterDuffFilter5(Canvas canvas){
        int width  = 100;
        int height = width * mBmp.getHeight()/mBmp.getWidth();

        canvas.drawBitmap(mBmp,null,new Rect(0,0,width,height),mPaint);

        canvas.translate(150,0);
        mPaint.setColorFilter(new PorterDuffColorFilter(0xffff00ff, PorterDuff.Mode.SRC));
        canvas.drawBitmap(mBmp,null,new Rect(0,0,width,height),mPaint);

        canvas.translate(150,0);
        mPaint.setColorFilter(new PorterDuffColorFilter(0xff00f0ff, PorterDuff.Mode.SRC_ATOP));
        canvas.drawBitmap(mBmp,null,new Rect(0,0,width,height),mPaint);

        canvas.translate(150,0);
        mPaint.setColorFilter(new PorterDuffColorFilter(0xfff0f0ff, PorterDuff.Mode.SRC_IN));
        canvas.drawBitmap(mBmp,null,new Rect(0,0,width,height),mPaint);

        canvas.translate(150,0);
        mPaint.setColorFilter(new PorterDuffColorFilter(0xffffff00, PorterDuff.Mode.SRC_OVER));
        canvas.drawBitmap(mBmp,null,new Rect(0,0,width,height),mPaint);


        canvas.translate(150,0);
        mPaint.setColorFilter(new PorterDuffColorFilter(0xff000000, PorterDuff.Mode.SRC_ATOP));
        canvas.drawBitmap(mBmp,null,new Rect(0,0,width,height),mPaint);
    }
}

总的来说,PorterDuffColorFilter是很强大的,前提是弄清楚PorterDuff.Mode的模式特点,就是掌握了精髓。上面实例并没有涉及到透明度的问题,所以还算简单。不足的是叠加的只能是颜色不能是其他的如bitmap等。有关PorterDuff.Mode的模式算法,后面会细说。

3)、paint设置setXfermode

Xfermode 的唯一子类就是PorterDuffXfermode,使用Xfermode时需要增加离屏

给出一张官方的PorterDuff.Mode图
Android VIew绘图机制_第8张图片
PorterDuff.Mode

对应的相应算法如下:

其中
Sa : 源透明度
Da :目标透明度
Sc : 源Color
Dc : 目标Color


Android VIew绘图机制_第9张图片
PorterDuff.Mode公式算法

使用:准备叠加之前paint先增加一个mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN))即可。

        canvas.drawBitmap(dstBmp, 0, 0, mPaint);
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        canvas.drawBitmap(srcBmp, 0, 0, mPaint);
        mPaint.setXfermode(null);

如PorterDuff.Mode.SRC_OUT实现刮刮卡的功能

public class GuaGuaCardView_SRCOUT extends View{
    private Paint mBitPaint;
    private Bitmap BmpDST,BmpSRC,BmpText;
    private Path mPath;
    private float mPreX,mPreY;
    public GuaGuaCardView_SRCOUT(Context context, AttributeSet attrs) {
        super(context, attrs);
 
        setLayerType(View.LAYER_TYPE_SOFTWARE, null);
        mBitPaint = new Paint();
        mBitPaint.setColor(Color.RED);
        mBitPaint.setStyle(Paint.Style.STROKE);
        mBitPaint.setStrokeWidth(45);
 
        BmpText = BitmapFactory.decodeResource(getResources(),R.drawable.guaguaka_text,null);
        BmpSRC = BitmapFactory.decodeResource(getResources(),R.drawable.guaguaka_pic,null);
        BmpDST = Bitmap.createBitmap(BmpSRC.getWidth(), BmpSRC.getHeight(), Bitmap.Config.ARGB_8888);
        mPath = new Path();
    }
 
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
 
        canvas.drawBitmap(BmpText,0,0,mBitPaint);
 
        int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
 
        //先把手指轨迹画到目标Bitmap上
        Canvas c = new Canvas(BmpDST);
        c.drawPath(mPath,mBitPaint);
 
        //然后把目标图像画到画布上
        canvas.drawBitmap(BmpDST,0,0,mBitPaint);
 
        //计算源图像区域
        mBitPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT));
        canvas.drawBitmap(BmpSRC,0,0,mBitPaint);
 
        mBitPaint.setXfermode(null);
        canvas.restoreToCount(layerId);
    }
 
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                mPath.moveTo(event.getX(),event.getY());
                mPreX = event.getX();
                mPreY = event.getY();
                return true;
            case MotionEvent.ACTION_MOVE:
                float endX = (mPreX+event.getX())/2;
                float endY = (mPreY+event.getY())/2;
                mPath.quadTo(mPreX,mPreY,endX,endY);
                mPreX = event.getX();
                mPreY =event.getY();
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        postInvalidate();
        return super.onTouchEvent(event);
    }
}

  • 1、首先,目标图像和源图像混合,需不需要生成颜色的叠加特效,如果需要叠加特效则从颜色叠加相关模式中选择,有Mode.ADD(饱和度相加)、Mode.DARKEN(变暗),Mode.LIGHTEN(变亮)、Mode.MULTIPLY(正片叠底)、Mode.OVERLAY(叠加),Mode.SCREEN(滤色)
  • 2、当不需要特效,而需要根据某一张图像的透明像素来裁剪时,就需要使用SRC相关模式或DST相关模式了。由于SRC相关模式与DST相关模式是相通的,唯一不同的是决定当前哪个是目标图像和源图像;
  • 3、当需要清空图像时,使用Mode.CLEAR

小结:

一共有三处地方使用PorterDuff.Mode

PorterDuff.Mode 描述
ComposeShader 两个Shader组合方式,使用时不能使用硬件加速
PorterDuffColorFilter 增加一个单色的ColorFilter
Xfermode 两个绘制内容的叠加

三、canvas变换函数和裁剪

1)、几何变换

  • 平移(translate)
// 画笔
        Paint paint = new Paint();
        paint.setTextSize(50);
        paint.setColor(Color.RED);
        String str = "Android View";
// 基线
        Paint.FontMetricsInt fm = paint.getFontMetricsInt();
        int dy = (fm.top + fm.bottom)/2;
        int baseLine = getHeight()/2 - dy;
// 构造一个背景bitmap
        Bitmap bitmap = Bitmap.createBitmap(getWidth(),getHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas1 = new Canvas(bitmap);
        canvas1.translate(100,100);
        canvas1.drawText(str,0,str.length(),0,baseLine,paint);
        canvas.drawBitmap(bitmap,null,new Rect(0,0,getWidth(),getHeight()),paint);

  • 旋转(Rotate)
    void rotate(float degrees)
    void rotate (float degrees, float px, float py) // 以某一个point旋转

  • 缩放(scale )
    public void scale (float sx, float sy) // x和y轴的缩放比例

  • 扭曲(skew)
    void skew (float sx, float sy)

2)、canvas 裁剪

// 一些函数,裁剪出一个区域
boolean clipPath(Path path)
boolean clipPath(Path path, Region.Op op)
boolean clipRect(Rect rect, Region.Op op)
boolean clipRect(RectF rect, Region.Op op)
boolean clipRect(int left, int top, int right, int bottom)
boolean clipRect(float left, float top, float right, float bottom)
boolean clipRect(RectF rect)
boolean clipRect(float left, float top, float right, float bottom, Region.Op op)
boolean clipRect(Rect rect)
boolean clipRegion(Region region)
boolean clipRegion(Region region, Region.Op op)

下面通过clip裁剪一块区域显示,来实现一段文字,不同颜色的功能

public class Test extends View {

    public Test(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }


    protected void onDraw(Canvas canvas) {

        // 画笔
        Paint paint = new Paint();
        paint.setTextSize(50);
        paint.setColor(Color.RED);

        String str = "Android View Administrator";

        // 基线
        Paint.FontMetricsInt fm = paint.getFontMetricsInt();
        int dy = (fm.top + fm.bottom)/2;
        int baseLine = getHeight()/2 - dy;

        // 画一个宽度为0~270px这块区域
        canvas.save();
        canvas.clipRect(0,0,270,getHeight());
        canvas.drawText(str,0,str.length(),0,baseLine,paint);
        canvas.restore();

        // 画一个宽度为270px~getWidth()这块区域
        canvas.save();
        paint.setColor(Color.GREEN);
        canvas.clipRect(270,0,getWidth(),getHeight());
        canvas.drawText(str,0,str.length(),0,baseLine,paint);
        canvas.restore();

    }


}
结果

3)、canvas 保存与恢复

  • int save () // 会把当前的画布的状态进行保存,然后放入特定的栈中;
  • void restore() //会把栈中最顶层的画布状态取出来,并按照这个状态恢复当前的画布,并在这个画布上做画。

上个例子中也使用了canvas的保存和恢复。不进行保存和恢复,就会在上一个画布的基础上进行绘画,后面的文字就不会被显示出来。

4)、Matrix变换

步骤:

1、创建 Matrix 对象;
2、调用 Matrix 的 pre/postTranslate/Rotate/Scale/Skew() 方法来设置几何变换;
3、使用 Canvas.setMatrix(matrix) 或 Canvas.concat(matrix) 来把几何变换应用到 Canvas。

Canvas.setMatrix(matrix)是用新的matrix替代之前的matrix。Canvas.concat(matrix)是用新的和旧的matrix矩阵相乘。

5)、Camera三维变换

①、Camera.rotate*() 三维旋转
  • rotateX(deg) 沿X轴旋转deg角度
  • rotateY(deg) 沿Y轴旋转deg角度
  • rotateZ(deg) 沿Z轴旋转deg角度
  • rotate(x, y, z) 沿X/Y/Z轴旋转相应的角度
rotateX(deg)举例:

执行camera.rotateX(30)时,需要设置一个旋转轴心,否则会出现不对称问题;而且canvas的几何变换顺序是相反的,即后面的先执行。所以先写canvas.translate(centerX, centerY);

canvas.save();
camera.save(); // 保存 Camera 的状态  
camera.rotateX(30); // 旋转 Camera 的三维空间  
canvas.translate(centerX, centerY); // 旋转之后把投影移动回来  
camera.applyToCanvas(canvas); // 把旋转投影到 Canvas  
canvas.translate(-centerX, -centerY); // 旋转之前把绘制内容移动到轴心(原点)  
camera.restore(); // 恢复 Camera 的状态
canvas.drawBitmap(bitmap, point1.x, point1.y, paint);  
canvas.restore(); 

四、View绘制顺序

android是顺序的绘制View的,这会导致后面绘制的View会影响前面的View。即绘制顺序的不同,会有不同的界面效果。

从源码draw()里面看绘制流程:

  • drawBackground() 绘制背景,该方法不可重写
  • onDraw() 绘制主体内容
  • dispatchDraw() 绘制子View,需要调用setWillNotDraw(false)
  • onDrawFroeground() 绘制前景 API 23时候,才引用了该方法
Android VIew绘图机制_第10张图片

1、由于ViewGroup默认不会执行onDraw方法,需要调用View.setWillNotDraw(false)来强制执行onDraw
2、在尽可能的情况下,将代码放在onDraw()里面写,View的机制中会在不需要重新绘制的时候,跳过onDraw方法,从而提升效率。

你可能感兴趣的:(Android VIew绘图机制)