为了账号安全,请及时绑定邮箱和手机立即绑定

一起来演示Android画布

标签:
Android

Canvas的常用操作速查表

操作类型相关API备注
绘制颜色drawColor, drawRGB, drawARGB使用单一颜色填充整个画布
绘制基本形状drawPoint, drawPoints, drawLine, drawLines, drawRect, drawRoundRect, drawOval, drawCircle, drawArc依次为 点、线、矩形、圆角矩形、椭圆、圆、圆弧
绘制图片drawBitmap, drawPicture绘制位图和图片
绘制文本drawText, drawPosText, drawTextOnPath依次为 绘制文字、绘制文字时指定每个文字位置、根据路径绘制文字
绘制路径drawPath绘制路径,绘制贝塞尔曲线时也需要用到该函数
顶点操作drawVertices, drawBitmapMesh通过对顶点操作可以使图像形变,drawVertices直接对画布作用、 drawBitmapMesh只对绘制的Bitmap作用
画布剪裁clipPath, clipRect设置画布的显示区域
画布快照save, restore, saveLayerXxx, restoreToCount, getSaveCount依次为 保存当前状态、 回滚到上一次保存的状态、 保存图层状态、 回滚到指定状态、 获取保存次数
画布变换translate, scale, rotate, skew依次为 位移、缩放、 旋转、倾斜
Matrix(矩阵)getMatrix, setMatrix, concat实际画布的位移,缩放等操作的都是图像矩阵Matrix,只不过Matrix比较难以理解和使用,故封装了一些常用的方法。

首先随意自定义一个View,重写onDraw(Canvas canvas)方法,在onDraw方法里面演示画布:

public class CustomView extends View {    private Paint mPaint;    public CustomView(Context context) {    super(context);
    }    public CustomView(Context context, @Nullable AttributeSet attrs) {    super(context, attrs);
    }    public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {    super(context, attrs, defStyleAttr);
    }    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)    public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {    super(context, attrs, defStyleAttr, defStyleRes);
    }    @Override
    protected void onDraw(Canvas canvas) {    super.onDraw(canvas);

    }
}

开始初始化画笔:

@Overrideprotected void onDraw(Canvas canvas) {    super.onDraw(canvas);    //初始化画笔
    initPaint();

}/**
 * 初始化画笔
 * @return
 */private void initPaint(){    if(mPaint == null){
        mPaint = new Paint();
    }    //设置画笔的颜色
    mPaint.setColor(Color.BLUE);    //设置画笔的大小
    mPaint.setStrokeWidth(10);
}

这里简单设置了画笔,本章目的是演示画布。
接下来拿起你的笔开始画画:

画直线:

画单条直线

//画直线,前四个参数为直线两端的坐标canvas.drawLine(100,200,300,300, mPaint);

效果如下


webp

图片.png


画多条直线(一):

    float[] pts1 = {//声明直线位置
            200,300,400,400,            300,400,500,600
    };
    canvas.drawLines(pts1, mPaint);

效果如下


webp

图片.png


画多条直线(二):

    float[] pts2 = {//数组的长度为12
            200,300,400,400,            500,450,500,600,            700,750,800,850
    };    // 第一个参数传递数组
    // 第二个参数传递偏移量,也就是跳过前多少个数据
    // 第三个参数传递数量,因为每四个数据确定一条直线,所以这里的传值都是4的倍数
    canvas.drawLines(pts2,2,8,mPaint);//从数组的第三个数据开始以后取8个数据,每4个数据画一条直线

效果如下


webp

图片.png

画弧:

drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)

参数详解:
oval:矩形对象
startAngle:开始绘制的角度
sweepAngle:绘制移动的角度
useCenter:是否使用矩形的中心点(true为扇形,false为非扇形)

drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, Paint paint)

参数详解:
left:矩形的左边位置
top:矩形的顶部位置
right:矩形的右边位置
bottom:矩形的下部位置
startAngle:开始绘制的角度
sweepAngle:绘制幅度
useCenter:是否使用矩形的中心点(true为扇形,false为非扇形)

