首页 > 代码库 > Android自定义View【实战教程】5??---Canvas详解及代码绘制安卓机器人

Android自定义View【实战教程】5??---Canvas详解及代码绘制安卓机器人

友情链接:

Canvas API

Android自定义View【实战教程】3??—-Paint类、Path类以及PathEffect类详解

神马是Canvas

基本概念

Canvas:可以理解为是一个为我们提供了各种工具的画布,我们可以在上面尽情的绘制(旋转,平移,缩放等等)。可以理解为系统分配给我们一个一个内存空间,然后提供了一些对这个内存空间操作的方法(API), 实际存储是在下面的bitmap。

两种画布

这里canvas可以绘制两种类型的画图,分别是view和surfaceView。
View:是普通画图,适合处理量比较小,帧率比较小的动画,比如说象棋游戏之类的。
SurfaceView:主要用在游戏,高品质动画方面的画图。
区别:在SurfaceView中定义一个专门的线程来完成画图工作,应用程序不需要等待View的刷图,提高性能。

Canvas坐标系与绘图坐标系

Canvas绘图中牵扯到两种坐标系:Canvas坐标系与绘图坐标系。

  • Canvas坐标系
    Canvas坐标系指的是Canvas本身的坐标系,Canvas坐标系有且只有一个,且是唯一不变的,其坐标原点在View的左上角,从坐标原点向右为x轴的正半轴,从坐标原点向下为y轴的正半轴。

  • 绘图坐标系
    Canvas的drawXXX方法中传入的各种坐标指的都是绘图坐标系中的坐标,而非Canvas坐标系中的坐标。默认情况下,绘图坐标系与Canvas坐标系完全重合,即初始状况下,绘图坐标系的坐标原点也在View的左上角,从原点向右为x轴正半轴,从原点向下为y轴正半轴。但不同于Canvas坐标系,绘图坐标系并不是一成不变的,可以通过调用Canvas的translate方法平移坐标系,可以通过Canvas的rotate方法旋转坐标系,还可以通过Canvas的scale方法缩放坐标系,而且需要注意的是,translate、rotate、scale的操作都是基于当前绘图坐标系的,而不是基于Canvas坐标系,一旦通过以上方法对坐标系进行了操作之后,当前绘图坐标系就变化了,以后绘图都是基于更新的绘图坐标系了。也就是说,真正对我们绘图有用的是绘图坐标系而非Canvas坐标系。

我们看下面代码就可以明白:

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

        //未平移 在原点
        canvas.drawLine(0, 0, width, 0, mPaint);//绘制x轴
        canvas.drawLine(0, 0, 0, height, mPaint);//绘制y轴

        //第一次移动
        canvas.translate(200,200);
        canvas.drawLine(0, 0, width, 0, mPaint);//绘制x轴
        canvas.drawLine(0, 0, 0, height, mPaint);//绘制y轴

        canvas.restore();

        //第二次移动并旋转
        canvas.translate(200,200);
        canvas.rotate(30);
        canvas.drawLine(0, 0, width, 0, mPaint);//绘制x轴
        canvas.drawLine(0, 0, 0, height, mPaint);//绘制y轴

    }

每次绘制同样的(startX, startY,stopX,stopY, paint)线,
但是我们发现平移或者旋转之后画出的线坐标发生了变化
技术分享

那么有童鞋问了,如果我不想让坐标发生变化,或者再回去原点怎么搞?
别担心,只需要执行canvas.restore(),下面详细讲解。

Canvas保存和还原

  • canvas.save()
    保存当前坐标
  • canvas.restore()
    回复上一次坐标,如果有保存,回到最后一次保存的坐标,如果没保存,则会报错java.lang.IllegalStateException: Underflow in restore - more restores than saves ,要先存再取。
  • restoreToCount(int saveCount)
    回到第几次的保存坐标状态

对Canvas的操作 — 平移,旋转,缩放

