自定义view————Android上的劳力士
2015-12-02 17:08
176 查看
来来来,先看看效果。
记得在学校学习的时候也写过一个C语言的手表,不过指针好像一直转不准,想想还是当年太年轻~囧~~
最近在研究canvas,看到某人写的例子里面也有只手表,今天便花了点时间自己重新写了一个,当是练手了。
例子本身没什么难的,初学者应该都看得懂。
首先拆分这个view:
手表的刻度和整点时刻是不变的,可以通过view的宽高直接画在中心
指针单独拿出来画,因为要计算角度
所以onDraw里面的内容是这样:
第5、6行是为了计算padding;因为手表是圆形,所以表应该是画在一个长宽均为表的直径的正方形内,所以第8行取了view长度、宽度中的一个较小值。
同时为了保证view的扩展性,手表的半径、指针的长度等都没有写固定值,而是根据view 的大小按比例来计算的(mStandard即作为整个view其他部分的长度参考),所以你就能看到上面那个gif中有三种不同尺寸的大小,除了我改了下整点时刻的字体大小外,没有改过其他任何地方的一句代码。
如果你愿意,当然也可以把字体做成适配view大小的,或者干脆写一个属性,在布局文件中赋值然后再代码中获取。
第14行中,有个drawAppearance(canvas);方法,主要是画表的刻度等。
可以看到,表的半径是上面那个mStandard值的0.8,第7行画表最外面的圆,第9行画圆心,12行开始画刻度。因为 drawCalibration(canvas, radius);方法中涉及到了canvas的旋转等操作,为了避免对后续的操作造成影响,所以在该方法的调用过后还原了那些操作,因此你可以看到有save、restore的调用。
画刻度的方法drawCalibration(canvas, radius);代码如下:
可以看到第7行使用了工具类,将sp转成px。做过自定义控件的朋友应该都知道,直接写数值是不科学的,这样在不同屏幕上面的显示效果很差,无法适配,应该使用sp转换成px的方式来设置字体的大小。dp同理,其实和布局文件中使用dp、sp是一个道理。
下面画刻度的过程就是每次旋转画布6°,重复60次的过程。
类似于旋转等的这些操作就像是虽然你的笔还是原来的地方画,但是画纸旋转或者移动了,所以你画的东西的方位就产生变化。
对于后面的过程,聪明的你想必已经无需我多做解释了。
画完刻度画指针,来看onDraw方法中的drawTime:
这里有一个画指针时间和一个画数字时间的,先看drawPointer(canvas, calendar);
第7行旋转了270°,为什么是270这个是试出来的,没仔细研究,有时间再看。
其他注释都写得很多了,没什么好说,就是每次旋转画完一个指针之后记得还原旋转的操作了,免得你转了几次之后自己都晕了。
下面是drawDigitalTime方法:
画数字时间之前先计算下字符串的宽度,如果宽度值比上面那个mStandard还大,那就不画那个数字时间了。
最后是view界面的刷新问题,一开始考虑在画指针的时候写个while循环然后每个多少秒刷新,但是会造成ui阻塞以及可能重叠等问题,后来想想其实直接调用invalidate就可以了,同时为了避免过度刷新,加了一个延迟刷新的操作,所以onDraw方法中18~21行中可以看到:
代码在这里
end.
记得在学校学习的时候也写过一个C语言的手表,不过指针好像一直转不准,想想还是当年太年轻~囧~~
最近在研究canvas,看到某人写的例子里面也有只手表,今天便花了点时间自己重新写了一个,当是练手了。
例子本身没什么难的,初学者应该都看得懂。
首先拆分这个view:
手表的刻度和整点时刻是不变的,可以通过view的宽高直接画在中心
指针单独拿出来画,因为要计算角度
所以onDraw里面的内容是这样:
[code]@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); this.mTotalWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight(); this.mTotalHeight = getMeasuredHeight() - getPaddingBottom() - getPaddingTop(); int smallerSize = this.mTotalWidth < this.mTotalHeight ? this.mTotalWidth : mTotalHeight; mStandard = smallerSize >> 1; //将画布移至中心 canvas.translate(getPaddingLeft() + this.mTotalWidth >> 1, getPaddingTop() + this.mTotalHeight >> 1); drawAppearance(canvas); drawTime(canvas); if (!mIsStop) { postInvalidateDelayed(INTERVAL_REFRESH); } }
第5、6行是为了计算padding;因为手表是圆形,所以表应该是画在一个长宽均为表的直径的正方形内,所以第8行取了view长度、宽度中的一个较小值。
同时为了保证view的扩展性,手表的半径、指针的长度等都没有写固定值,而是根据view 的大小按比例来计算的(mStandard即作为整个view其他部分的长度参考),所以你就能看到上面那个gif中有三种不同尺寸的大小,除了我改了下整点时刻的字体大小外,没有改过其他任何地方的一句代码。
如果你愿意,当然也可以把字体做成适配view大小的,或者干脆写一个属性,在布局文件中赋值然后再代码中获取。
第14行中,有个drawAppearance(canvas);方法,主要是画表的刻度等。
[code] /** * 画外观,包括刻度和整点时刻的文字 */ private void drawAppearance(Canvas canvas) { //表的半径为标准长度值的0.8,留点空隙 float radius = mStandard * 0.8f; //只描边,不填充 mPaint.setStyle(Paint.Style.STROKE); //画表的大圆 canvas.drawCircle(0, 0, radius, mPaint); //画表的圆心 canvas.drawCircle(0, 0, 5, mPaint); canvas.save(); drawCalibration(canvas, radius); canvas.restore(); }
可以看到,表的半径是上面那个mStandard值的0.8,第7行画表最外面的圆,第9行画圆心,12行开始画刻度。因为 drawCalibration(canvas, radius);方法中涉及到了canvas的旋转等操作,为了避免对后续的操作造成影响,所以在该方法的调用过后还原了那些操作,因此你可以看到有save、restore的调用。
画刻度的方法drawCalibration(canvas, radius);代码如下:
[code]/** * 画刻度 */ private void drawCalibration(Canvas canvas, float radius) { //整点时刻文字的大小 mPaint.setTextSize(DensityUtil.sp2px(mContext, TIME_NUMBER_SIZE)); //使文字居中 mPaint.setTextAlign(Paint.Align.CENTER); int calibrationHour = DensityUtil.dp2px(mContext, CALIBRATION_LENGTH_HOUR); int calibrationMinute = DensityUtil.dp2px(mContext, CALIBRATION_LENGTH_MINUTE); int diver = DensityUtil.dp2px(mContext, CALIBRATION_TEXT_DIVER); //使表的12点在正上方 canvas.rotate(210); for (int i = 0; i < 60; i++) { int length = calibrationMinute; if (i % 5 == 0) { length = calibrationHour; canvas.drawText(i / 5 + 1 + "", 0, radius - CALIBRATION_LENGTH_HOUR - diver, mPaint); } canvas.drawLine(0, radius, 0, radius - length, mPaint); canvas.rotate(6, 0, 0); } }
可以看到第7行使用了工具类,将sp转成px。做过自定义控件的朋友应该都知道,直接写数值是不科学的,这样在不同屏幕上面的显示效果很差,无法适配,应该使用sp转换成px的方式来设置字体的大小。dp同理,其实和布局文件中使用dp、sp是一个道理。
下面画刻度的过程就是每次旋转画布6°,重复60次的过程。
类似于旋转等的这些操作就像是虽然你的笔还是原来的地方画,但是画纸旋转或者移动了,所以你画的东西的方位就产生变化。
对于后面的过程,聪明的你想必已经无需我多做解释了。
画完刻度画指针,来看onDraw方法中的drawTime:
[code] private void drawTime(final Canvas canvas) { Calendar calendar = Calendar.getInstance(); canvas.save(); drawPointer(canvas, calendar); drawDigitalTime(canvas, calendar); canvas.restore(); }
这里有一个画指针时间和一个画数字时间的,先看drawPointer(canvas, calendar);
[code]/** * 画指针 */ private void drawPointer(Canvas canvas, Calendar calendar) { canvas.save(); canvas.rotate(270); int hour = calendar.get(Calendar.HOUR); int minute = calendar.get(Calendar.MINUTE); int second = calendar.get(Calendar.SECOND); //每小时所占的角度为30度 float degree = (hour + minute / 60.0f) * 30; canvas.save(); canvas.rotate(degree); canvas.drawLine(0, 0, mStandard * POINTER_LENGTH_HOUR, 0, mPaint); canvas.restore(); //每分钟所占的角度为6度 degree = (minute + second / 60.0f) * 6; canvas.save(); canvas.rotate(degree); canvas.drawLine(0, 0, mStandard * POINTER_LENGTH_MINUTE, 0, mPaint); canvas.restore(); //每秒钟所占的角度为6度 degree = second * 6; canvas.save(); canvas.rotate(degree); canvas.drawLine(0, 0, mStandard * POINTER_LENGTH_SECOND, 0, mPaint); canvas.restore(); canvas.restore(); }
第7行旋转了270°,为什么是270这个是试出来的,没仔细研究,有时间再看。
其他注释都写得很多了,没什么好说,就是每次旋转画完一个指针之后记得还原旋转的操作了,免得你转了几次之后自己都晕了。
下面是drawDigitalTime方法:
[code]/** * 画当前时间的数字显示 */ private void drawDigitalTime(Canvas canvas, Calendar calendar) { int hour = calendar.get(Calendar.HOUR_OF_DAY); int minute = calendar.get(Calendar.MINUTE); String time = String.format("北京时间:%s:%s", hour, minute); Rect bound = new Rect(); mPaint.getTextBounds(time, 0, time.length(), bound); if (mStandard > bound.width()) { canvas.drawText(time, 0, mTotalHeight >> 3, mPaint); } }
画数字时间之前先计算下字符串的宽度,如果宽度值比上面那个mStandard还大,那就不画那个数字时间了。
最后是view界面的刷新问题,一开始考虑在画指针的时候写个while循环然后每个多少秒刷新,但是会造成ui阻塞以及可能重叠等问题,后来想想其实直接调用invalidate就可以了,同时为了避免过度刷新,加了一个延迟刷新的操作,所以onDraw方法中18~21行中可以看到:
[code] if (!mIsStop) { postInvalidateDelayed(INTERVAL_REFRESH); }
代码在这里
end.
相关文章推荐
- Android 进程和线程 --多进程
- android Log图文详解(Log.v,Log.d,Log.i,Log.w,Log.e)
- Windows环境下Android Studio系列3—简单设置
- Android 自定义View (一)
- EditText设置cursor位置
- PullToRefreshListView 如何返回顶部
- Android4.4设置状态栏为透明
- Android开发中Activity属性设置
- Android引导指示层的制作 (ViewStub + SharePreference)
- Android Studio 教程(干货)
- Android之Activity的四种启动模式
- Android ListView 嵌套 ImageView,如何响应ImageView的点击和长按事件
- android 解决两个应用互相跳转,如果应用已经启动还是会重新打开应用的问题
- android 开发 程序中下载安装APK文件 问题汇总 解析程序包时出现问题
- Android动画
- Android Material Design:ViewPager与android.support.design.widget.TabLayout双向交互联动切换
- android使用GreenDao操作数据库
- Android onTouchEvent, onClick及onLongClick的调用机制
- Android Studio学习笔记4常用弹出窗口和解决输出中文乱码的问题
- android 打开软键盘