Android VIew绘图机制

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

本文大纲如下:

大纲
  • 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 线头形状方头
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完全相反
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与透明度有关


  • 源模式: 前缀为Mode.SRC 显示后加颜色,除了Mode.SRC_OUT透明外,其他显示RED


  • 目标模式:前缀为Mode.DST,除了Mode.DST_OUT透明外,其他显示目标图


  • 其他为叠加模式


代码如下:

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图
PorterDuff.Mode

对应的相应算法如下:

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


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时候,才引用了该方法

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345

推荐阅读更多精彩内容

  • 【Android 自定义View之绘图】 基础图形的绘制 一、Paint与Canvas 绘图需要两个工具,笔和纸。...
    Rtia阅读 11,643评论 5 34
  • 版权声明:本文为博主原创文章,未经博主允许不得转载 前言 Canvas 本意是画布的意思,然而将它理解为绘制工具一...
    cc荣宣阅读 41,531评论 1 47
  • 提及中国的文人才子,大多数人的印象都是:手无缚鸡之力。毕竟在人们的传统意识里,文武分开,文臣和武将的形象截然不同。...
    奇艺姑娘阅读 1,294评论 16 16
  • 纠结了四个小时,终于看完了。明明是喜剧,却让自己哭了稀里哗啦的。可是真的很感人,真心推荐这部影片。 《小萝莉的猴神...
    高藝菲Sophia阅读 155评论 0 0
  • 每到周一都有一种不适应,早上起来就特别的困。原本从家里出发的时间可能是差不多八点钟,但是为了找到公司提前准备,我还...
    大熊_3a66阅读 199评论 0 0