Canvas平移

/** 
 * 画布向(dx,dy)方向平移 
 *  
 * 参数1: 向X轴方向移动dx距离 
 * 参数2: 向Y轴方向移动dy距离   
 */
 canvas.translate(float dx, float dy);

Canvas缩放

/** 
 * 在X轴方向放大为原来sx倍,Y轴方向方大为原来的sy倍 
 * 默认原点为左上角
 * 参数1: X轴的放大倍数 
 * 参数2: Y轴的放大倍数 
 */
canvas.scale(float sx, float sy);

/** 
 * 在X轴方向放大为原来sx倍,Y轴方向方大为原来的sy倍 
 * 参数1: X轴的放大倍数 
 * 参数2: Y轴的放大倍数 
 * 参数3: 原点X坐标
 * 参数4: 原点Y坐标
 */
canvas.scale(float sx, float sy, float px, float py);

Canvas旋转

/** 
 * 原点为中心,旋转degrees度(顺时针方向为正方向 )
 * 参数: 旋转角度 
 */
canvas.rotate(float degrees);

/** 
 * 以(px,py)为中心,旋转30度,顺时针方向为正方向 
 * 参数1: 旋转角度
 * 参数2: 原点X坐标
 * 参数3: 原点Y坐标 
 */
canvas.rotate(float degrees, float px, float py);

绘制

画文字

/**
 * 参数1:输入的内容 
 * 参数2:文本x轴的位置 
 * 参数3:文本Y轴的位置 
 * 参数4:画笔对象 
 */
drawText(String text, float x, float y,  Paint paint)

/**
 * 参数1:输入的内容 
 * 参数2:要从第几个字开始绘制
 * 参数3:要绘制到第几个文字 
 * 参数4:文本x轴的位置 
 * 参数5:文本Y轴的位置 
 * 参数6:画笔对象 
 */
drawText(String text, int start, int end, float x, float y,Paint paint)

样例:

canvas.drawText("开始写字啦!", 200,200,mPaint);
canvas.drawText("开始写字啦!",2,3, 200,400,mPaint);

技术分享

画圆

/**
 * 参数1:圆心X 
 * 参数2:圆心Y 
 * 参数3:半径R 
 * 参数4:画笔对象 
 */  
drawCircle(float cx, float cy, float radius, Paint paint)

样例:

    mPaint.setStyle(Paint.Style.STROKE);
    canvas.drawCircle(300,300,80,mPaint);

    mPaint.setStyle(Paint.Style.FILL);
    canvas.drawCircle(300,500,80,mPaint);

    mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
    canvas.drawCircle(300,700,80,mPaint);

技术分享

画线

/* 
 * 参数1:startX 
 * 参数2:startY 
 * 参数3:stopX 
 * 参数4:stopY 
 * 参数5:画笔对象 
 */   
canvas.drawLine(float startX, float startY, float stopX, float stopY,Paint paint);

/* 
 * 同时绘制多条线。 
 * 参数1:float数组:每四个一组为一条线。
 * 参数2:画笔对象 
 */  
canvas.drawLines(@Size(multiple=4)float[] pts, Paint paint);

样例:

mPaint.setColor(getResources().getColor(android.R.color.holo_red_dark));
canvas.drawLine(50,50,200,50,mPaint);

mPaint.setColor(getResources().getColor(android.R.color.darker_gray));
canvas.drawLines(new float[]{200,200,300,200,300,300,300,400},mPaint);

技术分享

画椭圆

/**
 * 参数1: 矩形
 * 参数2: 画笔
 * /
canvas.drawOval(RectF oval, Paint paint);

/**
 *  参数1:float left 
 *  参数2:float top 
 *  参数3:float right 
 *  参数4:float bottom 
 *  参数5:画笔
 */
canvas.drawOval(float left, float top, float right, float bottom, @NonNull Paint paint);

样例:

