Android使用surfaceView自定義抽獎(jiǎng)大轉(zhuǎn)盤
使用surfaceView自定義抽獎(jiǎng)大轉(zhuǎn)盤
話不多說(shuō),先上效果圖

完整代碼地址歡迎start
實(shí)現(xiàn)思路以及過(guò)程
1、首先了解SurfaceView的基本用法,它跟一般的View不太一樣,采用的雙緩存機(jī)制,可以在子線程中繪制View,不會(huì)因?yàn)槔L制耗時(shí)而失去流暢性,這也是選擇使用SurfaceView去自定義這個(gè)抽獎(jiǎng)大轉(zhuǎn)盤的原因,畢竟繪制這個(gè)轉(zhuǎn)盤的盤塊,獎(jiǎng)項(xiàng)的圖片和文字以及轉(zhuǎn)動(dòng)都是靠繪制出來(lái)的,是一個(gè)比較耗時(shí)的繪制過(guò)程。
2、使用SurfaceView的一般模板樣式
一般會(huì)用到的成員變量
private SurfaceHolder mSurfaceHolder; private Canvas mCanvas;
初始化常亮
public SurfaceViewTemplate(Context context,AttributeSet attrs) {
super(context, attrs);
//初始化
mSurfaceHolder = getHolder();
mSurfaceHolder.addCallback(this);
//設(shè)置可獲得焦點(diǎn)
setFocusable(true);
setFocusableInTouchMode(true);
//這是常亮
setKeepScreenOn(true);
}
給SurfaceView添加callback實(shí)現(xiàn)其中三個(gè)方法
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
//surface創(chuàng)建的時(shí)候
mThread = new Thread(this);
//創(chuàng)建的時(shí)候就開(kāi)啟線程
isRunning = true;
mThread.start();
}
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
//變化的時(shí)候
}
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
//銷毀的時(shí)候 關(guān)閉線程
isRunning = false;
}
在子線程中定義一個(gè)死循環(huán)不斷的進(jìn)行繪制
@Override
public void run() {
//在子線程中不斷的繪制
while (isRunning) {
draw();
}
}
private void draw() {
try {
mCanvas = mSurfaceHolder.lockCanvas();
if (null != mCanvas) {
//避免執(zhí)行到這里的時(shí)候程序已經(jīng)退出 surfaceView已經(jīng)銷毀那么獲取到canvas為null
}
} catch (Exception e) {
//異??梢圆槐靥幚?
} finally {
//一定要釋放canvas避免泄露
mSurfaceHolder.unlockCanvasAndPost(mCanvas);
}
}
3、了解了SurfaceView的基本用法之后,接下來(lái)實(shí)現(xiàn)抽獎(jiǎng)轉(zhuǎn)盤
首先測(cè)量整個(gè)View的范圍,設(shè)置成正方形
@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;
//設(shè)置中心點(diǎn)
mCenter = width / 2;
//設(shè)置成正方形
setMeasuredDimension(width, width);
}
在SurfaceView創(chuàng)建的時(shí)候初始化畫筆矩形范圍等,見(jiàn)代碼
public void surfaceCreated(SurfaceHolder surfaceHolder) {
//初始化繪制Span的畫筆
mSpanPaint = new Paint();
mSpanPaint.setAntiAlias(true);
mSpanPaint.setDither(true);
//初始化繪制文本的畫筆
mTextPaint = new Paint();
mTextPaint.setTextSize(mTextSize);
mTextPaint.setColor(0Xffa58453);
//繪制圓環(huán)的畫筆
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];
//將獎(jiǎng)項(xiàng)的icon存儲(chǔ)為Bitmap
for (int i = 0; i < mSpanCount; i++) {
mImgIconBitmap[i] = BitmapFactory.decodeResource(getResources(), mPrizeIcon[i]);
}
//surface創(chuàng)建的時(shí)候
mThread = new Thread(this);
//創(chuàng)建的時(shí)候就開(kāi)啟線程
isRunning = true;
mThread.start();
}
接下來(lái)就是在開(kāi)啟的子線程中進(jìn)行繪制
@Override
public void run() {
//在子線程中不斷的繪制
while (isRunning) {
//保證繪制不低于50毫秒 優(yōu)化性能
long start = SystemClock.currentThreadTimeMillis();
draw();
long end = SystemClock.currentThreadTimeMillis();
if ((end - start) < 50) {
//休眠到50毫秒
SystemClock.sleep(50 - (end - start));
}
}
}
重點(diǎn)就在draw()方法中了下面就實(shí)現(xiàn)draw方法:
注意:避免mCanvas帶來(lái)的內(nèi)存泄漏
try {
mCanvas = mSurfaceHolder.lockCanvas();
if (null != mCanvas) {
//避免執(zhí)行到這里的時(shí)候程序已經(jīng)退出 surfaceView已經(jīng)銷毀那么獲取到canvas為null
//繪制背景
drawBg();
//繪制圓環(huán)
mCanvas.drawCircle(mCenter, mCenter, mRadius / 2 + mPadding / 20, mCirclePaint);
drawSpan();
}
} catch (Exception e) {
//異常可以不必處理
} finally {
//一定要釋放canvas避免泄露
mSurfaceHolder.unlockCanvasAndPost(mCanvas);
}
畫背景:
//繪制背景
private void drawBg() {
//背景設(shè)置為白色
mCanvas.drawColor(0xffffffff);
mCanvas.drawBitmap(mSpanBackground, null, new RectF(mPadding / 2, mPadding / 2, getMeasuredWidth() - mPadding / 2, getMeasuredHeight() - mPadding / 2), mSpanPaint);
}
參數(shù)解釋:
mSpanBackground背景圖片 new RectF(mPadding / 2, mPadding / 2, getMeasuredWidth() - mPadding / 2, getMeasuredHeight() - mPadding / 2) //限制背景在一個(gè)矩形范圍之類
繪制內(nèi)圓環(huán)
mCanvas.drawCircle(mCenter, mCenter, mRadius / 2 + mPadding / 20, mCirclePaint);
繪制中間八個(gè)盤塊
//定義一個(gè)變量臨時(shí)記錄開(kāi)始轉(zhuǎn)動(dòng)的角度
float tempAngle = mStartSpanAngle;
//每個(gè)盤塊所占的角度 CIRCLE_ANGLE = 360
float sweepAngle = CIRCLE_ANGLE / mSpanCount;
//循環(huán)繪制八個(gè)板塊
for (int i = 0; i < mSpanCount; i++) {
//設(shè)置每個(gè)盤塊畫筆的顏色
mSpanPaint.setColor(mSpanColor[i]);
//繪制扇形盤塊,第四個(gè)參數(shù)為true就是扇形否則就是弧形
mCanvas.drawArc(mRectCircleRange, tempAngle, sweepAngle, true, mSpanPaint);
//繪制文字
drawText(tempAngle, sweepAngle, mPrizeName[i]);
//繪制獎(jiǎng)項(xiàng)Icon
drawPrizeIcon(tempAngle, mImgIconBitmap[i]);
//改變角度
tempAngle += sweepAngle;
}
繪制文字
文字繪制成圓環(huán)形狀,根據(jù)path繪制文字
private void drawText(float tempAngle, float sweepAngle, String text) {
//繪制有弧度的文字 根據(jù)path繪制文字的路徑
Path path = new Path();
path.addArc(mRectRange, tempAngle, sweepAngle);
//讓文字水平居中 那繪制文字的起點(diǎn)位子就是 弧度的一半 - 文字的一半
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); //第三個(gè)四個(gè)參數(shù)是豎直和水平偏移量
}
繪制盤塊中的獎(jiǎng)品icon圖片
private void drawPrizeIcon(float tempAngle, Bitmap bitmap) {
//圖片的大小設(shè)置成直徑的1/8
int iconWidth = mRadius / 20;
//根據(jù)角度計(jì)算icon中心點(diǎn)
//角度計(jì)算 1度 == Math.PI / 180
double angle = (tempAngle + CIRCLE_ANGLE / mSpanCount / 2) * Math.PI / 180;
//根據(jù)三角函數(shù),計(jì)算中心點(diǎn)(x,y)
int x = (int) (mCenter + mRadius / 4 * Math.cos(angle));
int y = (int) (mCenter + mRadius / 4 * Math.sin(angle));
//定義一個(gè)矩形 限制icon位置
RectF rectF = new RectF(x - iconWidth, y - iconWidth, x + iconWidth, y + iconWidth);
mCanvas.drawBitmap(bitmap, null, rectF, null);
}
大致的繪制基本完成,重點(diǎn)就是通過(guò)改變開(kāi)始轉(zhuǎn)動(dòng)的角度讓轉(zhuǎn)盤轉(zhuǎn)動(dòng)起來(lái)。
mStartSpanAngle += mSpeed;//mSpeed的數(shù)值控制轉(zhuǎn)動(dòng)的速度
//聲明的一個(gè)結(jié)束標(biāo)志
if (isSpanEnd) {
mSpeed -= 1;
}
if (mSpeed <= 0) {
//停止旋轉(zhuǎn)了
mSpeed = 0;
isSpanEnd = false;
//定義一個(gè)回調(diào),監(jiān)控轉(zhuǎn)盤停止轉(zhuǎn)動(dòng)
mSpanRollListener.onSpanRollListener(mSpeed);
}
定義一個(gè)方法, 啟動(dòng)轉(zhuǎn)盤
//抽獎(jiǎng)轉(zhuǎn)盤重點(diǎn)就在這里,根據(jù)自己傳入的index控制抽到的獎(jiǎng)品
public void luckyStart(int index) {
//根據(jù)index控制停留的位置 angle 是每個(gè)獎(jiǎng)品所占的角度范圍
float angle = CIRCLE_ANGLE / mSpanCount;
//計(jì)算指針停留在某個(gè)index下的角度范圍HALF_CIRCLE_ANGLE=180度
float from = HALF_CIRCLE_ANGLE - (index - 1) * angle;
float end = from + angle;
//設(shè)置需要停下來(lái)的時(shí)候轉(zhuǎn)動(dòng)的距離 保證每次不停留的某個(gè)index下的同一個(gè)位置
float targetFrom = 4 * CIRCLE_ANGLE + from;
float targetEnd = 4 * CIRCLE_ANGLE + end;//最終停下來(lái)的位置在from-end之間,4 * CIRCLE_ANGLE 自定義要多轉(zhuǎn)幾圈
//計(jì)算要停留下來(lái)的時(shí)候速度的范圍,這里注意:涉及到等差數(shù)列的公式,因?yàn)樯婕暗阶屴D(zhuǎn)盤停止轉(zhuǎn)動(dòng)是使mSpeed-=1;所以它是從 vFrom--0等差遞減的一個(gè)過(guò)程,所以可以算出來(lái)vFrom,同理計(jì)算出vEnd
float vFrom = (float) ((Math.sqrt(1 + 8 * targetFrom) - 1) / 2);
float vEnd = (float) ((Math.sqrt(1 + 8 * targetEnd) - 1) / 2);
//在點(diǎn)擊開(kāi)始轉(zhuǎn)動(dòng)的時(shí)候 傳遞進(jìn)來(lái)的index值就已經(jīng)決定停留在那一項(xiàng)上面了
mSpeed = vFrom + Math.random() * (vEnd - vFrom);
isSpanEnd = false;
}
停止轉(zhuǎn)動(dòng)
public void luckStop() {
//在停止轉(zhuǎn)盤的時(shí)候強(qiáng)制吧開(kāi)始角度賦值為0 因?yàn)榭刂仆A糁付ㄎ恢玫慕嵌扔?jì)算是根據(jù)開(kāi)始角度為0計(jì)算的
mStartSpanAngle = 0;
isSpanEnd = true;
}
具體實(shí)現(xiàn)牽涉到一些數(shù)學(xué)知識(shí),可能講述不太清楚,不過(guò)上代碼就比較好了,直接看代碼會(huì)更加清晰,代碼中注釋很詳細(xì),防止以后自己再回頭看的時(shí)候忘記。歡迎訪問(wèn)github地址,查看完整代碼,可以根據(jù)自己的需求去修改,順便學(xué)習(xí)一下自定義view了解一下SurfaceView的用法。
地址:完整代碼地址歡迎start,如有發(fā)現(xiàn)問(wèn)題請(qǐng)多多指點(diǎn)互相學(xué)習(xí)交流。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android 實(shí)現(xiàn)九宮格抽獎(jiǎng)功能
- Android自定義view制作抽獎(jiǎng)轉(zhuǎn)盤
- Android自定義View實(shí)現(xiàn)抽獎(jiǎng)轉(zhuǎn)盤
- Android自定義View實(shí)現(xiàn)QQ運(yùn)動(dòng)積分轉(zhuǎn)盤抽獎(jiǎng)功能
- Android抽獎(jiǎng)輪盤的制作方法
- Android打造流暢九宮格抽獎(jiǎng)活動(dòng)效果
- Android中利用SurfaceView制作抽獎(jiǎng)轉(zhuǎn)盤的全流程攻略
- Android App中實(shí)現(xiàn)簡(jiǎn)單的刮刮卡抽獎(jiǎng)效果的實(shí)例詳解
- Android簡(jiǎn)單實(shí)現(xiàn)圓盤抽獎(jiǎng)界面
- Android實(shí)現(xiàn)九宮格抽獎(jiǎng)
相關(guān)文章
Android學(xué)習(xí)教程之日歷控件使用(7)
這篇文章主要為大家詳細(xì)介紹了Android學(xué)習(xí)教程之日歷控件操作代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11
Android動(dòng)態(tài)顯示當(dāng)前年月日時(shí)分秒系統(tǒng)時(shí)間(示例代碼)
這篇文章主要介紹了Android動(dòng)態(tài)顯示當(dāng)前年月日時(shí)分秒系統(tǒng)時(shí)間的示例代碼,需要的朋友可以參考下2017-05-05
Android源碼系列之深入理解ImageView的ScaleType屬性
Android源碼系列第一篇,這篇文章主要從源碼的角度深入理解ImageView的ScaleType屬性,感興趣的小伙伴們可以參考一下2016-06-06
Android實(shí)現(xiàn)單選與多選對(duì)話框的代碼
這篇文章主要介紹了Android實(shí)現(xiàn)單選與多選對(duì)話框的代碼,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-01-01
Android StrictMode運(yùn)行流程(推薦)
strictmode是android在 API9后引入的檢測(cè)影響app運(yùn)行流暢性的一種機(jī)制。這篇文章給大家介紹了android strictmode運(yùn)行流程,需要的朋友參考下吧2018-01-01
Flutter網(wǎng)絡(luò)請(qǐng)求Dio庫(kù)的使用及封裝詳解
本文主要介紹了Flutter網(wǎng)絡(luò)請(qǐng)求Dio庫(kù)的使用及封裝詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04
Android自定義View實(shí)現(xiàn)進(jìn)度條動(dòng)畫
這篇文章主要為大家詳細(xì)介紹了Android自定義View實(shí)現(xiàn)進(jìn)度條動(dòng)畫,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08
全面解析Android中對(duì)EditText輸入實(shí)現(xiàn)監(jiān)聽(tīng)的方法
這篇文章主要介紹了Android中對(duì)EditText輸入實(shí)現(xiàn)監(jiān)聽(tīng)的方法,包括一個(gè)仿iOS的帶清除功能的ClearEditText輸入框控件的詳細(xì)使用介紹,需要的朋友可以參考下2016-04-04
Android利用CountDownTimer實(shí)現(xiàn)倒計(jì)時(shí)功能 Android實(shí)現(xiàn)停留5s跳轉(zhuǎn)到登錄頁(yè)面
這篇文章主要為大家詳細(xì)介紹了Android利用CountDownTimer實(shí)現(xiàn)倒計(jì)時(shí)功能,Android實(shí)現(xiàn)停留5s跳轉(zhuǎn)到登錄頁(yè)面,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07

