前面一篇主要是巩固Cavas绘制基本图形(如直线,矩形,点等等),今天同样是复习Cavas画圆,圆弧,等等,但是今天会多了一个path,以及Canvas画布的旋转、缩放、平移等等,画布的保存(save)和回滚(restore),接下来进入我们的主题。
先来一张静态图片给大家看一下,我复习完这一节课程之后所做的一个东西,随后再上gif图。 看完了这个,接下来我们就要去先掌握做这个东西的一些基本的知识点。接下来我们就可以在 void onDraw(Canvas canvas) 画各种图形了。
画一个半径为100的空心圆和一个半径为100的实心圆(改一下style即可)
canvas.drawCircle(100, 100, 100, paint); paint.setStyle(Paint.Style.FILL); canvas.drawCircle(400, 100, 100, paint); 画一段圆弧和一个扇形 paint.setStyle(Paint.Style.FILL); //绘制弧线区域 RectF rect = new RectF(0, 0, 100, 100); canvas.drawArc(rect, //弧线所使用的矩形区域大小 0, //开始角度 90, //扫过的角度 false, //是否使用中心 paint); //绘制弧线区域 RectF rect2 = new RectF(100, 0, 200, 100); canvas.drawArc(rect2, //弧线所使用的矩形区域大小 0, //开始角度 90, //扫过的角度 true, //是否使用中心 paint); 对比图中左右两个圆弧,你会发现,当我们useCenter即是否使用中心为false时候,画出来的圆弧就是起始角度点和结束角度点两点之间的连线构成的弧形,而为true的时候,画出来的弧形则是起始角度点和结束角度点分别与中心点的连线构成的图形,即一个扇形。 画一个椭圆 drawOval(oval, paint); //定义一个矩形区域 RectF oval = new RectF(0,0,300,200); //矩形区域内切椭圆 canvas.drawOval(oval, paint); 按照指定坐标点绘制文字 //按照既定点 绘制文本内容 paint.setTextSize(24); String textString = "转动的是指针,逝去的是年华,不变的是真心。"; float textPoint[] = new float[]{ 20,20, //第一个字坐标20,20 40,40, //第二个字坐标40,40 60,60, 80,80, 100,100, 120,120, 140,140, 160,140, 180,140, 200,140, 220,140, 240,140, 260,140, 280,140, 300,160, 320,180, 340,200, 360,220, 380,240, 400,260, 420,280}; canvas.drawPosText(textString, textPoint, paint); 使用Path画一条路径 //将起始点移动到 坐标150,150 path.moveTo(150, 150); path.lineTo(450, 150); // 画线 path.lineTo(200,300); path.lineTo(300, 50); path.lineTo(400, 300); path.lineTo(150,150); canvas.drawPath(path, paint); 使用drawTextOnPath根据path绘制一串文字 String text = "任时光匆匆流去,我只在乎你。若时间是一个圆,与你在圆的另一头重逢。"; paint.setTextSize(20); //定义一条路径,将起始点移动到坐标100,100 path.moveTo(100, 100); path.lineTo(150, 200); path.lineTo(200,250); path.lineTo(400,250); path.lineTo(450,200); path.lineTo(500,100); path.lineTo(650,100); canvas.drawTextOnPath(text, path, 20, 20, paint); 好了,基本的图形绘制算是基本过了一遍了,那么接下来就应该搞事情了,曾经看到很多网上的博客都自定义画了一个时钟,于是我也想尝试一下,画一个属于自己的style的时钟,叫“奥迪牌手表”,开始撸起袖子干活了。基本的框架已经差不多画完了,接下来就是开始让时针、分针、秒针都动起来了,因此,我们需要做的就是将三根指针画出来,然后获取当前的系统时间,并将当前时间的小时、分钟、秒钟对应扫过的角度计算出来,并将这个角度作为画布的旋转角度,即可画出这三个指针所在手表上的位置,代码如下:
// 获取当前系统时间 int minute = mCalendar.get(Calendar.MINUTE);//得到当前分钟数 int hour = mCalendar.get(Calendar.HOUR);//得到当前小时数 int sec = mCalendar.get(Calendar.SECOND);//得到当前秒数 // 绘制秒针 paint.setColor(Color.GREEN); paint.setStrokeWidth(1); //canvas.drawLine(0, 15, 0, -85, paint); float secDegree = sec/60f*360;//得到秒针旋转的角度 canvas.save(); canvas.rotate(secDegree,0,0); canvas.drawLine(0,15,0,-85,paint); canvas.restore(); // 绘制分针 paint.setColor(Color.BLUE); paint.setStrokeWidth(2); //canvas.drawLine(0, 15, 0, -65, paint); float minuteDegree = minute/60f*360;//得到分针旋转的角度 canvas.save(); canvas.rotate(minuteDegree, 0, 0); canvas.drawLine(0,15, 0, -65, paint); canvas.restore(); // 绘制时针 paint.setColor(Color.RED); paint.setStrokeWidth(3); //canvas.drawLine(0, 15, 0, -35, paint); float hourDegree = (hour*60+minute)/12f/60*360;//得到时钟旋转的角度 canvas.save(); canvas.rotate(hourDegree, 0, 0); canvas.drawLine(0, 15, 0, -35, paint); canvas.restore(); // 重绘,复原画笔为初始化的颜色, paint.setColor(Color.BLUE);有些手表还会有日期和星期,因此我也尝试着把他画上去了,同样的也是要先获取当前的系统日期,然后计算当天是星期几,然后格式化一下就可以了,如下代码:
// 绘制日期 int year = mCalendar.get(Calendar.YEAR); int month = mCalendar.get(Calendar.MONTH) + 1; // +1是因为Java中月份是从0开始的 int day = mCalendar.get(Calendar.DAY_OF_MONTH); int dayOfWeek = mCalendar.get(Calendar.DAY_OF_WEEK); String weekStr = "星期"; switch (dayOfWeek){ case 1: weekStr = weekStr + "日"; break; case 2: weekStr = weekStr + "一"; break; case 3: weekStr = weekStr + "二"; break; case 4: weekStr = weekStr + "三"; break; case 5: weekStr = weekStr + "四"; break; case 6: weekStr = weekStr + "五"; break; case 7: weekStr = weekStr + "六"; break; } RectF rectF = new RectF(-65,25,15,45); // 日期 RectF rectFWeek = new RectF(25,25,65,45); // 星期 Paint datePaint = new Paint(); datePaint.setColor(Color.BLACK); datePaint.setStyle(Paint.Style.FILL); datePaint.setAntiAlias(true); datePaint.setStrokeWidth(1); datePaint.setTextSize(10); // 日期框 canvas.drawRect(rectF,paint); canvas.drawText(year+"年"+month+"月"+day+"日",-60,38,datePaint); // 星期框 datePaint.setColor(Color.RED); canvas.drawRect(rectFWeek,paint); canvas.drawText(weekStr,30,38,datePaint);说好的是装逼的“奥迪牌手表”,不画几个圈圈上去怎么装逼
// 画奥迪标志 datePaint.setStyle(Paint.Style.STROKE); datePaint.setStrokeWidth(2); canvas.drawCircle(-20,-40,10,datePaint); canvas.drawCircle(-5,-40,10,datePaint); canvas.drawCircle(10,-40,10,datePaint); canvas.drawCircle(25,-40,10,datePaint);至此,一个静态的时钟表已经可以画出来了,但是我们需要让它的三个时针都动起来,因此我们用到了handler,一启动即在初始化的时候就要发一个空消息去通知UI线程重新绘制,然后重绘完之后继续延时1000毫秒发一条空消息进行下一次秒的绘制,集体的handler,代码如下
// 每隔一秒,在handler中调用一次重绘方法 private Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { switch (msg.what){ case INVALIDATE_SEND_MESSAGE_TAG: mCalendar = Calendar.getInstance(); invalidate(); // 告诉Ui线程重新绘制 handler.sendEmptyMessageDelayed(INVALIDATE_SEND_MESSAGE_TAG,1000);// 间隔1秒发送空消息 break; default: break; } } };在构造函数中即初始化画笔的时候也要先获取一个Calendar的实例对象,以及向UI线程发送一条空消息
mCalendar = Calendar.getInstance(); // 通知handler重绘 handler.sendEmptyMessageDelayed(INVALIDATE_SEND_MESSAGE_TAG,1000); 好了,奥迪牌手表已经全部完成了,而且三根指针都动起来了,附上ClockView.java完整代码如下: package com.chen.canvas; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.RectF; import android.os.Handler; import android.os.Message; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.view.View; import java.util.Calendar; /** * Created by Administrator on 2018/4/24 0024. */ public class ClockView extends View { private Paint paint; private Calendar mCalendar; // 获取一个时间对象 public static final int INVALIDATE_SEND_MESSAGE_TAG = 0X01; String word1 ="转动的是指针,逝去的是年华,不变的是真心。"; String word2 = "任时光匆匆流去,我只在乎你。"; String word3 = "若时间是 一个圆,"; String word4 = "与你在圆的另一头重逢。"; // 任时光匆匆流去我只在乎你。 // 若时间是一个圆, // 与你在圆的另一头重逢 // 每隔一秒,在handler中调用一次重绘方法 private Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { switch (msg.what){ case INVALIDATE_SEND_MESSAGE_TAG: mCalendar = Calendar.getInstance(); invalidate(); // 告诉Ui线程重新绘制 handler.sendEmptyMessageDelayed(INVALIDATE_SEND_MESSAGE_TAG,1000);// 间隔1秒发送空消息 break; default: break; } } }; public ClockView(Context context) { super(context); mCalendar = Calendar.getInstance(); // 初始化一个画笔 paint = new Paint(); paint.setColor(Color.BLUE); paint.setStrokeJoin(Paint.Join.ROUND); // 圆形 paint.setStrokeCap(Paint.Cap.ROUND); // 圆角 paint.setStrokeWidth(3); // 通知handler重绘 handler.sendEmptyMessageDelayed(INVALIDATE_SEND_MESSAGE_TAG,1000); } public ClockView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); } public ClockView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } int start=0; @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); paint.setAntiAlias(true); //抗锯齿 paint.setStyle(Paint.Style.STROKE); canvas.translate(canvas.getWidth() / 2, 200); // 将位置移动画纸的坐标点 canvas.drawCircle(0,0,100,paint); // 画圆圈 // 使用path绘制路径文字 canvas.save(); // 保存画布状态 canvas.translate(-75,-75);// 将圆心点的坐便往左往上移动,并将该点作为起始点(0,0) Path path = new Path(); // 初始化一条路径 path.addArc(new RectF(0,0,150,150),-224,268); // 画圆弧 -224起始角度,268为扫过的角度 // canvas.drawRect(new RectF(0,0,150,150),paint); Paint citePaint = new Paint(paint); // 使用前一个画笔的样式 citePaint.setTextSize(14); citePaint.setStrokeWidth(1); canvas.drawTextOnPath("Every second should create something new in it", path, 28, 0, citePaint); canvas.restore(); // 画时钟刻度尺子 Paint tmpPaint = new Paint(paint); tmpPaint.setStrokeWidth(1); float y = 100; // 圆的半径那么长 int count = 60; // 总刻度数 for (int i = count; i > 0; i--) { // 如果i从0开始是顺时针从y坐标正方向开始画,如果i从count开始则是从坐标负方向开始画起 if (i == count) // 标志从哪里开始画,目的是便于直观观察 paint.setColor(Color.RED); else paint.setColor(Color.BLUE); if (i % 5 == 0){ canvas.drawLine(0f,-y,0f,-y-12f,paint); // 画长刻度线,线长12f,起点(0f,y) 终点(0f,y + 12f) if (i == count) // 画 12 点 canvas.drawText(String.valueOf(i / 5), -4f, -y-25f, tmpPaint); // 文字,绘制原点x坐标,绘制原点y坐标,画笔 else // 画 1 ~ 11 点 canvas.drawText(String.valueOf(12 - i / 5), -4f, -y-25f, tmpPaint); // 文字,绘制原点x坐标,绘制原点y坐标,画笔 }else { canvas.drawLine(0f,-y,0f,-y-5f,tmpPaint); // 画短刻度线 } canvas.rotate(360 / count,0f,0f); // 旋转画布 旋转角度,x倾斜,y倾斜度 } // 绘制中心点 tmpPaint.setColor(Color.BLACK); tmpPaint.setStrokeWidth(4); canvas.drawCircle(0,0,10,tmpPaint); tmpPaint.setStyle(Paint.Style.FILL); tmpPaint.setColor(Color.RED); canvas.drawCircle(0,0,5,tmpPaint); // 获取当前系统时间 int minute = mCalendar.get(Calendar.MINUTE);//得到当前分钟数 int hour = mCalendar.get(Calendar.HOUR);//得到当前小时数 int sec = mCalendar.get(Calendar.SECOND);//得到当前秒数 // 绘制秒针 paint.setColor(Color.GREEN); paint.setStrokeWidth(1); // canvas.drawLine(0, 15, 0, -85, paint); float secDegree = sec/60f*360;//得到秒针旋转的角度 canvas.save(); canvas.rotate(secDegree,0,0); canvas.drawLine(0,15,0,-85,paint); canvas.restore(); // 绘制分针 paint.setColor(Color.BLUE); paint.setStrokeWidth(2); // canvas.drawLine(0, 15, 0, -65, paint); float minuteDegree = minute/60f*360;//得到分针旋转的角度 canvas.save(); canvas.rotate(minuteDegree, 0, 0); canvas.drawLine(0,15, 0, -65, paint); canvas.restore(); // 绘制时针 paint.setColor(Color.RED); paint.setStrokeWidth(3); // canvas.drawLine(0, 15, 0, -35, paint); float hourDegree = (hour*60+minute)/12f/60*360;//得到时钟旋转的角度 canvas.save(); canvas.rotate(hourDegree, 0, 0); canvas.drawLine(0, 15, 0, -35, paint); canvas.restore(); // 重绘,复原画笔为初始化的颜色, paint.setColor(Color.BLUE); // 绘制日期 int year = mCalendar.get(Calendar.YEAR); int month = mCalendar.get(Calendar.MONTH) + 1; // +1是因为Java中月份是从0开始的 int day = mCalendar.get(Calendar.DAY_OF_MONTH); int dayOfWeek = mCalendar.get(Calendar.DAY_OF_WEEK); String weekStr = "星期"; switch (dayOfWeek){ case 1: weekStr = weekStr + "日"; break; case 2: weekStr = weekStr + "一"; break; case 3: weekStr = weekStr + "二"; break; case 4: weekStr = weekStr + "三"; break; case 5: weekStr = weekStr + "四"; break; case 6: weekStr = weekStr + "五"; break; case 7: weekStr = weekStr + "六"; break; } RectF rectF = new RectF(-65,25,15,45); // 日期 RectF rectFWeek = new RectF(25,25,65,45); // 星期 Paint datePaint = new Paint(); datePaint.setColor(Color.BLACK); datePaint.setStyle(Paint.Style.FILL); datePaint.setAntiAlias(true); datePaint.setStrokeWidth(1); datePaint.setTextSize(10); // 日期框 canvas.drawRect(rectF,paint); canvas.drawText(year+"年"+month+"月"+day+"日",-60,38,datePaint); // 星期框 datePaint.setColor(Color.RED); canvas.drawRect(rectFWeek,paint); canvas.drawText(weekStr,30,38,datePaint); // 画奥迪标志 datePaint.setStyle(Paint.Style.STROKE); datePaint.setStrokeWidth(2); canvas.drawCircle(-20,-40,10,datePaint); canvas.drawCircle(-5,-40,10,datePaint); canvas.drawCircle(10,-40,10,datePaint); canvas.drawCircle(25,-40,10,datePaint); // 画第一行字 Path wordPath1 = new Path(); wordPath1.moveTo(-300,200); wordPath1.lineTo(300,200); wordPath1.lineTo(-300,200); datePaint.setTextSize(28); canvas.drawTextOnPath(word1,wordPath1,15,15,datePaint); // 画第二行字 Path wordPath2 = new Path(); wordPath2.addArc(new RectF(-200,250,200,450),-160,230); datePaint.setTextSize(28); datePaint.setColor(Color.GREEN); canvas.drawTextOnPath(word2,wordPath2,15,15,datePaint); // 画第三行字 Path wordPath3 = new Path(); wordPath3.moveTo(-150,350); wordPath3.lineTo(150,350); datePaint.setTextSize(28); datePaint.setColor(Color.BLUE); canvas.drawTextOnPath(word3,wordPath3,15,15,datePaint); // 画第4行字 Path wordPath4 = new Path(); wordPath4.moveTo(-100,500); wordPath4.addCircle(0,500,100, Path.Direction.CW); datePaint.setTextSize(28); datePaint.setColor(Color.CYAN); canvas.drawTextOnPath(word4,wordPath4,15,15,datePaint); // 画一个加载转动圈圈 Paint loadPaint=new Paint(); loadPaint.setStyle(Paint.Style.STROKE); loadPaint.setColor(Color.BLACK); loadPaint.setStrokeWidth(2); loadPaint.setAntiAlias(true); RectF oval=new RectF(-30, 400, 30, 460); canvas.drawArc(oval, start=start>360?0:start+3, start, false, loadPaint); loadPaint.setTextSize(18); if (start % 3 == 0) canvas.drawText(String.valueOf(start / 3) + "% ",-15,430,loadPaint); // 画百分比例 invalidate(); } } 然后,在Activity中调用: package com.chen.canvas; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import java.util.ArrayList; public class MainActivity extends AppCompatActivity { private PieView pieView; private ArrayList<PieData> mData = new ArrayList<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(new ClockView(this)); } } gif效果图如下: 另外,你会发现ClockView.java后面会多出一部分代码,上面都有注释的,就是画上图的手表底下的那些文字以及加载转动圈的代码,其实就是做完再次巩固一下而已,懒得重新写一个工程了,所以我直接丢到这里去了,影响不大。