那么弧是怎么绘制的呢?
(1)弧绘制的过程是按照顺时针绘制的;
(2)绘制弧需要一个矩形作为参照物;
(3)弧在矩形中是内切的;
(4)找到矩形的中心点,可以想象从该点向右画一条水平直线,其角度为0,顺时针角度为正数,逆时针角度为负数,从startAngle角度开始顺时针绘制,绘制幅度为sweepAngle。

假设矩形参数为(400, 400, 600, 800),从-30度位置开始绘制,绘制的幅度分别是60度,120度,220度,为了方便演示,我们画一个矩形作为底部。

    RectF rectF = new RectF(400, 400, 600, 800);//声明矩形
    canvas.drawRect(rectF, new Paint());
    canvas.drawArc(rectF, -30, 60, true, mPaint);//60度扇形
    //canvas.drawArc(rectF, -30, 60, false, mPaint);//60度非扇形
    //canvas.drawArc(rectF, -30, 120, true, mPaint);//120度扇形
    //canvas.drawArc(rectF, -30, 120, false, mPaint);//120度非扇形
    //canvas.drawArc(rectF, -30, 220, true, mPaint);//220度扇形
    //canvas.drawArc(rectF, -30, 220, false, mPaint);//220度非扇形

60度效果如下


webp

图片.png

webp

图片.png


120度效果如下


webp

图片.png


webp

图片.png


220度效果如下

webp

图片.png


webp

图片.png

画矩形

方法:

public void drawRect(RectF rect, Paint paint);public void drawRect(Rect r, Paint paint) ;public void drawRect(float left, float top, float right, float bottom, Paint paint) ;

效果如下


webp

图片.png

画圆

方法

drawCircle(float cx, float cy, float radius, Paint paint)

效果如下


webp

图片.png

画椭圆

方法

public void drawOval(RectF oval, Paint paint) public void drawOval(float left, float top, float right, float bottom, Paint paint)

效果如下


webp

图片.png

画矢量图

方法

public void drawPicture(Picture picture)public void drawPicture(Picture picture, RectF dst) public void drawPicture(Picture picture, Rect dst)

使用之前请先关闭硬件加速Android开发如何关闭GPU硬件加速
但是关闭硬件加速会带来一些异常问题,所以到底关闭还是开启硬件加速,是否使用drawPicture看情况而定。

绘制位图有四种方法:
(1)使用Picture提供的draw方法绘制

    if(picture == null){
        picture = new Picture();
    }
    Canvas ca = picture.beginRecording(50, 50);
    ca.drawColor(Color.RED);
    ca.drawText("床前明月光, 地上鞋两双!", 200, 400, mPaint);
    picture.endRecording();
    picture.draw(canvas);

效果如下


webp

图片.png


我们发现即使我这里设置的Picture大小只有50x50,红色背景依然覆盖了整个屏幕, 根据现象可以得知绘制Picture默认是覆盖整个当前画布的。

(2)使用Canvas提供的drawPicture方法绘制,并且没有矩形区域限制。

    Canvas ca = picture.beginRecording(50, 50);
    ca.drawColor(Color.RED);
    ca.drawText("床前明月光, 地上鞋两双!", 200, 400, mPaint);
    picture.endRecording();
    canvas.drawPicture(picture);

效果如下


webp

图片.png

(3)使用Canvas提供的drawPicture方法绘制,并且有矩形区域限制

    if(picture == null){
        picture = new Picture();
    }
    Canvas ca = picture.beginRecording(50, 50);
    ca.drawColor(Color.RED);
    ca.drawCircle(100, 100, 100, mPaint);
    picture.endRecording();
    RectF rectF = new RectF(50, 50, 200, 200);
    canvas.drawPicture(picture, rectF);

效果如下


webp

图片.png


(4)使用PictureDrawable绘制

    if(picture == null){
        picture = new Picture();
    }
    Canvas ca = picture.beginRecording(50, 50);
    ca.drawColor(Color.RED);
    ca.drawCircle(100, 100, 100, mPaint);
    picture.endRecording();
    PictureDrawable drawable = new PictureDrawable(picture);
    drawable.setBounds(0,0,500, 500);
    drawable.draw(canvas);

效果如下


webp

图片.png

这个方法可以任意设置Picture在屏幕中的位置。

画位图

方法

