Android 抽獎轉盤的實現
** 本篇文章已授權公眾號 guolin_blog (郭霖)獨家釋出 **
序言
最近需要實現一個抽獎的控制元件,我簡單搜尋了下,感覺要不很多細節地方沒有處理,要麼,根本就不能用。索性想自己實現個,從千圖網搜了下,挑選了個自己比較喜歡的出來,psd開啟後效果如下:
最終實現效果如下:
點選Go按鈕自動滾動:
隨手勢滾動:
實現的效果還不錯,因為是模擬器加錄製,畫面可能會有些卡頓,真機其實蠻順暢的,下面簡單的講講實現的步驟。
實現
1,繪製。
首先第一個我們要它給畫出來,但是要注意的就是Android所對應的座標系的問題。
for(int i= 0;i<6;i++){
if(i%2 == 0){
canvas.drawArc(rectF,angle,60,true,dPaint);
}else
{
canvas.drawArc(rectF,angle,60,true,sPaint);
}
angle += 60;
}
for(int i=0;i<6;i++){
drawIcon(width/2, height/2, radius, InitAngle, i, canvas);
InitAngle += 60;
}
for(int i=0;i<6;i++){
drawText(InitAngle+30,strs[i], 2*radius, textPaint, canvas,rectF);
InitAngle += 60;
}
其中有兩個地方需要注意下,第一個就是畫弧的地方第一個角度是起始角度,第二個是弧的角度,並不是結束的角度,所以是固定值60。第二個地方就是計算具體的x,y的值的時候要根據弧度去計算,不能根據角度。
- 2.使用屬性動畫讓其自動旋轉。
如果用SurfaceView去進行重繪旋轉存在一些問題,比如旋轉的角度不好控制,旋轉的速度不好控制。但是用屬性動畫,這個問題就很好解決了。
ValueAnimator animtor = ValueAnimator.ofInt(InitAngle,DesRotate);
animtor.setInterpolator(new AccelerateDecelerateInterpolator());
animtor.setDuration(time);
animtor.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int updateValue = (int) animation.getAnimatedValue();
InitAngle = (updateValue % 360 + 360) % 360;
ViewCompat.postInvalidateOnAnimation(RotatePan.this);
}
});
animtor.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
int pos = InitAngle / 60;
if(pos >= 0 && pos <= 3){
pos = 3 - pos;
}else{
pos = (6-pos) + 3;
}
if(l != null)
l.endAnimation(pos);
}
});
animtor.start();
用動畫最重要的就是,如何計算出結束動畫後的位置,那麼把最終旋轉的總角度%360°就得到最後一圈實際旋轉的角度,再除以60就得到了到底選擇了幾個位置,因為一個位置佔據60°,這應該不難理解。
@Override
public void onAnimationEnd(Animation animation) {
int pos = startDegree % 360 / 60;
if(pos >= 0 && pos <= 3){
pos = 3 - pos;
}else{
pos = (6-pos) + 3;
}
if(l != null)
l.endAnimation(pos);
}
但是問題又來了,Android所對應的座標系,0的位置應該是最底下,而指標的位置是在最上面,所以,我們結合上面的座標系來看,還需要處理下,如上面的程式碼所示。
3.利用Scroller和GestureDetector對手勢進行處理。
觸控事件的處理,最後到底允不允許轉盤隨手勢滑動呢?其實貌似做成這樣也就可以了,但是最後還是實現了下,用到了GestureDetector 和 Scroller這個類。其實做法有很多,首先獲取我們的滑動的距離,Math.sqrt(dx * dx + dy * dy),然後無非就是把這個距離轉換成我們需要的角度,你可以把這個距離當作我們的周長來處理,也可以把這個距離當作我們總的旋轉的角度來處理。之後就是隨著時間的流逝,不斷的重新整理我們的介面了。
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean consume = mDetector.onTouchEvent(event);
if(consume)
{
getParent().requestDisallowInterceptTouchEvent(true);
return true;
}
return super.onTouchEvent(event);
}
public void setRotate(int rotation){
rotation = (rotation % 360 + 360) % 360;
InitAngle = rotation;
ViewCompat.postInvalidateOnAnimation(this);
}
@Override
public void computeScroll() {
if(scroller.computeScrollOffset()){
setRotate(scroller.getCurrY());
}
super.computeScroll();
}
private class RotatePanGestureListener extends GestureDetector.SimpleOnGestureListener{
@Override
public boolean onDown(MotionEvent e) {
return super.onDown(e);
}
@Override
public boolean onDoubleTap(MotionEvent e) {
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
float centerX = (RotatePan.this.getLeft() + RotatePan.this.getRight())*0.5f;
float centerY = (RotatePan.this.getTop() + RotatePan.this.getBottom())*0.5f;
float scrollTheta = vectorToScalarScroll(distanceX, distanceY, e2.getX() - centerX, e2.getY() -
centerY);
int rotate = InitAngle -
(int) scrollTheta / FLING_VELOCITY_DOWNSCALE;
setRotate(rotate);
return true;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
float centerX = (RotatePan.this.getLeft() + RotatePan.this.getRight())*0.5f;
float centerY = (RotatePan.this.getTop() + RotatePan.this.getBottom())*0.5f;
float scrollTheta = vectorToScalarScroll(velocityX, velocityY, e2.getX() - centerX, e2.getY() -
centerY);
scroller.abortAnimation();
scroller.fling(0, InitAngle , 0, (int) scrollTheta / FLING_VELOCITY_DOWNSCALE,
0, 0, Integer.MIN_VALUE, Integer.MAX_VALUE);
return true;
}
}
private float vectorToScalarScroll(float dx, float dy, float x, float y) {
// get the length of the vector
float l = (float) Math.sqrt(dx * dx + dy * dy);
// decide if the scalar should be negative or positive by finding
// the dot product of the vector perpendicular to (x,y).
float crossX = -y;
float crossY = x;
float dot = (crossX * dx + crossY * dy);
float sign = Math.signum(dot);
return l * sign;
}
4.剩餘問題處理
還存在個問題,如果沒有手勢去操作轉盤,那我們很容易判斷它所旋轉的角度,但是有手勢的參與,我們很容易旋轉到轉盤中兩個分片中間的位置,那麼,我們在讓它旋轉之前,要簡單處理下,避免這種事情發生。
//TODO 為了每次都能旋轉到轉盤的中間位置
int offRotate = DesRotate % 360 % 60;
DesRotate -= offRotate;
DesRotate += 30;
這樣不管手勢怎麼操作,我最終都是旋轉到分片的中間位置了。
- 5.轉動到指定某個區域
/**
* 開始轉動
* @param pos 如果 pos = -1 則隨機,如果指定某個值,則轉到某個指定區域
*/
public void startRotate(int pos){
int lap = (int) (Math.random()*12) + 4;
int angle = 0;
if(pos < 0){
angle = (int) (Math.random() * 360);
}else{
int initPos = queryPosition();
if(pos > initPos){
angle = (pos - initPos)*60;
lap -= 1;
angle = 360 - angle;
}else if(pos < initPos){
angle = (initPos - pos)*60;
}else{
//nothing to do.
}
}
好多人加我QQ,問我怎麼轉動到指定位置,所以更新了下程式碼,上傳到github上去了,這裡做個日誌。如果傳的是 -1 則隨機轉動,如果傳的是大於0,則轉動到指定位置。
- 6.改變轉盤數量
<com.hr.nipuream.luckpan.view.RotatePan
android:id="@+id/rotatePan"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="78dp"
android:layout_centerHorizontal="true"
luckpan:pannum="8"
luckpan:names="@array/names"
luckpan:icons="@array/icons"
/>
<resources>
<string-array name="names">
<item>action</item>
<item>adventure</item>
<item>combat</item>
<item>moba</item>
<item>other</item>
<item>role</item>
<item>sports</item>
<item>words</item>
</string-array>
<string-array name="icons">
<item>action</item>
<item>adventure</item>
<item>combat</item>
<item>moba</item>
<item>other</item>
<item>role</item>
<item>sports</item>
<item>words</item>
</string-array>
</resources>
將pannum改為你想要的數量,然後names和icons定義在arrays.xml檔案中, 其中arrays.xml中的數量要和轉盤的數量一致。理論上可以改為轉盤數量為N的情況,但是綜合來看還是6個和8個轉盤數量最適宜,而且很多人也只問我怎麼改成8個轉盤,所以對這兩種情況做了適配,如果後期還有別的需求在加吧,github上程式碼已經更新,請重新下載。
程式碼
最後,程式碼已經上傳到github上去了。地址:https://github.com/Nipuream/LuckPan 歡迎Star
LuckPan.apk apk下載地址
相關文章
- [Android][抽獎轉盤]②---繪製轉盤的獎項文字Android
- iOS 抽獎輪盤效果實現思路iOS
- canvas 基礎系列(二)之實現大轉盤抽獎Canvas
- 抽獎轉盤的簡單思路
- 大轉盤抽獎系統
- 微信小程式:抽獎轉盤微信小程式
- 一步一步帶你實現一個canvas抽獎轉盤Canvas
- thinkphp5 抽獎幸運大轉盤PHP
- 按照獎品概率分佈抽獎的實現概率分佈
- Android抽獎功能Android
- JavaScript實現隨機抽獎效果JavaScript隨機
- 開發一個抽獎大轉盤 Lucky-spin
- javascript實現的簡單抽獎程式碼JavaScript
- Java實現隨機抽獎的方法有哪些Java隨機
- php實現抽獎的簡單概率演算法PHP演算法
- JavaScript簡單抽獎程式的實現及程式碼JavaScript
- js實現刮刮樂抽獎效果程式碼例項JS
- 商家如何製作幸運大轉盤抽獎小程式 快速吸粉?
- 用VB編寫抽獎程式 (轉)
- js抽獎JS
- vue3實現一個抽獎小專案Vue
- canvas 基礎系列(三)之實現九宮格抽獎Canvas
- php抽獎功能PHP
- 隨機抽獎隨機
- 手遊“抽獎介面”的思考
- 實戰實現一個h5轉盤抽獎頁面,談談程式碼實現,順便談一下優化和資料處理H5優化
- Oracle技術嘉年華 現場抽獎活動Oracle
- Canvas 基礎系列(零)之大轉盤九宮格刮刮卡抽獎外掛封裝Canvas封裝
- php中獎概率演算法,可用於大轉盤、九宮格、翻牌、刮刮卡等抽獎演算法PHP演算法
- canvas 基礎系列(一)之實現抽獎刮刮卡(橡皮擦)Canvas
- 中獎與抽獎次序無關
- 抽獎問題分析
- 如何模仿MX抽獎
- Android開發之DrawerLayout實現抽屜效果Android
- 能夠設定中獎概率的抽獎效果
- 【微信刮刮卡抽獎遊戲】教你輕鬆做個抽獎遊戲頁面遊戲
- IOS抽屜效果的實現iOS
- 樂智抽獎外掛