慕客网视频传送门:http://www.imooc.com/learn/444


好久都没去慕客网了,虽然这次学习的是一个比较老的视频了,但是总比不学的好。(末尾附源码)

在学习之前,先来了解一波SurfaceView是什么,以及其作用:Android中的Surface和SurfaceView,以及SurfaceView 基础用法

之后,就可以开始视频的学习了。


视频的开始,对SurfaceView与一般的View进行了对比,这才前面的博客也有所提及:

SurfaceView继承自View
一般view是在UI线程中绘制自己,通过onDreaw方法
而SurfaceView则是在一个子线程中对自己进行绘制  优势:避免造成UI线程阻塞
在SurfaceView中包含一个专门用于绘制的Surface,Surface中包含一个Canvas

之后讲解了实现自定义SurfaceView的关键点,即获得Canvas用于绘制。
同时还需要注意在surfaceCreated 中开启一个子线程进行绘制,在surfaceDestoryed 在方法中暂停子线程中的绘制。

    实现SurfaceView的一般步骤:1、在构造方法中初始化holder,并进行相关设置,如:setFocusable(true);setFocusableInTouchMode(true);setKeepScreenOn(true);2、然后在surfaceCreated中去启动子线程,在surfaceDestroyed中暂停子线程中的绘制3、在子线程中实现绘制操作,绘制时先通过holder拿到Canvas,绘制结束后需要释放Canvas

以下就是通用代码的实现:

public class SurfaceViewImpl extends android.view.SurfaceView implements SurfaceHolder.Callback, Runnable {private SurfaceHolder mHolder;private Canvas mCanvas;private Thread mDrawThread;//用于绘制的线程private boolean isRunning;//作为子线程运行的控制开关public SurfaceViewImpl(Context context) {this(context, null);}public SurfaceViewImpl(Context context, AttributeSet attrs) {super(context, attrs);mHolder = getHolder();mHolder.addCallback(this);setFocusable(true);setFocusableInTouchMode(true);setKeepScreenOn(true);}@Overridepublic void surfaceCreated(SurfaceHolder holder) {isRunning = true;mDrawThread = new Thread(this);mDrawThread.start();}@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {isRunning = false;}@Overridepublic void run() {//不断进行绘制while (isRunning) {draw();}}private void draw() {/*** try-catch与判空的原因:* 当SurfaceView在主界面时,如果点击home或者back键,都会使得Surface销毁,* 但是在销毁之后,有可能已经进入该方法执行相应的逻辑了,因此需要对mCanvas进行判空,* 另外,由于Surface被销毁,但是线程却不是那么容易被关闭,继续执行draw something的操作,* 此时就有可能会抛出某些异常*/try {//首先拿到Canvas用于绘制mCanvas = mHolder.lockCanvas();if (mCanvas != null) {//TODO draw something}} catch (Exception e) {} finally {if (mCanvas != null)mHolder.unlockCanvasAndPost(mCanvas);}}
}

了解通用代码的实现后,就进入正题了,实现抽奖转盘(旋转的原理:以一定的时间间隔绘制转盘,但是每次绘制时转盘都会偏转固定的角度,连续起来,就像转盘在滚动),主要的代码如下,具体代码解释看注解(需要注意的是,在视频中有一个成员变量为mRadius,但本意是值转盘的直径,所以我这里改成了mDia):

