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直接还原到对应栈的保存状态
这里需要声明一点,如果连续两次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配置写法,以及直接引用在界面里的适配问题,眼下看着是够用了,然后想去学习下动画