canvas基础用法

canvas

画布,画笔绘制东西的承载物,展示一切效果

坐标系


canvas有两套坐标系,一套是绘制坐标系,一套是canvas坐标系。

canvas坐标系 唯一确定。在 viewToolImpl里 创建canvas时,就已确定

绘制坐标系,随着canvas的变化而变化

//第一次绘制

canvas.drawLine(0,0,canvas.getWidth(),0,mPaint);

//挪移canvas

canvas.translate(0,100);

//改变画笔颜色,做个区分

mPaint.setColor(Color.BLUE);

//第二次绘制

canvas.drawLine(0,0,canvas.getWidth(),0,mPaint);

绘制线上的文字


drawTextOnPath(txt,path,xOffset,yOffset,paiint)

图层


状态栈

save、restore方法来保存和还原变换操作MAtrix及Clip裁剪,也可通过restoretocount直接还原到对应栈的保存状态

可以通过canvas.getSaveCount()查看当前图层

        这里需要声明一点,如果连续两次save,图层会是0(默认canvas有一层)、1、2 ,再调用一次restore,会回到1 而不是上图中的直接全部都在一个图层,上图只是大概说明一下情况,restore会合并

layer栈

saveLayer会创建一个透明图层(离屏Bitmap离屏缓存),并且会将savalayer之前的一些canvas操作延续过来,后续的绘图操作都在新建的layer上面进行,当我们调用restore或restoreToCount时,更新到对应的图层和画布上

截取部分图片绘制


Bitmap bitmap= BitmapFactory.decodeResource(getResources(),R.drawable.pink_change);

//绘制的普通bitmap

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

//要绘制的部分

Rect src=new Rect();

src.left=0;

src.top=0;

//srcRect中left、top、right、bottom的值都是以Bitmap本身的局部坐标系为基础的。

src.right=bitmap.getWidth();

src.bottom=(int)bitmap.getHeight()/2;

float radio=((((float)(src.bottom-src.top))/(src.right-src.left)));

//绘制到的位置

Rect dst=new Rect();

//dstRecF是绘图坐标系中的坐标,不是Bitmap本身的局部坐标系

dst.left=0;

dst.top=bitmap.getHeight();

dst.right=bitmap.getWidth()*3;

dst.bottom=(int)(radio*(dst.right-dst.left))*3+bitmap.getHeight();

//将原bitmap原宽度高度一半绘制成新的 3倍的宽和高

canvas.drawBitmap(bitmap,src,dst,mPaint);

图层存储


canvas.save()

图层加载


canvas.restore()

//存储当前图层状态

canvas.save();

//-----------------在这部分的内容相当于是在临时空间绘制,绘制完成后调用restore,又会再次进入之前的状态----------------------------------

canvas.translate(0,300);

//这里需要注意 涉及到paint的属性变化 都需要重置回去,否则会继续延用当前的paint属性

mPaint.setColor(Color.RED);

canvas.drawLine(0,0,canvas.getWidth(),0,mPaint);

mPaint.setColor(Color.BLUE);

//----------------------------------------------------------

//重新回到之前状态

canvas.restore();

        实践画了一个表。传上来随意看看。 参考了Android canvas绘图基础之运动的时钟 ,基本按照这个文章敲一次就可以,我在这个的基础上,做了一点改变,将表的半径进行了修改,另外把时分秒针的变化,做了进一步的优化处理。

public class WatchCanvasextends View {

//表盘画笔

    PaintmPaint;

//时针

    PainthourPaint;

//分针

    PaintminutePaint;

//秒针

    PaintsecendPaint;

//控件宽高

    int mWidth,mHeight;

//半径

    int radius;

//圆心

    int circleX,circleY;

//指针 时分秒长度

    int hourLength,minuteLength,secendLength;

//反向长度 -越过原点后的一截

    int backLength;

//长刻度

int longDegreeLength=40;

//短刻度

int shortDegreeLength=20;


Handler mHandler=new Handler(){

@Override

        public void handleMessage(Message msg) {

super.handleMessage(msg);

invalidate();

sendEmptyMessageDelayed(0,1000);

}

};

public WatchCanvas(Context context) {

this(context,null);

}

public WatchCanvas(Context context,@Nullable AttributeSet attrs) {

this(context, attrs,0);

}

public WatchCanvas(Context context,@Nullable AttributeSet attrs,int defStyleAttr) {

this(context, attrs, defStyleAttr,0);

}

public WatchCanvas(Context context,@Nullable AttributeSet attrs,int defStyleAttr,int defStyleRes) {

super(context, attrs, defStyleAttr, defStyleRes);

//绘制线路

        mPaint =new Paint();

//抗锯齿

        mPaint.setAntiAlias(true);

//防抖动

        mPaint.setDither(true);

//添加帽

        mPaint.setStrokeCap(Paint.Cap.ROUND);

mPaint.setColor(0xFF7FC2D8);

mPaint.setStrokeWidth(10);

mPaint.setTextSize(40);

mPaint.setStyle(Paint.Style.STROKE);

//时针

        hourPaint=new Paint();

hourPaint.setStrokeWidth(20);

hourPaint.setAntiAlias(true);

//分针

        minutePaint=new Paint();

minutePaint.setStrokeWidth(10);

minutePaint.setAntiAlias(true);

//秒针

        secendPaint=new Paint();

secendPaint.setStrokeWidth(5);

secendPaint.setAntiAlias(true);

}

@Override

    protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

mWidth=getMeasuredWidth();

mHeight=getMeasuredHeight();

radius=mWidth/2-10;

circleX=mWidth/2;

circleY=mHeight/2;

//这些长度是我随意配比写的 觉得还不错

hourLength=radius/3-50;

minuteLength=radius/2+20;

secendLength=radius-150;

backLength=radius/9;

}

@Override

    protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

canvas.drawCircle(circleX,circleY,radius,mPaint);

//画线 用旋转canvas实现即可

        for (int i=0;i<60;i++){

if (i%5==0) {

mPaint.setStrokeWidth(5);

canvas.drawLine(circleX,circleY-radius,circleX,(circleY-radius)+longDegreeLength,mPaint);

}else{

mPaint.setStrokeWidth(2);

canvas.drawLine(circleX,circleY-radius,circleX,(circleY-radius)+shortDegreeLength,mPaint);

}

canvas.rotate(6f,circleX,circleY);

}

//画字 如果采用旋转canvas,数字会各种外协 6字颠倒为9 等等

        canvas.save();

canvas.translate(radius,circleY);

mPaint.setTextSize(70);

mPaint.setStyle(Paint.Style.FILL);

mPaint.setTextAlign(Paint.Align.CENTER);

for(int i=0;i<12;i++){

float text[]=calculatePoint((i+1)*30,radius-longDegreeLength-mPaint.getTextSize());

canvas.drawText(String.valueOf(i+1),text[2],text[3]+mPaint.getTextSize()/2,mPaint);

}

canvas.restore();

//画指针

        Calendar calendar=Calendar.getInstance();

//为了更贴合实际 先画秒针  再分针  时针

        canvas.save();

float[] secondPointF = calculatePoint(calendar.get(Calendar.SECOND)%60*360/60,secendLength);

canvas.translate(circleX,circleY);

canvas.drawLine(secondPointF[0], secondPointF[1], secondPointF[2], secondPointF[3],secendPaint);

canvas.restore();

//分针  对比链接和我的 会发现 主要就是这个角度这块,我做了处理,分针会随着秒针划过的弧度缓慢变化,同理时针也是

        canvas.save();

float[] minutePointF = calculatePoint(calendar.get(Calendar.MINUTE)%60*360/60+calendar.get(Calendar.SECOND)%60*(30.0f/360f),minuteLength);

canvas.translate(circleX,circleY);

canvas.drawLine(minutePointF[0], minutePointF[1], minutePointF[2], minutePointF[3],minutePaint);

canvas.restore();

//时针

        canvas.save();

float[] hourPointF = calculatePoint(calendar.get(Calendar.HOUR)*360/12+calendar.get(Calendar.MINUTE)%60*(360.0f/12/360f)+calendar.get(Calendar.SECOND)%60*(360.0f/12/60/360f),hourLength);

canvas.translate(circleX,circleY);

canvas.drawLine(hourPointF[0],hourPointF[1],hourPointF[2],hourPointF[3],hourPaint);

canvas.restore();

//画圆心

        mPaint.setStyle(Paint.Style.STROKE);

canvas.drawCircle(circleX,circleY,2,mPaint);

//通知handler 后续刷新界面

mHandler.sendEmptyMessage(0);

}

//计算线段的起始坐标 这块需要理解下,其实就是高中数学知识,只不过太久不用,都忘光了,有时间需要温习下

    private float[] calculatePoint(float angle,float length){

float[] points =new float[4];

if(angle <=90f){

points[0] = -(float) Math.sin(angle*Math.PI/180) *backLength;

points[1] = (float) Math.cos(angle*Math.PI/180) *backLength;

points[2] = (float) Math.sin(angle*Math.PI/180) * length;

points[3] = -(float) Math.cos(angle*Math.PI/180) * length;

}else if(angle <=180f){

points[0] = -(float) Math.cos((angle-90)*Math.PI/180) *backLength;

points[1] = -(float) Math.sin((angle-90)*Math.PI/180) *backLength;

points[2] = (float) Math.cos((angle-90)*Math.PI/180) * length;

points[3] = (float) Math.sin((angle-90)*Math.PI/180) * length;

}else if(angle <=270f){

points[0] = (float) Math.sin((angle-180)*Math.PI/180) *backLength;

points[1] = -(float) Math.cos((angle-180)*Math.PI/180) *backLength;

points[2] = -(float) Math.sin((angle-180)*Math.PI/180) * length;

points[3] = (float) Math.cos((angle-180)*Math.PI/180) * length;

}else if(angle <=360f){

points[0] = (float) Math.cos((angle-270)*Math.PI/180) *backLength;

points[1] = (float) Math.sin((angle-270)*Math.PI/180) *backLength;

points[2] = -(float) Math.cos((angle-270)*Math.PI/180) * length;

points[3] = -(float) Math.sin((angle-270)*Math.PI/180) * length;

}

return points;

}

}

这里是一个自定义的仪表盘,我感觉还是满有趣的

public class CarDashboardCanvasextends View {

private PaintmPaint;

//屏幕宽度 高度

    private int mWidth,mHeight;

//圆心

    private int circleX,circleY;

//半径

    private int radius;

//边距

    private int padding;

private int longDegree=80,//长刻度

            mediumDegree=55,//中刻度

            shortDegree=40;//短刻度

//进度

    @FloatRange(from =0f,to =270f)

private float interProgress;

//中间显示的数字

    private int progress;

//配置四个色值

    private int circleColor=0xFF7FC2D8,//表盘 刻度色值

            progressColor=Color.RED,//进度条色值

            circleTextColor=Color.GRAY,//表盘刻度数字色值

            textColor=0xFF7FC2D8;//中间数字色值

//表盘最大值

    private int  maxInt=200;

public CarDashboardCanvas(Context context) {

this(context,null);

}

public CarDashboardCanvas(Context context,@Nullable AttributeSet attrs) {

this(context, attrs,0);

}

public CarDashboardCanvas(Context context,@Nullable AttributeSet attrs,int defStyleAttr) {

this(context, attrs, defStyleAttr,0);

}

public CarDashboardCanvas(Context context,@Nullable AttributeSet attrs,int defStyleAttr,int defStyleRes) {

super(context, attrs, defStyleAttr, defStyleRes);

//绘制线路

        mPaint =new Paint();

//抗锯齿

        mPaint.setAntiAlias(true);

//防抖动

        mPaint.setDither(true);

//添加帽

        mPaint.setStrokeCap(Paint.Cap.ROUND);

mPaint.setColor(circleColor);

mPaint.setStrokeWidth(50);

mPaint.setTextSize(40);

mPaint.setStyle(Paint.Style.STROKE);

padding=20;

}

@Override

    protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

mWidth=measure(widthMeasureSpec);

mHeight=measure(heightMeasureSpec);

circleX=mWidth/2;

circleY=mHeight/2;

radius=circleX-(int)mPaint.getStrokeWidth()/2-padding/2;

}

@Override

    protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

mPaint.setColor(circleColor);

mPaint.setStrokeWidth(50);

mPaint.setStyle(Paint.Style.STROKE);

//画弧线

        RectF rectF=new RectF(circleX-radius,circleY-radius,circleX+radius,circleY+radius);

//弧线是从3点钟方向开始顺时针旋转的  useCenter属性 表示是否显示中点连接线

        canvas.drawArc(rectF,135,270,false,mPaint);

//画刻度

        canvas.save();

//line从12点钟绘制所以需要顺时针旋转225°

        canvas.rotate(225,circleX,circleY);