mPaint.setColor(getResources().getColor(android.R.color.holo_red_dark));
canvas.drawOval(new RectF(50,50,400,400),mPaint);

mPaint.setColor(getResources().getColor(android.R.color.darker_gray));
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
     canvas.drawOval(50,500,700,700,mPaint);
}

技术分享

画弧度

/**
 *  参数1:RectF对象。 
 *  参数2:开始的角度。(水平向右为0度顺时针反向为正方向) 
 *  参数3:扫过的角度 
 *  参数4:是否和中心连线 
 *  参数5:画笔对象 
 */    
canvas.drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter,Paint paint);

/**
 *  参数1:float left 
 *  参数2:float top 
 *  参数3:float right 
 *  参数4:float bottom  
 *  参数5:开始的角度。(水平向右为0度顺时针反向为正方向) 
 *  参数6:扫过的角度 
 *  参数7:是否和中心连线 
 *  参数8:画笔对象 
 */
canvas.drawArc(float left, float top, float right, float bottom,float startAngle, float sweepAngle, boolean useCenter,Paint paint);

样例:

mPaint.setColor(getResources().getColor(android.R.color.holo_red_dark));
canvas.drawArc(new RectF(50,50,400,400),45,135,true,mPaint);

mPaint.setColor(getResources().getColor(android.R.color.darker_gray));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
      canvas.drawArc(50,500,700,700,45,135,false,mPaint);
}

技术分享

矩形

/** 
 *  矩形 
 *  参数1:float left 
 *  参数2:float top 
 *  参数3:float right 
 *  参数4:float bottom 
 *  参数5:画笔
 */  
canvas.drawRect(float left, float top, float right, float bottom,Paint paint);  

/** 
 *  参数1:矩形 
 *  参数2:画笔
 */  
canvas.drawRectRect r,Paint paint);  

样例:

mPaint.setColor(getResources().getColor(android.R.color.darker_gray));
canvas.drawRect(new RectF(50,50,400,400),mPaint);

        mPaint.setColor(getResources().getColor(android.R.color.holo_red_dark));
canvas.drawRect(50,500,700,700,mPaint);

技术分享

圆角矩形

/**
  *  参数1:矩形 
  *  参数2:x半径
  *  参数3:y半径 
  *  参数4: 画笔
  */
drawRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Paint paint)


/**
  *  参数1float left 
  *  参数2float top 
  *  参数3float right 
  *  参数4float bottom 
  *  参数5:x半径
  *  参数6:y半径 
  *  参数4: 画笔
  */
drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, @NonNull Paint paint)

样例:

mPaint.setColor(getResources().getColor(android.R.color.darker_gray));
canvas.drawRoundRect(new RectF(50,50,400,400),20,20,mPaint);
                               mPaint.setColor(getResources().getColor(android.R.color.holo_red_dark));
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    canvas.drawRoundRect(50,500,700,700,30,50,mPaint);
}

技术分享

画点

/** 
 * 参数1、2:点的x、y坐标 
 */  
canvas.drawPoint(60, 390, p);//画一个点  

/** 
 * 参数1:多个点,每两个值为一个点。最后个数不够两个的值,忽略。 
 */  
canvas.drawPoints(new float[]{60,400,65,400,70,400}, p);//画多个点

样例:

mPaint.setColor(getResources().getColor(android.R.color.darker_gray));
        canvas.drawPoint(50,50,mPaint);

        mPaint.setColor(getResources().getColor(android.R.color.holo_red_dark));
        canvas.drawPoints(new float[]{100,100,200,200,300, 300, 400,400,500,500,600,600},mPaint);

技术分享

画图片

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);   
/** 
 * 参数1:bitmap对象 
 * 参数2:图像左边坐标点 
 * 参数3:图像上边坐标点 
 */  
canvas.drawBitmap(Bitmap bitmap, float left, float top, Paint paint);

样例:

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
canvas.drawBitmap(bitmap, 200,300, mPaint);

