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)

/**
  *  参数1:float left
  *  参数2:float top
  *  参数3:float right
  *  参数4:float 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;
    }
}

时间: 2024-10-07 04:57:43

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

转载爱哥自定义View系列--Canvas详解

上面所罗列出来的各种drawXXX方法就是Canvas中定义好的能画什么的方法(drawPaint除外),除了各种基本型比如矩形圆形椭圆直曲线外Canvas也能直接让我们绘制各种图片以及颜色等等,但是Canvas真正屌的我觉得不是它能画些什么,而是对画布的各种活用,上一节最后的一个例子大家已经粗略见识了变换Canvas配合save和restore方法给我们绘制图形带来的极大便利,事实上Canvas的活用远不止此,在讲Canvas之前,我想先给大家说说Canvas中非常屌毛而且很有个性的一个方法:

【Android自定义View实战】之仿百度加载动画,一种优雅的Loading方式

转载请注明出处:http://blog.csdn.net/linglongxin24/article/details/53470872 本文出自[DylanAndroid的博客] Android自定义View实战之仿百度加载动画一种优雅的Loading方式 第一个仿百度加载动画用ObjectAnimator属性动画操作ImageView的属性方法实现 第二个仿百度加载动画第二种实现方式用ValueAnimator原生的ondraw方法实现 第三个扔球动画-水平旋转动画 第四个扔球动画-垂直旋转动

【Android自定义View实战】之超简单SearchView

[Android自定义View实战]之超简单SearchView 在Android开发中我们经常会用到搜索框,而系统提供的又不尽完美.所以自定义一个比较简单的SearchView. 效果图 实现代码 package cn.bluemobi.dylan.searchview; import android.content.Context; import android.text.Editable; import android.text.TextWatcher; import android.ut

ANDROID自定义视图——onMeasure流程,MeasureSpec详解

简介: 在自定义view的时候,其实很简单,只需要知道3步骤: 1.测量--onMeasure():决定View的大小 2.布局--onLayout():决定View在ViewGroup中的位置 3.绘制--onDraw():如何绘制这个View. 而第3步的onDraw系统已经封装的很好了,基本不用我们来操心,只需要专注到1,2两个步骤就中好了. 而这篇文章就来谈谈第一步,也是十分关键得一步:"测量(Measure)" Measure(): Measure的中文意思就是测量.所以它的

【Android自定义View实战】之仿QQ运动步数圆弧及动画,Dylan计步中的控件StepArcView

转载请注明出处:http://blog.csdn.net/linglongxin24/article/details/52936609[DylanAndroid的csdn博客] 在之前的Android超精准计步器开发-Dylan计步中的首页用到了一个自定义控件,和QQ运动的界面有点类似,还有动画效果,下面就来讲一下这个View是如何绘制的. 1.先看效果图 2.效果图分析 功能说明:黄色的代表用户设置的总计划锻炼步数,红色的代表用户当前所走的步数. 初步分析:完全自定义View重写onDraw(

转载爱哥自定义View系列--Paint详解

上图是paint中的各种set方法 这些属性大多我们都可以见名知意,很好理解,即便如此,哥还是带大家过一遍逐个剖析其用法,其中会不定穿插各种绘图类比如Canvas.Xfermode.ColorFilter等等的用法. set(Paint src) 顾名思义为当前画笔设置一个画笔,说白了就是把另一个画笔的属性设置Copy给我们的画笔,不累赘了 setARGB(int a, int r, int g, int b) 不扯了,别跟我说不懂 setAlpha(int a) 同上 setAntiAlias

转载爱哥自定义View系列--文字详解

FontMetrics FontMetrics意为字体测量,这么一说大家是不是瞬间感受到了这玩意的重要性?那这东西有什么用呢?我们通过源码追踪进去可以看到FontMetrics其实是Paint的一个内部类,而它里面呢就定义了top,ascent,descent,bottom,leading五个成员变量其他什么也没有: 这五个成员变量除了top和bottom我们较熟悉外其余三个都很陌生是做什么用的呢?首先我给大家看张图: 这张图很简单但是也很扼要的说明了top,ascent,descent,bot

【朝花夕拾】Android自定义View篇之(八)多点触控(上)基础知识

前言 转载请声明,转自[https://www.cnblogs.com/andy-songwei/p/11155259.html],谢谢! 在前面的文章中,介绍了不少触摸相关的知识,但都是基于单点触控的,即一次只用一根手指.但是在实际使用App中,常常是多根手指同时操作,这就需要用到多点触控相关的知识了.多点触控是在Android2.0开始引入的,在现在使用的Android手机上都是支持多点触控的.本系列文章将对常见的多点触控相关的重点知识进行总结,并使用多点触控来实现一些常见的效果,从而达到将

Android自定义view教程05--自定义view的基础知识之LayoutInflater详解

前面的教程我们讲了一下android的动画 的使用,尤其是着重讲解了属性动画的使用.那么这章开始我们将会讲一下android 自定义view的一些基础知识.帮助大家理解. 首先我们来关注一下LayoutInflater这个类,经常使用开源控件的人对这个类应该很熟悉了,但是很少有人能把这个类讲明白 用清楚,今天我们就来深挖一下这个类. 首先我们定义一个button.xml 和button2.xml 1 <?xml version="1.0" encoding="utf-8