public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {private SurfaceHolder mHolder;private Canvas mCanvas;private Thread mDrawThread;//用于绘制的线程private boolean isRunning;//作为子线程运行的控制开关//背景图private Bitmap mBcgBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bg2);//盘块的奖项private final String[] mAwardsName = getResources().getStringArray(R.array.AwardsName);//盘块的奖项图片idprivate final int[] mAwardsImgs = new int[]{R.drawable.danfan, R.drawable.ipad, R.drawable.f040, R.drawable.iphone, R.drawable.meizi, R.drawable.f040};//盘块的奖项图片private Bitmap[] mImgsBitmap;//盘块的数量private final int mItemCount = 6;//判断是否点击了停止按钮的标志private boolean isShouldEnd;//转盘的中心位置private int mCenter;//直接以padding值为准(或者取left、right、top、bottom中设置的最小的)private int mPadding;//整个盘块的范围private RectF mRange = new RectF();//整个盘快的直径private int mDia;//绘制盘块、文本的画笔private Paint mArcPaint, mTextPaint;//盘块滚动的速度(即转盘每隔mSpeed设置的角度重绘一次,但绘制的时间间隔不变)private double mSpeed;//起始角度(设置为float而非int,因为转盘存在某些逻辑会使得mStartAngle带有小数,如果为int会失去精度对指定奖项时的计算产生影响)private volatile float mStartAngle = 0;//可能会存在于两个线程,同时更新private final int platePartColor1 = 0xFFFFC300, platePartColor2 = 0xFFF17E01;public MySurfaceView(Context context) {this(context, null);}public MySurfaceView(Context context, AttributeSet attrs) {super(context, attrs);mHolder = getHolder();mHolder.addCallback(this);//可获得焦点setFocusable(true);setFocusableInTouchMode(true);//设置常量setKeepScreenOn(true);}//强制将转盘设置为正方形,并设置一些相关参数@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);int width = Math.min(getMeasuredWidth(), getMeasuredHeight());mPadding = getPaddingLeft();//半径mDia = width - mPadding * 2;//中心点mCenter = width / 2;setMeasuredDimension(width, width);}@Overridepublic void surfaceCreated(SurfaceHolder holder) {init();isRunning = true;mDrawThread = new Thread(this);mDrawThread.start();}private void init() {//初始化绘制盘快的画笔mArcPaint = new Paint();mArcPaint.setAntiAlias(true);mArcPaint.setDither(true);//初始化文本画笔mTextPaint = new Paint();mTextPaint.setColor(0xffffffff);mTextPaint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 20, getResources().getDisplayMetrics()));//设置文字大小//初始化盘快绘制的范围(mRadius已经减去了mPadding)mRange = new RectF(mPadding, mPadding, mDia + mPadding, mDia + mPadding);//初始化图片mImgsBitmap = new Bitmap[mItemCount];for (int i = 0; i < mItemCount; i++)mImgsBitmap[i] = BitmapFactory.decodeResource(getResources(), mAwardsImgs[i]);}@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {isRunning = false;}@Overridepublic void run() {//不断进行绘制while (isRunning) {long start = System.currentTimeMillis();draw();long end = System.currentTimeMillis();if (end - start < 100) {try {Thread.sleep(100 - (end - start));} catch (InterruptedException e) {e.printStackTrace();}}}}private void draw() {/*** try-catch与判空的原因:* 当SurfaceView在主界面时,如果点击home或者back键,都会使得Surface销毁,* 但是在销毁之后,有可能已经进入该方法执行相应的逻辑了,因此需要对mCanvas进行判空,* 另外,由于Surface被销毁,但是线程却不是那么容易被关闭,继续执行draw something的操作,* 此时就有可能会抛出某些异常*/try {//首先拿到Canvas用于绘制mCanvas = mHolder.lockCanvas();if (mCanvas != null) {//绘制背景mCanvas.drawColor(0xffffffff);mCanvas.drawBitmap(mBcgBitmap, null, new Rect(mPadding / 2, mPadding / 2, getMeasuredWidth() - mPadding / 2, getMeasuredWidth() - mPadding / 2), null);//绘制盘块float tmpAngle = mStartAngle;float sweepAngle = 360 / mItemCount;for (int i = 0; i < mItemCount; i++) {//1、绘制盘块mArcPaint.setColor((i % 2 == 0 ? platePartColor1 : platePartColor2));mCanvas.drawArc(mRange, tmpAngle, sweepAngle, true, mArcPaint);//2、绘制盘块上的文本(弧形的)Path path = new Path();path.addArc(mRange, tmpAngle, sweepAngle);//垂直偏移量取半径的1/6int vOffset = mDia / 8;//利用水平偏移量使文字水平居中/*** 圆的周长/盘块数量=每个盘块弧的长度* 之后再/2,即取一半* 最后减去文字的长度的一半(减文字的长度之前需要注意文字长度的值的一半小于等于上一步所求的值)*/int hOffset = (int) (mDia * Math.PI / mItemCount / 2 - mTextPaint.measureText(mAwardsName[i]) / 2);mCanvas.drawTextOnPath(mAwardsName[i], path, hOffset, vOffset, mTextPaint);//3、绘制盘块图标//设置图片的宽度为半径的1/8int imgWidth = mDia / 8;//求得弧度值(即图片所示的α)float angle = (float) ((tmpAngle + sweepAngle / 2) * Math.PI / 180);//求得图标中心点的坐标(而非图标左上角的坐标)int x = (int) (mCenter + mDia / 2 / 2 * Math.cos(angle));//mDia / 2 / 2->自定义去半径的一半int y = (int) (mCenter + mDia / 2 / 2 * Math.sin(angle));//确定图标的位置Rect rect = new Rect(x - imgWidth / 2, y - imgWidth / 2, x + imgWidth / 2, y + imgWidth / 2);mCanvas.drawBitmap(mImgsBitmap[i], null, rect, null);tmpAngle += sweepAngle;
//                    if(tmpAngle==360) tmpAngle=0;}//mSpeed设置为10角度,即转盘每隔10角度重绘一次mStartAngle += mSpeed;//如果点击了停止按钮,使得转盘缓缓停止if (isShouldEnd)mSpeed -= 1;if (mSpeed <= 0) {mSpeed = 0;isShouldEnd = false;}}} catch (Exception e) {e.printStackTrace();} finally {if (mCanvas != null)mHolder.unlockCanvasAndPost(mCanvas);}}/*** 启动转盘*/public void startDial() {mSpeed = 10;isShouldEnd = false;}/*** 启动转盘,指定停止时的奖项*/public void startDial(int index) {int angle=360/mItemCount;//计算指定index的中奖角度范围int from=270-(index+1)*angle;int end=from+angle;//设置停下来需要旋转的角度范围(每次都停在指定奖项所在的范围内,而不是每次都停在指定奖项的同一点)int targetFrom=1*360+from;//1*360+from中的2表示点击停止后再转一圈再停止int targetEnd=targetFrom+60;//为了实现上述所说的停在奖项对应区间的任意点//且停止时所对应的奖项是靠mSpeed的值决定的//所以需要使得mSpeed处于[targetFrom,targetEnd]所对应的值的区间(即[v1,v2])/*** mSpeed->0时停止转动且要考虑点击停止按钮时因惯性每次-1** 设mSpeed.v1对应targetFrom* 则有 (v1+0)*(v1+1)/2=targetFrom=>v1=(-1+Math.sqrt(1+8*targetFrom))/2(除去了负值的)*/float v1 = (float) ((-1 + Math.sqrt(1 + 8 * targetFrom)) / 2);float v2 = (float) ((-1 + Math.sqrt(1 + 8 * targetEnd)) / 2);mSpeed = v1+Math.random()*(v2-v1);isShouldEnd = false;}/*** 停止转盘*/public void stopDial() {isShouldEnd = true;mStartAngle=0;}/*** 判断是转盘是否正在转*/public boolean isRotating() {return mSpeed != 0;}public boolean isShouldEndFlag() {return isShouldEnd;}
}

