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配置写法,以及直接引用在界面里的适配问题,眼下看着是够用了,然后想去学习下动画

你可能感兴趣的:(canvas基础用法)