Android自定义View制作仪表盘界面
前言
最近我跟自定义view杠上了,甚至说有点上瘾到走火入魔了。身为菜鸟的我自然要查阅大量的资料,学习大神们的代码,这不,前两天正好在郭神在微信公众号里推送一片自定义控件的文章——一步步实现精美的钟表界面。正适合我这种菜鸟来学习,闲着没事,我就差不多依葫芦画瓢也写了一个自定义表盘view,现在纯粹最为笔记记录下来。先展示下效果图:
下面进入正题
自定义表盘属性
老规矩,先在attrs文件里添加表盘自定义属性
<declare-styleable name="watchview"> <attr name="watchradius" format="dimension"/> //表盘半径 <attr name="watchpadding" format="dimension"/> //表盘相对控件边框距离 <attr name="watchscalepadding" format="dimension"/> //刻度相对表盘距离 <attr name="watchscalecolor" format="color|reference"/> //常规刻度颜色 <attr name="watchscalelength" format="dimension|reference"/> //常规刻度长度 <attr name="watchhourscalecolor" format="dimension|reference"/> //整点刻度颜色 <attr name="watchhourscalelength" format="dimension|reference"/> //整点刻度长度 <attr name="hourpointcolor" format="color|reference"/> //时针颜色 <attr name="hourpointlength" format="dimension|reference"/> //时针长度 <attr name="minutepointcolor" format="color|reference"/> //分针颜色 <attr name="minutepointlength" format="dimension|reference"/> //分针长度 <attr name="secondpointcolor" format="color|reference"/> //秒针颜色 <attr name="secondpointlength" format="dimension|reference"/> //秒针长度 <attr name="timetextsize" format="dimension|reference"/> //表盘字体大小 <attr name="timetextcolor" format="color|reference"/> //表盘字体颜色 </declare-styleable>
在自定义view的构造方法种获取自定义属性
先将属性变量声明如下:
<span style="font-size:14px;"> /**表盘边距*/ private float mwatchpadding = 5; /**表盘与刻度边距*/ private float mwatchscalepadding = 5; /**表盘半径*/ private float mwatchradius = 250; /**表盘刻度长度*/ private float mwatchscalelength; /**表盘刻度颜色*/ private int mwatchscalecolor = color.black; /**表盘整点刻度长度*/ private float mhourscalelength = 8; /**表盘整点刻度颜色*/ private int mhourscalecolor = color.blue; /**表盘时针颜色*/ private int mhourpointcolor = color.black; /**表盘时针长度*/ private float mhourpointlength = 100; /**表盘分针颜色*/ private int mminutepointcolor = color.black; /**表盘分针长度*/ private float mminutepointlength = 130; /**表盘秒针颜色*/ private int msecondpointcolor = color.red; /**表盘秒针长度*/ private float msecondpointlength = 160; /**表盘尾部指针长度*/ private float mendpointlength; /**表盘数字颜色*/ private int mtimetextcolor = color.black; /**表盘数字大小*/ private int mtimetextsize = 15;</span>
在构造方法种获取自定义属性
<span style="font-size:14px;"> public watchview(context context, attributeset attrs) { super(context, attrs); typedarray array = context.obtainstyledattributes(attrs,r.styleable.watchview); int n = array.getindexcount(); for (int i = 0;i<n;i++){ int attr = array.getindex(i); switch (attr){ case r.styleable.watchview_watchradius: mwatchradius = array.getdimensionpixelsize(attr,myutil.dip2px(context,60)); break; case r.styleable.watchview_watchpadding: mwatchpadding = array.getdimensionpixelsize(attr,myutil.dip2px(context,5)); break; case r.styleable.watchview_watchscalepadding: mwatchscalepadding = array.getdimensionpixelsize(attr,myutil.dip2px(context,3)); break; case r.styleable.watchview_watchscalelength: mwatchscalelength = array.getdimensionpixelsize(attr,myutil.dip2px(context,5)); break; case r.styleable.watchview_watchscalecolor: mwatchscalecolor = array.getcolor(attr, color.parsecolor("#50000000")); break; case r.styleable.watchview_watchhourscalelength: mhourscalelength = array.getdimensionpixelsize(attr,myutil.dip2px(context,10)); break; case r.styleable.watchview_watchhourscalecolor: mhourscalecolor = array.getcolor(attr,color.black); break; case r.styleable.watchview_hourpointlength: mhourpointlength = array.getdimensionpixelsize(attr,myutil.dip2px(context,35)); break; case r.styleable.watchview_hourpointcolor: mhourpointcolor = array.getcolor(attr,color.black); break; case r.styleable.watchview_minutepointlength: mminutepointlength = array.getdimensionpixelsize(attr,myutil.dip2px(context,40)); break; case r.styleable.watchview_minutepointcolor: mminutepointcolor = array.getcolor(attr,color.black); break; case r.styleable.watchview_secondpointlength: msecondpointlength = array.getdimensionpixelsize(attr,myutil.dip2px(context,50)); break; case r.styleable.watchview_secondpointcolor: msecondpointcolor = array.getcolor(attr,color.blue); break; case r.styleable.watchview_timetextsize: mtimetextsize = array.getdimensionpixelsize(attr,myutil.dip2px(context,15)); break; case r.styleable.watchview_timetextcolor: mtimetextcolor = array.getcolor(attr,color.black); break; } } array.recycle(); }</span>
设置控件大小
这里当然就是重写onmeasure方法啦,这里我们处理的简单点,如下面代码所示,当我们将控件的宽高都设定为wrap_content(即measurespec.unspecifed)时,我们将宽高设定为默认值(wrapcontentsize)和圆盘半径+圆盘边距(mwatchradius+mwatchpadding)之间取最大值,其他情况下就取系统自取值。当然作为一个严谨的控件,仅仅这样处理肯定是不行的。项目中,我们要根据我们的需求自行修改里面的代码以适配。
<span style="font-size:14px;"> @override protected void onmeasure(int widthmeasurespec, int heightmeasurespec) { int wrapcontentsize = 1000; int widthsize = measurespec.getsize(widthmeasurespec); int widthmode = measurespec.getmode(widthmeasurespec); int heightsize = measurespec.getsize(heightmeasurespec); int heightmode = measurespec.getmode(heightmeasurespec); if (widthmode == measurespec.unspecified && heightmode == measurespec.unspecified){ wrapcontentsize = (int) math.max(wrapcontentsize,mwatchradius+mwatchpadding); setmeasureddimension(wrapcontentsize,wrapcontentsize); }else { setmeasureddimension(widthsize,heightsize); } }</span>
重写ondraw方法
来到最关键真正画表盘时刻了。一步一步来,首先初始化我们的画笔(我的习惯,写一个initpaint方法)
<span style="font-size:14px;"> private void initpaint(){ mpaint = new paint(); mpaint.setantialias(true); mpaint.setcolor(color.white); mpaint.setstyle(paint.style.fill); }</span>
为了不显赘述,方便理解,我直接展示代码,在代码中解释
开画之前我们先将画笔移动到控件中心点位置,如下:
<span style="font-size:14px;">@override protected void ondraw(canvas canvas) { canvas.translate(getwidth()/2,getheight()/2); }</span>
第一步,画表盘
<span style="font-size:14px;"> /** * 画表盘 * @param canvas */ private void paintwatchboard(canvas canvas){ initpaint(); canvas.save(); canvas.drawcircle(0,0,mwatchradius,mpaint); //画圆盘 canvas.restore(); }</span>
注:每次画图之前都要先调用canvas.save()方法,保存画笔属性,画完之后要调用canvas.restore()方法,重置画笔属性
这里就不一一展示每次画完之后的效果图了。
第二步,画刻度+整点时间数字(刻度从12点方向开始画)
<span style="font-size:14px;"> /** * 画刻度及整点数字 * @param canvas */ private void paintscale(canvas canvas){ int linelength; //刻度线长度 canvas.save(); for (int i = 0;i<60;i++){ if (i%5 == 0){//整点刻度下画笔相关属性 mpaint.setstrokewidth(myutil.dip2px(getcontext(),1.5f)); mpaint.setcolor(mhourscalecolor); linelength = myutil.dip2px(getcontext(),8); canvas.drawline(0,-mwatchradius+mwatchscalepadding,0,-mwatchradius+mwatchscalepadding+linelength,mpaint); mpaint.setcolor(mtimetextcolor); mpaint.settextsize(mtimetextsize); canvas.drawtext(mtimes[i/5],-mtimetextsize/2,-mwatchradius+mwatchscalepadding + linelength+mtimetextsize,mpaint);//整点的位置标上整点时间数字 }else {//非整点刻度下画笔相关属性 mpaint.setstrokewidth(myutil.dip2px(getcontext(),0.8f)); mpaint.setcolor(mwatchscalecolor); linelength = myutil.dip2px(getcontext(),5); canvas.drawline(0,-mwatchradius+mwatchscalepadding,0,-mwatchradius+mwatchscalepadding+linelength,mpaint); } canvas.rotate(6);//每次画完一个刻度线,画笔顺时针旋转6度(360/60,相邻两刻度之间的角度差为6度) } canvas.restore(); }</span>
其中,整点数字我用了罗马数字来表示
<span style="font-size:14px;">private string[] mtimes = {"xii","ⅰ","ⅱ","ⅲ","ⅳ","ⅴ","ⅵ","ⅶ","ⅷ","ⅸ","ⅹ","xi"};</span>
第三步,画时针、分针、秒针以及其它修饰图
考虑到时针、分针和秒针大小长度各不一样,我这里特意定义了三支画笔来分别画时针、分针和秒针。
同样的,先初始化指针画笔:
<span style="font-size:14px;">/** * 初始化指针 */ private void initpointpaint(){ mhourpaint = new paint(); mhourpaint.setantialias(true); mhourpaint.setstyle(paint.style.fill); mhourpaint.setstrokewidth(16); mhourpaint.setcolor(mhourpointcolor); mminutepaint = new paint(); mminutepaint.set(mhourpaint); mminutepaint.setstrokewidth(12); mminutepaint.setcolor(mminutepointcolor); msecondpaint = new paint(); msecondpaint.set(mhourpaint); msecondpaint.setstrokewidth(7); msecondpaint.setcolor(msecondpointcolor); mendpointlength = mwatchradius/6; //(修饰部分)指针尾部长度,定义为表盘半径的六分之一 }</span>
画指针
<span style="font-size:14px;">/** * 画指针 * @param canvas */ private void paintpoint(canvas canvas){ initpointpaint(); calendar c = calendar.getinstance(); //取当前时间 int hour = c.get(calendar.hour_of_day); int minute = c.get(calendar.minute); int second = c.get(calendar.second); //绘制时针 canvas.save(); canvas.rotate(hour*30); canvas.drawline(0,0,0,-mhourpointlength,mhourpaint); canvas.drawline(0,0,0,mendpointlength,mhourpaint); canvas.restore(); //绘制分针 canvas.save(); canvas.rotate(minute*6); canvas.drawline(0,0,0,-mminutepointlength,mminutepaint); canvas.drawline(0,0,0,mendpointlength,mminutepaint); canvas.restore(); //绘制秒针 canvas.save(); canvas.rotate(second*6); canvas.drawline(0,0,0,-msecondpointlength,msecondpaint); canvas.drawline(0,0,0,mendpointlength,msecondpaint); canvas.restore(); }</span>
ok,该有的差不多都有了,直接在ondraw中调用吧
<span style="font-size:14px;">@override protected void ondraw(canvas canvas) { canvas.translate(getwidth()/2,getheight()/2); paintwatchboard(canvas); //画表盘 paintscale(canvas); //画刻度 paintpoint(canvas); //画指针 canvas.drawcircle(0,0,15,msecondpaint); //为了美观,也让表盘更接近我们显示生活中的样子,我在圆盘中心画了一个大红圆点装饰秒针 postinvalidatedelayed(1000); //每隔一秒钟画一次 }</span>
(⊙v⊙)嗯,自定义view大功告成,我们在布局文件里调用看下效果吧
<span style="font-size:14px;"><?xml version="1.0" encoding="utf-8"?> <relativelayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:zhusp="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/coloraccent"> <com.wondertek.propertyanimatordemo.watchview android:layout_width="wrap_content" android:layout_height="wrap_content" zhusp:timetextsize="20dp" zhusp:watchradius="150dp" zhusp:hourpointlength="80dp" zhusp:minutepointlength="100dp" zhusp:secondpointlength="115dp"/> </relativelayout></span>
最后我这里的静态效果是这样的: