Android笔记自定义View之制作表盘界面

努力尽今夕,少年犹可夸。这篇文章主要讲述Android笔记自定义View之制作表盘界面相关的知识,希望能为你提供帮助。
前言
最近我跟自定义View杠上了, 甚至说有点上瘾到走火入魔了。身为菜鸟的我自然要查阅大量的资料, 学习大神们的代码, 这不, 前两天正好在郭神在微信公众号里推送一片自定义控件的文章——一步步实现精美的钟表界面。正适合我这种菜鸟来学习, 闲着没事, 我就差不多依葫芦画瓢也写了一个自定义表盘View, 现在纯粹最为笔记记录下来。先展示下效果图:

Android笔记自定义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>


最后我这里的静态效果是这样的:
Android笔记自定义View之制作表盘界面

文章图片


尾声

写完了, 最后来加点逼格
Android笔记自定义View之制作表盘界面

文章图片
, 学无止境, 循序渐进。既然选择了远方, 便只顾风雨兼程


参考资料: 自定义View新手实战-一步步实现精美的钟表界面

【Android笔记自定义View之制作表盘界面】


    推荐阅读