前言

由于自己经常使用四川移动掌上营业厅,经常在大转盘抽奖,每次都在使用这个抽奖,突然最近在练习自定义View,而且以前也没有做过这个自定义抽奖转盘,所以就当是练练手,然而百度上随便一搜索就是一大把,各种各样的解决方案。虽然网上的解决方案很多,但是我觉得即使再简单我们也要自己撸一变才能变成自己的东西,因此有了此文。

国际惯例先看运行效果,怕你们跑了:

分析实现步骤:

从上面的效果图我们可以简单的分析下,总的我们拆解成四部分:

  • 背景层就是上图边缘的红色部分

  • 中间旋转的圆盘部分

  • 文字

  • 小图标

1.背景层部分实现:

背景层部分的话,有两种实现方式:

(1)画在最底层,相当于背景的形式;假如你在边缘有设计其他的样式,只需要在第二步在边缘设置合适的padding即可

(2)画在最上层,UI切图成一个圆环的形式;这里同样要设置一个合适的padding来满足需求。

2.中间旋转的盘块

Canvas中有个5个参数的方法叫:

drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter,@NonNull Paint paint)
复制代码
  • 参数一 oval:圆弧所在的椭圆对象。(所在的椭圆或者圆要跟oval内切)

  • 参数二 startAngle:圆弧的起始角度。

  • 参数三 sweepAngle:圆弧的角度。

  • 参数四 useCenter:是否显示半径连线,true表示显示圆弧与圆心的半径连线,false表示不显示;为false那么就只是一段圆弧,不会和中心店连接起来。

  • 参数五 paint:绘制时所使用的画笔。

只需要设置好起始角度和圆弧的角度,paint的颜色即可绘制出我们需要的圆盘。

3.文字

从效果图可以看出,我们的文字不在一条直线上,而是带有圆弧。因此这里就不能使用我们常用的drawText()方法,而是通过传入Path:

drawTextOnPath(@NonNull String text, @NonNull Path path, float hOffset,float vOffset, @NonNull Paint paint)复制代码
  • 参数一 text:需要画的文字

  • 参数二 path:我们需要画的文字的路径。这里怎么获得我们path的路径呢?

addArc(RectF oval, float startAngle, float sweepAngle)
复制代码

这个是Path的方法,添加一段圆弧路径。

  • 参数三 paint:画笔

4.小图标

小图标是比较麻烦的,我们这里是将每个图标都canvas的方法:

drawBitmap(@NonNull Bitmap bitmap, @Nullable Rect src, @NonNull RectF dst,@Nullable Paint paint)
复制代码
  • 参数一 bitmap:我们需要显示的图片

  • 参数二 Rect:对图片进行裁剪,如果传入null,则表示显示整个图片

  • 参数三 RectF:在canvas画布中我们需要显示的大小,RectF比图片大则图片放大,RectF比图片小则图片缩小

  • 参数四 Paint:我们的画笔,画bitmap的时候传入null即可。

实践步骤

