Android使用surfaceView自定义抽奖大转盘
使用surfaceview自定义抽奖大转盘
话不多说,先上效果图
完整代码地址欢迎start
实现思路以及过程
1、首先了解surfaceview的基本用法,它跟一般的view不太一样,采用的双缓存机制,可以在子线程中绘制view,不会因为绘制耗时而失去流畅性,这也是选择使用surfaceview去自定义这个抽奖大转盘的原因,毕竟绘制这个转盘的盘块,奖项的图片和文字以及转动都是靠绘制出来的,是一个比较耗时的绘制过程。
2、使用surfaceview的一般模板样式
一般会用到的成员变量
private surfaceholder msurfaceholder; private canvas mcanvas;
初始化常亮
public surfaceviewtemplate(context context,attributeset attrs) { super(context, attrs); //初始化 msurfaceholder = getholder(); msurfaceholder.addcallback(this); //设置可获得焦点 setfocusable(true); setfocusableintouchmode(true); //这是常亮 setkeepscreenon(true); }
给surfaceview添加callback实现其中三个方法
@override public void surfacecreated(surfaceholder surfaceholder) { //surface创建的时候 mthread = new thread(this); //创建的时候就开启线程 isrunning = true; mthread.start(); } @override public void surfacechanged(surfaceholder surfaceholder, int i, int i1, int i2) { //变化的时候 } @override public void surfacedestroyed(surfaceholder surfaceholder) { //销毁的时候 关闭线程 isrunning = false; }
在子线程中定义一个死循环不断的进行绘制
@override public void run() { //在子线程中不断的绘制 while (isrunning) { draw(); } } private void draw() { try { mcanvas = msurfaceholder.lockcanvas(); if (null != mcanvas) { //避免执行到这里的时候程序已经退出 surfaceview已经销毁那么获取到canvas为null } } catch (exception e) { //异常可以不必处理 } finally { //一定要释放canvas避免泄露 msurfaceholder.unlockcanvasandpost(mcanvas); } }
3、了解了surfaceview的基本用法之后,接下来实现抽奖转盘
首先测量整个view的范围,设置成正方形
@override protected void onmeasure(int widthmeasurespec, int heightmeasurespec) { super.onmeasure(widthmeasurespec, heightmeasurespec); //直接控制span为正方形 int width = math.min(getmeasuredwidth(), getmeasuredheight()); mpadding = getpaddingleft(); //直径 mradius = width - mpadding * 2; //设置中心点 mcenter = width / 2; //设置成正方形 setmeasureddimension(width, width); }
在surfaceview创建的时候初始化画笔矩形范围等,见代码
public void surfacecreated(surfaceholder surfaceholder) { //初始化绘制span的画笔 mspanpaint = new paint(); mspanpaint.setantialias(true); mspanpaint.setdither(true); //初始化绘制文本的画笔 mtextpaint = new paint(); mtextpaint.settextsize(mtextsize); mtextpaint.setcolor(0xffa58453); //绘制圆环的画笔 mcirclepaint = new paint(); mcirclepaint.setantialias(true); mcirclepaint.setcolor(0xffdfc89c); //初始化span的范围 mrectrange = new rectf(mpadding, mpadding, mpadding + mradius, mpadding + mradius); mrectcirclerange = new rectf(mpadding * 3 / 2, mpadding * 3 / 2, getmeasuredwidth() - mpadding * 3 / 2, getmeasuredwidth() - mpadding * 3 / 2); //初始化bitmap mimgiconbitmap = new bitmap[mspancount]; //将奖项的icon存储为bitmap for (int i = 0; i < mspancount; i++) { mimgiconbitmap[i] = bitmapfactory.decoderesource(getresources(), mprizeicon[i]); } //surface创建的时候 mthread = new thread(this); //创建的时候就开启线程 isrunning = true; mthread.start(); }
接下来就是在开启的子线程中进行绘制
@override public void run() { //在子线程中不断的绘制 while (isrunning) { //保证绘制不低于50毫秒 优化性能 long start = systemclock.currentthreadtimemillis(); draw(); long end = systemclock.currentthreadtimemillis(); if ((end - start) < 50) { //休眠到50毫秒 systemclock.sleep(50 - (end - start)); } } }
重点就在draw()方法中了下面就实现draw方法:
注意:避免mcanvas带来的内存泄漏
try { mcanvas = msurfaceholder.lockcanvas(); if (null != mcanvas) { //避免执行到这里的时候程序已经退出 surfaceview已经销毁那么获取到canvas为null //绘制背景 drawbg(); //绘制圆环 mcanvas.drawcircle(mcenter, mcenter, mradius / 2 + mpadding / 20, mcirclepaint); drawspan(); } } catch (exception e) { //异常可以不必处理 } finally { //一定要释放canvas避免泄露 msurfaceholder.unlockcanvasandpost(mcanvas); }
画背景:
//绘制背景 private void drawbg() { //背景设置为白色 mcanvas.drawcolor(0xffffffff); mcanvas.drawbitmap(mspanbackground, null, new rectf(mpadding / 2, mpadding / 2, getmeasuredwidth() - mpadding / 2, getmeasuredheight() - mpadding / 2), mspanpaint); }
参数解释:
mspanbackground背景图片 new rectf(mpadding / 2, mpadding / 2, getmeasuredwidth() - mpadding / 2, getmeasuredheight() - mpadding / 2) //限制背景在一个矩形范围之类
绘制内圆环
mcanvas.drawcircle(mcenter, mcenter, mradius / 2 + mpadding / 20, mcirclepaint);
绘制中间八个盘块
//定义一个变量临时记录开始转动的角度 float tempangle = mstartspanangle; //每个盘块所占的角度 circle_angle = 360 float sweepangle = circle_angle / mspancount; //循环绘制八个板块 for (int i = 0; i < mspancount; i++) { //设置每个盘块画笔的颜色 mspanpaint.setcolor(mspancolor[i]); //绘制扇形盘块,第四个参数为true就是扇形否则就是弧形 mcanvas.drawarc(mrectcirclerange, tempangle, sweepangle, true, mspanpaint); //绘制文字 drawtext(tempangle, sweepangle, mprizename[i]); //绘制奖项icon drawprizeicon(tempangle, mimgiconbitmap[i]); //改变角度 tempangle += sweepangle; }
绘制文字
文字绘制成圆环形状,根据path绘制文字
private void drawtext(float tempangle, float sweepangle, string text) { //绘制有弧度的文字 根据path绘制文字的路径 path path = new path(); path.addarc(mrectrange, tempangle, sweepangle); //让文字水平居中 那绘制文字的起点位子就是 弧度的一半 - 文字的一半 float textwidth = mtextpaint.measuretext(text); float hoval = (float) ((mradius * math.pi / mspancount / 2) - (textwidth / 2)); float voval = mradius / 15;//竖直偏移量可以自定义 mcanvas.drawtextonpath(text, path, hoval, voval, mtextpaint); //第三个四个参数是竖直和水平偏移量 }
绘制盘块中的奖品icon图片
private void drawprizeicon(float tempangle, bitmap bitmap) { //图片的大小设置成直径的1/8 int iconwidth = mradius / 20; //根据角度计算icon中心点 //角度计算 1度 == math.pi / 180 double angle = (tempangle + circle_angle / mspancount / 2) * math.pi / 180; //根据三角函数,计算中心点(x,y) int x = (int) (mcenter + mradius / 4 * math.cos(angle)); int y = (int) (mcenter + mradius / 4 * math.sin(angle)); //定义一个矩形 限制icon位置 rectf rectf = new rectf(x - iconwidth, y - iconwidth, x + iconwidth, y + iconwidth); mcanvas.drawbitmap(bitmap, null, rectf, null); }
大致的绘制基本完成,重点就是通过改变开始转动的角度让转盘转动起来。
mstartspanangle += mspeed;//mspeed的数值控制转动的速度 //声明的一个结束标志 if (isspanend) { mspeed -= 1; } if (mspeed <= 0) { //停止旋转了 mspeed = 0; isspanend = false; //定义一个回调,监控转盘停止转动 mspanrolllistener.onspanrolllistener(mspeed); }
定义一个方法, 启动转盘
//抽奖转盘重点就在这里,根据自己传入的index控制抽到的奖品 public void luckystart(int index) { //根据index控制停留的位置 angle 是每个奖品所占的角度范围 float angle = circle_angle / mspancount; //计算指针停留在某个index下的角度范围half_circle_angle=180度 float from = half_circle_angle - (index - 1) * angle; float end = from + angle; //设置需要停下来的时候转动的距离 保证每次不停留的某个index下的同一个位置 float targetfrom = 4 * circle_angle + from; float targetend = 4 * circle_angle + end;//最终停下来的位置在from-end之间,4 * circle_angle 自定义要多转几圈 //计算要停留下来的时候速度的范围,这里注意:涉及到等差数列的公式,因为涉及到让转盘停止转动是使mspeed-=1;所以它是从 vfrom--0等差递减的一个过程,所以可以算出来vfrom,同理计算出vend float vfrom = (float) ((math.sqrt(1 + 8 * targetfrom) - 1) / 2); float vend = (float) ((math.sqrt(1 + 8 * targetend) - 1) / 2); //在点击开始转动的时候 传递进来的index值就已经决定停留在那一项上面了 mspeed = vfrom + math.random() * (vend - vfrom); isspanend = false; }
停止转动
public void luckstop() { //在停止转盘的时候强制吧开始角度赋值为0 因为控制停留指定位置的角度计算是根据开始角度为0计算的 mstartspanangle = 0; isspanend = true; }
具体实现牵涉到一些数学知识,可能讲述不太清楚,不过上代码就比较好了,直接看代码会更加清晰,代码中注释很详细,防止以后自己再回头看的时候忘记。欢迎访问github地址,查看完整代码,可以根据自己的需求去修改,顺便学习一下自定义view了解一下surfaceview的用法。
地址:完整代码地址欢迎start,如有发现问题请多多指点互相学习交流。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
推荐阅读
-
Android使用surfaceView自定义抽奖大转盘
-
Android自定义Toolbar使用方法详解
-
使用Android studio创建的AIDL编译时找不到自定义类的解决办法
-
Android自定义viewgroup 使用adapter适配数据(6)
-
在Android开发中使用自定义组合控件的例子
-
Android使用setCustomTitle()方法自定义对话框标题
-
Android自定义Toolbar使用方法详解
-
Android使用自定义控件HorizontalScrollView打造史上最简单的侧滑菜单
-
使用Android studio创建的AIDL编译时找不到自定义类的解决办法
-
Android中使用自定义ViewGroup的总结