drawBitmap(Bitmap bitmap, float left, float top, Paint paint) 
drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint) 
drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint)
drawBitmap(int[] colors, int offset, int stride, float x, float y, int width, int height, boolean hasAlpha, Paint paint)
drawBitmap(int[] colors, int offset, int stride, int x, int y, int width, int height, boolean hasAlpha, Paint paint) 
drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) 
drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors, int colorOffset, Paint paint)

(1)     根据图片左上角的位置绘画

canvas.drawBitmap(bitmap, 100,100, mPaint);

效果如下


webp

图片.png


(2)取bitmap的某一块填充的制定区域

    //取bitmap某块的区域
    Rect rect = new Rect(0,0,bitmap.getWidth()/2,bitmap.getHeight()/2);    //将要填充的区域
    RectF rectF = new RectF(200,200,800,800);
    canvas.drawBitmap(bitmap, rect,rectF,mPaint);

效果如下


webp

图片.png

(3)已过时的方法

drawBitmap(int[] colors, int offset, int stride, float x, float y, int width, int height, boolean hasAlpha, Paint paint)
drawBitmap(int[] colors, int offset, int stride, int x, int y, int width, int height, boolean hasAlpha, Paint paint)

这两个方法不需要在意。

(4)根据矩阵作用于将要绘制出的图片

   Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
    Matrix matrix = new Matrix();    //旋转45度
    matrix.postRotate(45);    //平移到屏幕的(300,400)位置
    matrix.postTranslate(300, 400);
    canvas.drawBitmap(bitmap, matrix, mPaint);

效果如下


webp

图片.png

(5)扭曲图像的绘制

drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors, int colorOffset, Paint paint);

参数说明:
bitmap    需要扭曲的源位图
meshWidth     控制在横向上把该源位图划成成多少格
meshHeight    控制在纵向上把该源位图划成成多少格
verts        长度为(meshWidth + 1) * (meshHeight + 1) * 2的数组,它记录了扭曲后的位图各顶点位置
vertOffset         控制verts数组中从第几个数组元素开始才对bitmap进行扭曲

效果如下


webp

1535009798103.gif

代码:

public class CustomView extends View {    private Paint mPaint;    private Bitmap bitmap;    private int MESHWIDTH = 5;//图片横向划分成5格
    private int MESHHEIHT = 5;//图片纵向划分成5格
    private int COUNT = (MESHWIDTH + 1) * (MESHHEIHT + 1);//图中有多少个点
    private float[] verts;//所有坐标的数据
    private float[] vertsChange;//改变之后的坐标数据

    public CustomView(Context context) {    super(context);
    initVerts();
    }    public CustomView(Context context, @Nullable AttributeSet attrs) {    super(context, attrs);
    initVerts();
    }    public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {    super(context, attrs, defStyleAttr);
    initVerts();
    }    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)    public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {    super(context, attrs, defStyleAttr, defStyleRes);
    initVerts();
    }    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    int widthMode = MeasureSpec.getMode(widthMeasureSpec);    int heightMode = MeasureSpec.getMode(heightMeasureSpec);    int width = MeasureSpec.getSize(widthMeasureSpec);    int height = MeasureSpec.getSize(heightMeasureSpec);    if(widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST){        //如果都是wrap_content的时候
        setMeasuredDimension(bitmap.getWidth(), bitmap.getHeight());
    }else{
        setMeasuredDimension(width, height);
    }
    }    @Override
    protected void onDraw(Canvas canvas) {    super.onDraw(canvas);    //初始化画笔
    initPaint();

    canvas.drawBitmapMesh(bitmap, MESHWIDTH, MESHHEIHT,verts, 0, null,0,mPaint);
    }    private void initVerts(){
    bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.pic);
    verts = new float[COUNT * 2];//所有坐标的数据
    vertsChange = new float[COUNT * 2];//改变之后的坐标数据
    int index = 0;//数组坐标
    for (int y = 0;y <= MESHHEIHT;y++){        float fy = y * bitmap.getHeight()/MESHHEIHT;        for (int x = 0;x <= MESHWIDTH;x++){        float fx = x * bitmap.getWidth()/MESHWIDTH;
        vertsChange[index * 2 + 0] = verts[index * 2 + 0] = fx;
        vertsChange[index * 2 + 1] = verts[index * 2 + 1] = fy;
        index += 1;
        }
    }
    }    /**
     * 初始化画笔
     * @return
     */
    private void initPaint(){    if(mPaint == null){
        mPaint = new Paint();
    }    //设置画笔的颜色
    mPaint.setColor(Color.BLUE);    //设置画笔的大小
    mPaint.setStrokeWidth(10);
    }    /**
     * 触摸之后改变数组
     * @param cx
     * @param cy
     */
    private void changeVerts(float cx, float cy) {    for(int i = 0; i < COUNT * 2; i += 2) {        float fx = cx - vertsChange[i + 0];        float fy = cy - vertsChange[i + 1];        float rr = fx * fx + fy * fy;        //计算每个坐标点与当前点(cx,cy)之间的距离
        float r = (float)Math.sqrt(rr);        //图片坐标离当前点的距离比对角线的距离,求出变化幅度(这里的算法可以自定义)
        float fudu = (float) (r / Math.sqrt(bitmap.getWidth() * bitmap.getWidth() + bitmap.getHeight() * bitmap.getHeight()));
        verts[i + 0] = vertsChange[i + 0] + vertsChange[i + 0] * fudu;
        verts[i + 1] = vertsChange[i + 1] + vertsChange[i + 1] * fudu;
    }    //重绘
    invalidate();
    }    @Override
    public boolean onTouchEvent(MotionEvent event) {
    changeVerts(event.getX(), event.getY());    return super.onTouchEvent(event);
    }
}

画背景色

方法

canvas.drawColor(Color.BLUE);
canvas.drawColor(Color.BLUE, PorterDuff.Mode.LIGHTEN);
canvas.drawRGB(0, 0, 255);
drawARGB(int a, int r, int g, int b) //带透明度,第一个参数就是透明度

这四个方法的使用不难理解,唯一需要讲解的是第二个方法中的Mode:

看枚举

public static enum Mode {
    ADD,
    CLEAR,
    DARKEN,
    DST,
    DST_ATOP,
    DST_IN,
    DST_OUT,
    DST_OVER,
    LIGHTEN,
    MULTIPLY,
    OVERLAY,
    SCREEN,
    SRC,
    SRC_ATOP,
    SRC_IN,
    SRC_OUT,
    SRC_OVER,
    XOR;    private Mode() {
    }
}

看图

webp

1344993853_6311.JPG

1.PorterDuff.Mode.CLEAR
所绘制不会提交到画布上。

2.PorterDuff.Mode.SRC
显示上层绘制图片

3.PorterDuff.Mode.DST
显示下层绘制图片

4.PorterDuff.Mode.SRC_OVER
正常绘制显示,上下层绘制叠盖。

5.PorterDuff.Mode.DST_OVER
上下层都显示。下层居上显示。

6.PorterDuff.Mode.SRC_IN
取两层绘制交集。显示上层。

7.PorterDuff.Mode.DST_IN
取两层绘制交集。显示下层。

8.PorterDuff.Mode.SRC_OUT
取上层绘制非交集部分。

9.PorterDuff.Mode.DST_OUT
取下层绘制非交集部分。

10.PorterDuff.Mode.SRC_ATOP
取下层非交集部分与上层交集部分

11.PorterDuff.Mode.DST_ATOP
取上层非交集部分与下层交集部分

12.PorterDuff.Mode.XOR
取两层绘制非交集。两层绘制非交集。

13.PorterDuff.Mode.DARKEN
上下层都显示。变暗

14.PorterDuff.Mode.LIGHTEN
上下层都显示。变量

15.PorterDuff.Mode.MULTIPLY
取两层绘制交集

16.PorterDuff.Mode.SCREEN
上下层都显示。

如果想了解更多PorterDuff.Mode,请乘坐下方的飞机:
各个击破搞明白PorterDuff.Mode

本人刚才坐飞机刚回来,现在结合以上资料的案例一一给大家演示不同混合模式的效果:
PorterDuff.Mode详解

注意,默认情况下是给默认画布添加背景色,当有裁剪操作时,默认画布就会变成裁剪区域的画布。

画圆角矩形

方法

public void drawRoundRect(RectF rect, float rx, float ry, Paint paint)public void drawRoundRect(float left, float top, float right, float bottom, float rx, fl



作者:NoBugException
链接:https://www.jianshu.com/p/82d70697cf45


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消