1.绘制背景

   @Overrideprotected void onDraw(Canvas canvas) {//1.绘制背景canvas.drawBitmap(mBgBitmap, null, new RectF(mPadding / 2, mPadding / 2, getMeasuredWidth() - mPadding / 2, getMeasuredHeight() - mPadding / 2), null);}
复制代码

这里为了简单方便,直接使用一个背景图片来画,参数mPadding是我们在XML布局中设置padding值。 效果:

2.画圆盘的盘块:

   @Overrideprotected void onDraw(Canvas canvas) {//1.绘制背景canvas.drawBitmap(mBgBitmap, null, new RectF(mPadding / 2, mPadding / 2, getMeasuredWidth() - mPadding / 2, getMeasuredHeight() - mPadding / 2), null);//2.绘制盘块int tempAngle = (int) mStartAngle;float sweepAngle = 360 / mItems;for (int i = 0; i < mItems; i++) {//1.绘制盘块背景mArcPaint.setColor(ContextCompat.getColor(getContext(), colors[i]));canvas.drawArc(mRange, tempAngle, sweepAngle, true, mArcPaint);tempAngle += sweepAngle;}}
复制代码

mStartAngle是一个起始画圆盘的角度,360/盘块数 就是要的画一个盘块的角度,tempAngle 每个盘块的起始角度。

运行效果:

3.绘制文字

  @Overrideprotected void onDraw(Canvas canvas) {//1.绘制背景canvas.drawBitmap(mBgBitmap, null, new RectF(mPadding / 2, mPadding / 2, getMeasuredWidth() - mPadding / 2, getMeasuredHeight() - mPadding / 2), null);//2.绘制盘块int tempAngle = (int) mStartAngle;float sweepAngle = 360 / mItems;for (int i = 0; i < mItems; i++) {//1.绘制盘块背景mArcPaint.setColor(ContextCompat.getColor(getContext(), colors[i]));canvas.drawArc(mRange, tempAngle, sweepAngle, true, mArcPaint);//2.绘制盘块的文字Path path = new Path();path.addArc(mRange, tempAngle, sweepAngle);//通过水平偏移量使得文字居中  水平偏移量=弧度/2 - 文字宽度/2float textWidth = mTextPaint.measureText(prizeName[i]);float hOffset = (float) (mRadius * Math.PI / mItems / 2 - textWidth / 2);//垂直偏移量 = 半径/6float vOffset = mRadius / 2 / 6;canvas.drawTextOnPath(prizeName[i], path, hOffset, vOffset, mTextPaint);tempAngle += sweepAngle;}
复制代码

这里画出弧形的文字,前面说了主要是通过Path来获得一个弧形的路径,然后通过drawTextOnPath()方法,其中要注意两个参数hOffsetvOffset两个值,分别代表水平方向距离和垂直方向距离,垂直方向偏移量我们便于适配大小,使用的是圆盘的半径的1/6,水平方向偏移量=圆弧/2-文字宽度/2

运行效果:

4.绘制图片

这里我们先来解释下如何将图片放置到我们想要的位置,假设这里我讲图片放在每个盘块的中心的位置,也就是半径/2的位置。简单解释一下,我们中心点的坐标是知道的假设是(centerX,centerY),中心点到我们放置的盘块的中心的距离知= 半径/2 ,角度=tempAngle画盘块的起始角度+弧度sweepAngle/2,这三个参数有了之后,我们直接通过cos、sin可以分别得出每小图标中心点的坐标。

@Overrideprotected void onDraw(Canvas canvas) {//1.绘制背景canvas.drawBitmap(mBgBitmap, null, new RectF(mPadding / 2, mPadding / 2, getMeasuredWidth() - mPadding / 2, getMeasuredHeight() - mPadding / 2), null);//2.绘制盘块int tempAngle = (int) mStartAngle;float sweepAngle = 360 / mItems;for (int i = 0; i < mItems; i++) {//1.绘制盘块背景mArcPaint.setColor(ContextCompat.getColor(getContext(), colors[i]));canvas.drawArc(mRange, tempAngle, sweepAngle, true, mArcPaint);//2.绘制盘块的文字Path path = new Path();path.addArc(mRange, tempAngle, sweepAngle);//通过水平偏移量使得文字居中  水平偏移量=弧度/2 - 文字宽度/2float textWidth = mTextPaint.measureText(prizeName[i]);float hOffset = (float) (mRadius * Math.PI / mItems / 2 - textWidth / 2);//垂直偏移量 = 半径/6float vOffset = mRadius / 2 / 6;canvas.drawTextOnPath(prizeName[i], path, hOffset, vOffset, mTextPaint);//3.绘制盘块上面的IMG//约束下图片的宽度int imgWidth = mRadius / 8;//获取弧度float angle = (float) Math.toRadians(tempAngle + sweepAngle / 2);//将图片移动到圆弧中心位置float x = (float) (mCenter + mRadius / 2 / 2 * Math.cos(angle));float y = (float) (mCenter + mRadius / 2 / 2 * Math.sin(angle));//确认绘制的矩形RectF rectF = new RectF(x - imgWidth / 2, y - imgWidth / 2, x + imgWidth / 2, y + imgWidth / 2);Bitmap bitmap = BitmapFactory.decodeResource(getResources(), imgs[i]);canvas.drawBitmap(bitmap, null, rectF, null);tempAngle += sweepAngle;}复制代码

这里我们固定了一个图片的宽、高为直径/8,便于适配。这里同样是使用到了RectF,只要计算到四个边的距离可以了。

运行效果:

哈哈,到这里我们可以看到,我们抽奖转盘已经-完成了。中间在添加一个指示器,我们的圆盘就大功告成了。

额,不对,好像还要旋转的哇?

不知道大家有没有看出来这里的图片都是竖着放置的,如果有需求是要将我们的图片和我们的盘块的方向一致,这里简单提示下是需要同Matrix来旋转角度,使得我们的图片旋转

核心代码:

                  //旋转绘制的图片ArrayList<Bitmap> bitmaps = new ArrayList<>();for (int j = 0; j < imgs.length; j++) {//获取bitmapBitmap bitmap = BitmapFactory.decodeResource(getResources(), imgs[j]);int width = bitmap.getWidth();int height = bitmap.getHeight();Matrix matrix = new Matrix();//设置缩放值matrix.postScale(1f, 1f);//旋转的角度matrix.postRotate(sweepAngle * j);//获取旋转后的bitmapBitmap rotateBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);//将旋转过的图片保存到列表中bitmaps.add(rotateBitmap);}
复制代码

旋转的角度就是我们每个盘块的角度

5.圆盘旋转

常用的方式有两种:

  • 一种是一直重新绘制圆盘,改变起始角度。

  • 另一种是通过属性动画,也推荐的方式,简单,方便。

这里我就使用一种不常用的方式,就是一直重新绘制:

@Overrideprotected void onDraw(Canvas canvas) {//1.绘制背景canvas.drawBitmap(mBgBitmap, null, new RectF(mPadding / 2, mPadding / 2, getMeasuredWidth() - mPadding / 2, getMeasuredHeight() - mPadding / 2), null);//2.绘制盘块int tempAngle = (int) mStartAngle;float sweepAngle = 360 / mItems;for (int i = 0; i < mItems; i++) {//1.绘制盘块背景mArcPaint.setColor(ContextCompat.getColor(getContext(), colors[i]));canvas.drawArc(mRange, tempAngle, sweepAngle, true, mArcPaint);//2.绘制盘块的文字Path path = new Path();path.addArc(mRange, tempAngle, sweepAngle);//通过水平偏移量使得文字居中  水平偏移量=弧度/2 - 文字宽度/2float textWidth = mTextPaint.measureText(prizeName[i]);float hOffset = (float) (mRadius * Math.PI / mItems / 2 - textWidth / 2);//垂直偏移量 = 半径/6float vOffset = mRadius / 2 / 6;canvas.drawTextOnPath(prizeName[i], path, hOffset, vOffset, mTextPaint);//3.绘制盘块上面的IMG//约束下图片的宽度int imgWidth = mRadius / 8;//获取弧度float angle = (float) Math.toRadians(tempAngle + sweepAngle / 2);//将图片移动到圆弧中心位置float x = (float) (mCenter + mRadius / 2 / 2 * Math.cos(angle));float y = (float) (mCenter + mRadius / 2 / 2 * Math.sin(angle));//确认绘制的矩形RectF rectF = new RectF(x - imgWidth / 2, y - imgWidth / 2, x + imgWidth / 2, y + imgWidth / 2);Bitmap bitmap = BitmapFactory.decodeResource(getResources(), imgs[i]);canvas.drawBitmap(bitmap, null, rectF, null);tempAngle += sweepAngle;}if (start) {mStartAngle += mSpeed;//16ms之后刷新界面mHandler.postDelayed(new MyRunnable(), 16);mSpeed -= 1;if (mSpeed < 10) {mSpeed -= 0.5;}if (mSpeed < 3) {mSpeed -= 0.1;}if (mSpeed < 0) {mSpeed = 0;start = false;}}}
复制代码

通过一个标志位:start 是否点击了开始抽奖,如果点击了,我们就刷新一下界面,同时start=true,通过handle来发送消息再重新绘制界面;这里我们每绘制一次,就将我们的mSeep-1,当mSeep小于10的时候每次-0.5,小于3的时候-0.1,这样就达到了慢慢减速的效果,知道mSeep=0的时候就停止转动了,start=false,就停止转动了。

这样我们的自定View抽奖转盘就大功告成了。

最终的效果:

最后附上Demo地址:github.com/scorpioLt/L…

总结

首先这个自定义抽奖转盘,网上有很多的例子,我这里再详细的讲一遍,首先是自己练习下,其次是记录下自己的过程。也参考了网上的资料,值得一提的是,不要觉得某个事情很简单你就不动手自己做,只有你自己亲自动手做了一遍,才能变成你自己的东西,程序猿更加是如此,拒绝眼高手低,看着谁都会,自己做的时候就各种懵逼。

温馨提示:

我创建了一个技术交流群,如果有想加入的伙伴加我微信号【luotaosc】备注一下“加群”

另外关注公众号,还有一些个人收藏的视频:

后台回复“学习资源” ,获取学习视频。

文章不易,如果觉得写得好,扫码关注一下点个赞,是我最大的动力。

Android 详解自定义View抽奖转盘相关推荐

  1. android标尺自定义view,android尺子的自定义view——RulerView详解

    项目中用到自定义尺子的样式: 原效果为 因为跟自己要使用的view稍有不同 所以做了一些修改,修改的注释都放在代码中了,特此记录一下. 首先是一个自定义View: public class RuleV ...

  2. SVG 详解——自定义可点击的中国地图

    SVG 详解--自定义可点击的中国地图 SVG 定义 SVG 是一种图像文件格式,类似于 JPG.PNG.只不过 JPG 和 PNG 这种文件需要图像引擎加载,而 SVG 则是由画布来加载的. 它的英 ...

  3. RecyclerView详解 —— 自定义动画

    RecyclerView的强大之处相信大家已经体验到了,在上一篇RecyclerView详解 -- 自定义分割线我们学习了如何定义分割线,本篇将介绍如何自定义动画. Google为我们提供了一个默认的 ...

  4. 【转载】CodeWarrior IDE使用tips之prm链接文件详解(自定义存储器分区以及自定义RAM数据初始化与在RAM中运行函数)...

    CodeWarrior IDE使用tips之prm链接文件详解(自定义存储器分区以及自定义RAM数据初始化与在RAM中运行函数) 2017-08-19 胡恩伟 汽车电子expert成长之路 内容提要 ...

  5. Linux systemctl 详解自定义 systemd unit

    Linux systemctl 详解&自定义 systemd unit systemctl 序 大家都知道,我们安装了很多服务之后,使用 systemctl 来管理这些服务,比如开启.重启.关 ...

  6. Android中的自定义View以及绘图工具

    1.1自定义view的简介 为什么要使用自定义view 在Android开发中有很多业务场景,原生的控件是无法满足应用,并且经常也会遇到一个UI在多处 重复使用情况,那么就需要通过自定义View的方式 ...

  7. Android进阶之自定义View实战(二)九宫格手势解锁实现

    一.引言 在上篇博客Android进阶之自定义View实战(一)仿iOS UISwitch控件实现中我们主要介绍了自定义View的最基本的实现方法.作为自定义View的入门篇,仅仅介绍了Canvas的 ...

  8. android自定义抽奖,Android 自定义View 抽奖大转盘(2)

    这是转盘的第二个版本,添加了外围的圆圈 第一个demo在这儿可以找到 镇楼图 S171011-11370203.jpg 项目的本来来源来自于 添加外围的圆圈,主要由两部分组成 1.画小圆.2.圆盘位置 ...

  9. android 换行模式,Android进阶之自定义View(1)实现可换行的TextView

    今天来一起学习一下最简单的自定义view,自己动手写一个MyTextView,当然不会像系统的TextView那么复杂,只是实现一下TextView的简单功能,包括分行显示及自定义属性的处理,主要目的 ...

  10. Android开发,自定义View的学习合集

    转载自:http://blog.csdn.net/u011507982/article/details/51199644 自定义控件学习  https://github.com/GcsSloop/An ...

最新文章

  1. textureview 旋转90度后平铺_C++初级编程NOIP题:11H1537: 图像旋转
  2. 酒店叫醒系统服务器,酒店叫醒服务的流程
  3. unique函数_unique函数使用场景(一)
  4. Blum Integer的定义及举例
  5. Git使用教程:最详细、最浅显、一文读懂Git常用操作!
  6. mysql 查找相似数据_局部敏感哈希LSH(Locality-Sensitive Hashing)——海量数据相似性查找技术...
  7. Docker-mysql 安装
  8. Centos7搭建pptp一键安装脚本
  9. Spring源码全解析,帮你彻底学习Spring源码
  10. php ecshop二次开发,ecshop二次开发对ecshop系统框架分析
  11. 阿里云域名怎么注册和使用(新手教程)
  12. Android Layout inflate过程分析(1)
  13. linux qt 找不到 lgl,c ++ - Qt:找不到-lGL
  14. dblink导致存储过程报异常ORA-03113:通信通道文件尾 ORA-02063紧接着line(xxxdblink名称) ORA-06512在(xxxx)line 24
  15. 最全 MySQL主从同步与主主同步
  16. jquery 删除数组
  17. ConcurrentHashMap 的理解
  18. 白嫖京东读书专业版!附赠限量全国高校激活码,先到先得!
  19. 灵异事件之idea和金山词霸
  20. jquery 获取系统默认年份_js中获得当前时间是年份和月份

热门文章

  1. ios上传闪退 php,iOS应用上架后出现闪退原因浅析
  2. Flink Weekly | 每周社区动态更新
  3. android UncaughtExceptionHandler全局异常处理
  4. 国庆在家太无聊, 用Java爬了上千张小姐姐照片...
  5. CTO 要我把这份 MySQL 规范贴在工位上!
  6. 正在做鸡肋事情的你,准备好了奔赴下一个战场么?
  7. Scala学习01——Scala介绍
  8. python中常用的函数有哪些_python里常用的函数类型
  9. android 7.0添加菜单,Android 7.0 settings中添加/删除菜单
  10. oracle rman在线备份,Oracle之RMAN备份及还原