技术分享

还有问题可以查看Canvas API

到这里基本属性就讲完了,接下来是一个练习。

代码绘制安卓小机器人

下面是代码 , 相当简单,就是计算一下坐标,就不详细讲了,有问题可以留言。

public class AndroidView extends View {

    private float bodyWidth;
    private float bodyHeigh;

    private float armWidth;
    private float armHeight;

    private float legWidth;
    private float legHeight;

    private static final int INTERSPACE = 20;

    private Paint mPaint;

    private RectF bodyRect;
    private RectF legRect;
    private RectF armRect;


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

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

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

        init();
    }

    private void init() {
        mPaint = new Paint();
        mPaint.setColor(getResources().getColor(android.R.color.holo_green_dark));
    }

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

        setBodyParams();

        setArmParams();

        setLegParams();
    }

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

        canvas.save();

        //画身体
        canvas.drawRoundRect(bodyRect, 20, 20, mPaint);

        //画头
        canvas.translate(0, -(bodyWidth / 2 + INTERSPACE));
        canvas.drawArc(bodyRect, 0, -180, true, mPaint);

        //画左胳膊
        canvas.drawRoundRect(armRect, 30, 30, mPaint);

        //画右胳膊
        canvas.translate(bodyWidth + 5 * INTERSPACE, 0);
        canvas.drawRoundRect(armRect, 30, 30, mPaint);

        //画左腿
        canvas.translate(-(bodyWidth + 7 * INTERSPACE),bodyWidth*11/10);
        canvas.drawRoundRect(legRect, 30, 30, mPaint);

        //画右腿
        canvas.translate(2*INTERSPACE+legWidth,0);
        canvas.drawRoundRect(legRect, 30, 30, mPaint);

        //画左眼
        canvas.translate(0,-bodyHeigh-5*INTERSPACE);
        mPaint.setColor(getResources().getColor(android.R.color.white));
        canvas.drawCircle(getWidth()/2,getHeight()/2,INTERSPACE/2,mPaint);

        //画右眼
        canvas.translate(-(2*INTERSPACE+legWidth),0);
        mPaint.setColor(getResources().getColor(android.R.color.white));
        canvas.drawCircle(getWidth()/2,getHeight()/2,INTERSPACE/2,mPaint);

        canvas.restore();
        mPaint.setTextSize(60);
        mPaint.setColor(getResources().getColor(android.R.color.holo_red_dark));
        canvas.drawText("我是安卓小机器人",150,100,mPaint);
    }

    private void setBodyParams() {
        bodyWidth = getWidth() * 2 / 5;
        bodyHeigh = bodyWidth;

        bodyRect = new RectF();
        bodyRect.left = (getWidth() - bodyWidth) / 2;
        bodyRect.top = (getHeight() - bodyHeigh) / 2;
        bodyRect.right = bodyRect.left + bodyWidth;
        bodyRect.bottom = bodyRect.top + bodyHeigh;

    }

    private void setLegParams() {
        legWidth = getWidth() * 1 / 13;
        legHeight = getHeight() * 1 / 7;

        legRect = new RectF();
        legRect.left = (getWidth() - legWidth) / 2;
        legRect.top = (getHeight() - legHeight) / 2;
        legRect.right = legRect.left + legWidth;
        legRect.bottom = legRect.top + legHeight;
    }

    private void setArmParams() {
        armWidth = getWidth() * 1 / 13;
        armHeight = getHeight() * 1 / 6;

        armRect = new RectF();
        armRect.left = (getWidth() - bodyWidth) / 2 - INTERSPACE * 4;
        armRect.top = getHeight() / 2 + INTERSPACE * 2;
        armRect.right = armRect.left + armWidth;
        armRect.bottom = armRect.top + armHeight;
    }
}

技术分享

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    Android自定义View【实战教程】5??---Canvas详解及代码绘制安卓机器人