for (int i=0;i<=maxInt;i++){

if (i%10==0){

mPaint.setStrokeWidth(10);

canvas.drawLine(circleX,circleY-radius+25,circleX,circleY-radius+longDegree+mPaint.getStrokeWidth()/2,mPaint);

}else if(i%5==0){

mPaint.setStrokeWidth(8);

canvas.drawLine(circleX,circleY-radius+25,circleX,circleY-radius+mediumDegree+mPaint.getStrokeWidth()/2,mPaint);

}else{

mPaint.setStrokeWidth(5);

canvas.drawLine(circleX,circleY-radius+25,circleX,circleY-radius+shortDegree+mPaint.getStrokeWidth()/2,mPaint);

}

canvas.rotate(270f/maxInt,circleX,circleY);

}

canvas.restore();

//画数字

        canvas.save();

canvas.translate(circleX,circleY);

mPaint.setTextAlign(Paint.Align.CENTER);

mPaint.setTextSize(40);

mPaint.setColor(circleTextColor);

mPaint.setStyle(Paint.Style.FILL_AND_STROKE);

mPaint.setStrokeWidth(1);

for (int i=0;i<=maxInt;i+=10){

float[] f=calculateXy((float) (270f/maxInt)*(i),radius-25-longDegree-(int)(mPaint.getTextSize()/2)-10);

canvas.drawText(String.valueOf(i),f[0],f[1],mPaint);

}

canvas.restore();

//再绘制个内部的进度

        mPaint.setStrokeWidth(40);

mPaint.setStyle(Paint.Style.STROKE);

mPaint.setColor(progressColor);

//弧线是从3点钟方向开始顺时针旋转的  useCenter属性 表示是否显示中点连接线---这里屏蔽了 单一色效果,采用了渐变色圆弧 有需要放开这句将下面的比较骚 注释就好了

//canvas.drawArc(rectF,135,interProgress,false,mPaint);

//比较骚的效果--圆弧渐变色

        canvas.save();

int save=canvas.saveLayer(null,null);

canvas.drawArc(rectF,135,interProgress,false,mPaint);

mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));

//自己搞得大的渐变色图就行

Bitmap bitmap= BitmapFactory.decodeResource(getResources(),R.drawable.change);

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

mPaint.setXfermode(null);

canvas.restoreToCount(save);

canvas.restore();

//中间绘制的字

        mPaint.setStrokeWidth(2);

mPaint.setTextSize(160);

mPaint.setStyle(Paint.Style.FILL);

mPaint.setColor(textColor);

canvas.drawText(String.valueOf(progress),circleX-mPaint.getStrokeWidth()/2,circleY+mPaint.getTextSize()/2-mPaint.getStrokeWidth()/2,mPaint);

}

//测量

    private int measure(int measureSpec){

int result=getMeasuredWidth();

int mode= MeasureSpec.getMode(measureSpec);

int size=MeasureSpec.getSize(measureSpec);

switch (mode){

case MeasureSpec.UNSPECIFIED:

case MeasureSpec.AT_MOST:

result= Math.min(result,size);

break;

case MeasureSpec.EXACTLY:

result=size;

break;

}

return result;

}

//计算位置

    private float[] calculateXy(float angle,int length){

float[] f=new float[2];

//需要加225°

        angle+=225;

if (angle>360){

angle=angle%360;

}

if (angle<=90f) {

f[0]=(float) Math.sin(angle*Math.PI/180)*length;

f[1]=-(float) Math.cos(angle*Math.PI/180)*length;

}else if (angle<=180f){

f[0]=(float) Math.cos((angle-90)*Math.PI/180)*length;

f[1]=(float) Math.sin((angle-90)*Math.PI/180)*length;

}else if (angle<=270f){

f[0]=-(float) Math.sin((angle-180)*Math.PI/180)*length;

f[1]=(float) Math.cos((angle-180)*Math.PI/180)*length;

}else{

f[0]=-(float) Math.cos((angle-270)*Math.PI/180)*length;

f[1]=-(float) Math.sin((angle-270)*Math.PI/180)*length;

}

return f;

};

//设置进度

    public void setProgress(int i){

progress=i;

interProgress=i*270f/maxInt;

invalidate();

}

  2021-01-29 到 2021.2.1 canvas只是基本的了解了下,此外没有考虑写xml配置写法,以及直接引用在界面里的适配问题,眼下看着是够用了,然后想去学习下动画

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

推荐阅读更多精彩内容