附:
图一:盘块中文字水平偏移量的图解

图二:盘块图标中心点坐标的图解(非图标的左上角点坐标)

然后是在主界面实现点击按钮的逻辑:

    public void click(View view) {if (mDial.isRotating()) {view.setBackgroundResource(R.drawable.start);mDial.stopDial();} else {//如果点击了停止按钮,且转盘由于惯性还在旋转时,则不起作用if(!mDial.isShouldEndFlag()) {view.setBackgroundResource(R.drawable.stop);mDial.startDial(1);}}

番外:

对于奖项概率的设置:
包装一层,将奖项的概率与指定的数的范围区间对应起来,当落在某一区间则对应某一奖项,例如总的概率是1,总的数的范围区间为[0,1000],假设iPad的中奖概率为0.1,则iPad对应的区间为[start,start+100]的连续区间,其中start按实际情况自定义,然后再生产一个[0,1000]的随机数,如果落在了iPad对应的区间,则用控制停止时的中奖项的方法(前文中的startDial(int index)方法)指定停止在iPad奖项上


源码下载:http://download.csdn.net/download/qq_22804827/9772291
(使用的AS,module基本参数:
compileSdkVersion 25;buildToolsVersion “25.0.2”;minSdkVersion 21;targetSdkVersion 25)

Android实现抽奖转盘相关推荐

  1. android自定义抽奖转盘

    项目中有用到抽奖转盘,网上找的demo有些不合设计的要求(不能随意添加转盘中item的个数,不能以上层view滚动等),于是自己尝试写了个自定义的抽奖转盘,方便以在别的项目中更改使用,大致的效果如下图 ...

  2. android自定义抽奖,Android自定义view制作抽奖转盘

    本文实例为大家分享了Android自定义view制作抽奖转盘的具体代码,供大家参考,具体内容如下 效果图 TurntableActivity package com.bawei.myapplicati ...

  3. 详解与重构hyman《Android SurfaceView实战 打造抽奖转盘》

    详解与重构hyman<Android SurfaceView实战 打造抽奖转盘> 作者:邵励治 一.概述--关于SurfaceView您不得不知道的二三事 1.SurfaceView是干什 ...

  4. Android开发:抽奖转盘的实现

    故事的开始 最近有个需求,支付成功的时候加个抽奖轮盘.类似问卷星提交后的那种东西,翻了一下gayhub,下面给出自己的实现思路. 写在题前 这东西是在github上一个项目是拓展的.但是实现时间和下载 ...

  5. android自定义大转盘,android 代码绘制转盘抽奖的实现

    android 代码绘制转盘抽奖的实现 先上图 第一个是 整体的布局 xmlns:tools="http://schemas.android.com/tools" android: ...

  6. Android撸一个转盘抽奖

    Android撸一个转盘抽奖 前言 最近在学习的时候想做个积分转盘抽奖的功能,以前项目中使用过,但是是用的H5写的,但是我现在还不是太会写网页,就想算了,用Android写个吧!因为我这边的业务逻辑是 ...

  7. 九宫格抽奖转盘源码分析

         效果如上图所示,下面对其实现代码进行分析,看能不能破解其抽奖规则.需要引入jquery-1.8.3.min.js和images/9张图片. <!DOCTYPE html PUBLIC ...

  8. android自定义抽奖,Android简单实现圆盘抽奖界面

    闲来无事,做了一个简单的抽奖转盘的ui实现,供大家参考 package com.microchange.lucky; import android.content.Context; import an ...

  9. 安卓源码集合,视频播放器手机屏幕助手点融投资悬浮窗抽奖转盘

    1.android视频播放器源码 2.Android手机屏幕助手 3.android一个类似点融投资的app源码 4.android实现支付宝信用界面动画 5.android自定义悬浮窗 6.综合资讯 ...

最新文章

  1. Dataset:Big Mart Sales数据集的简介、下载、案例应用之详细攻略
  2. C#——判断数列是否排序
  3. python字符串转浮点数_Python | 打印不同的值(整数,浮点数,字符串,布尔值)...
  4. Kafka核心源码解析 - KafkaApis源码解析
  5. 树莓派超声波车牌识别系统
  6. SpringMVC框架第一天
  7. “锤死挣扎”的骁龙845+128GB旗舰机暴降1500元 仍冷清!
  8. hdu 3592 差分约束
  9. http://book.ifeng.com/lianzai/detail_2011_05/08/6243572_37.shtml
  10. 第三课:java开发hdfs
  11. Allure清除历史记录
  12. 如何把高版本unity资源导入Laya
  13. Excel如何批量删除公式只保留数值
  14. 无法打开FTP在 windows资源管理器中打开FTP站点解决方法
  15. swing-组件Collapse折叠面板2
  16. Maven在线仓库地址
  17. MySQL索引的介绍和使用
  18. 计算机c盘加容量,增加C盘空间,详细教您怎么增加C盘空间
  19. 基于JAVA家电售后管理系统计算机毕业设计源码+数据库+lw文档+系统+部署
  20. 学习笔记之——Semi-direct Visual Odometry (SVO)

热门文章

  1. android 酷狗demo_在Android上使用酷狗歌词API
  2. 《和声学教程》学习笔记(二):终止和终止四六和弦
  3. win10下快速搭建metasploitable3教程-简单版-已避坑
  4. 科大讯飞2020脑PET图像分析和疾病预测---单模型进决赛前五
  5. Reference Counted Smart Pointers
  6. 3.2 Hadoop简介
  7. 华为vrrp默认优先级_网关冗余VRRP
  8. ORACLE 按照指定的ID 顺序排序
  9. centos7是linux内核,在CentOS 7上用源代码编译最新的Linux内核
  10. OSChina 周日乱弹 ——我是胖娜丽莎