打造流暢九宮格抽獎活動


因爲company項目中需要做九宮格抽獎活動,以前都沒有做過類似的功能,雖然之前在瀏覽大神們的博客中,無意中也看到了好多關於抽獎的項目,但因爲項目中沒有需要,一直都沒有點擊進去看。這次不去看估計不行。直到公司計劃要做抽獎功能,才迫不得已上網查找demo


網上找了大半天,好不容易找到了幾個demo,下載下來,解壓縮包發現竟然裏面空空如也,只有幾張九宮格的圖片,害我白白浪費了幾個CSDN積分。後面在eoe網站那發現了一個demo,於是好開心,下載下來後馬上導入到工程中,運行看了效果,九宮格是出來了,但效果真不敢恭維,主要是運行不流暢。但我還是進去稍微看了一下demo,基本思路是這樣的:定義好九宮格界面,然後開啓子線程不斷循環修改狀態,再通過handler發送消息到主線程中修改界面(子線程不能直接修改界面)。


這個demo雖然功能上實現了,但不是我想要的效果,因爲我這一關都不能通過,到了產品那邊更加不用說了。那怎麼辦呢?


於是我想到了一個控件,叫做SurfaceView,做遊戲開發的同志們,應該對這個控件不陌生吧?首先介紹一下這個控件:
1.SurfaceView繼承於View,多用於遊戲開發中
2.可以直接在子線程中運行(其他UI控件都必須在主線程中運行的)。
3.一般的UI控件自定義時都是重寫onDraw方法,但在SurfaceView中是通過SurfaceHolder獲取Canvas來繪製圖形的

好了,來吧各位,先來看看效果圖:
九宮格

這樣,下面我開始根據我的想法,把自定義九宮格的步驟說一下。
步驟:
1.計算各位方塊的位置
2.繪製每個獎品的方塊(主要讓界面更加好看)
3.繪製獎品圖
4.計算旋轉方塊的下一步位置
5.繪製旋轉方塊
6.監聽點擊開始按鈕事件

主要核心技術:
SurfaceView,SurfaceHolder

OK,有了基本步驟,接下來就是根據步驟一步一步來進行了。
在開始繪製九宮格之前,我們先重寫onMeasure方法,主要是爲了讓九宮格成爲一個正方形,這樣看起來體驗更好,基本代碼如下:

public class LotteryView extends SurfaceView{

    /**
     * holder
     */
    private SurfaceHolder mHolder;


    private List<Prize>prizes;
    private boolean flags;    //抽獎開關

    private int lottery=6;   //設置中獎號碼

    private int current=2;   //抽獎開始的位置

    private int count=0;   //旋轉次數累計

    private int countDown;    //倒計次數,快速旋轉完成後,需要倒計多少次循環才停止

    //旋轉抽獎的方塊默認顏色
    private int transfer= 0xffff0000;

    private int MAX=50;   //最大旋轉次數
    /** 
     * 重新測量
     */  
    @Override  
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)  
    {  
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
        int width = Math.min(getMeasuredWidth(), getMeasuredHeight());  
        setMeasuredDimension(width, width);  
    }
}

SurfaceView一般不是通過重寫onDraw方法來繪製控件的,那麼怎麼獲取到Canvas呢?主要是通過SurfaceHolder監聽Callback事件來獲取的
基本代碼如下:

/**
     * holder
     */
    private SurfaceHolder mHolder;
    public LotteryView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mHolder = this.getHolder();
        //監聽CallBack
        mHolder.addCallback(this);
    }

    public LotteryView(Context context) {
        this(context,null);
    }

現在有了對象SurfaceHolder對象,我們就可以獲取到Canvas對象了,下面開始真正的繪製工作。

1.計算方塊的具體顯示位置
2.繪製每個獎品的方塊

    //繪製背景
    private void drawBg(Canvas canvas) {
        //清除已繪製的圖形
        canvas.drawColor(Color.WHITE, Mode.CLEAR);
        //獲取控件的寬度,因爲要繪製九宮格,所以要平局分成三列
        int width = getMeasuredWidth()/3;
        int x1=0;
        int y1=0;

        int x2=0;
        int y2=0;

        int len = (int) Math.sqrt(prizes.size());

        for(int x=0;x<len*len;x++){

            Prize prize = prizes.get(x);

            int index=x;
            x1=getPaddingLeft()+width*(Math.abs(index)%len);
            y1=getPaddingTop()+width*(index/len);

            x2=x1+width;
            y2=y1+width;
            Rect rect=new Rect(x1,y1,x2,y2);

            Paint paint=new Paint();
            //繪製方塊
            canvas.drawRect(rect, paint);
        }
    }

解析:prizes 是一個集合,裏面封裝了獎品的一些基本信息,x1,y1,x2,y2分別是繪製獎品容器正方形的左上頂點和右下頂點,
計算圖

通過觀察發現,每一個方塊位置都有一定的關係,即 x1=getPaddingLeft()+width*(Math.abs(index)%len);
y1=getPaddingTop()+width*(index/len);
x2=x1+width;
y2=y1+width;
有了這些點的關係,就可以通過canvas.drawRect(rect, paint);繪製出方塊了

3.繪製獎品圖

    //繪製獎品
    private void drawPrize(Canvas canvas) {
        int width = getMeasuredWidth()/3;
        int x1=0;
        int y1=0;

        int x2=0;
        int y2=0;

        int len = (int) Math.sqrt(prizes.size());

        for(int x=0;x<len*len;x++){

            Prize prize = prizes.get(x);

            int index=x;
            x1=getPaddingLeft()+width*(Math.abs(index)%len);
            y1=getPaddingTop()+width*(index/len);

            x2=x1+width;
            y2=y1+width;
            Rect rect=new Rect(x1+width/6,y1+width/6,x2-width/6,y2-width/6);
            prize.setRect(rect);
            canvas.drawBitmap(prize.getIcon(), null, rect, null);

        }
    }

通過了步驟1,2知道了方塊的位置關係,就可以輕鬆的根據這些關係繪製出獎品來,Rect rect=new Rect(x1+width/6,y1+width/6,x2-width/6,y2-width/6);是讓獎品比方塊縮小一些,這樣看起來會更自然一點。

4.計算旋轉方塊的下一步位置

    //下一步
    public int next(int position,int len){
        int current=position;
        if(current+1<len){
            return ++current;
        }

        if((current+1)%len==0&&current<len*len-1){
            return current+=len;
        }

        if(current%len==0){
            return current-=len;
        }

        if(current<len*len){
            return --current;
        }

        return current;
    }

position是當前旋轉方塊的位置,len是3

5.繪製旋轉方塊

    //繪製旋轉的方塊
    private void drawTransfer(Canvas canvas) {
        int width = getMeasuredWidth()/3;
        int x1;
        int y1;

        int x2;
        int y2;
        int len = (int) Math.sqrt(prizes.size());
        //得到下一步方塊的位置
        current=next(current, len);
        x1=getPaddingLeft()+width*(Math.abs(current)%len);
        y1=getPaddingTop()+width*((current)/len);

        x2=x1+width;
        y2=y1+width;

        Rect rect=new Rect(x1,y1,x2,y2);
        Paint paint=new Paint();
        paint.setColor(transfer);
        canvas.drawRect(rect, paint);
    }

6.監聽點擊開始按鈕事件

    private OnTransferWinningListener listener;

    public void setOnTransferWinningListener(OnTransferWinningListener listener){
        this.listener=listener;
    }

    public interface OnTransferWinningListener{
        /**
         * 中獎回調
         * @param position
         */
        void onWinning(int position);
    }
        @Override
    public boolean onTouchEvent(MotionEvent event) {
        handleTouch(event);
        return super.onTouchEvent(event);
    }
    /**
     * 觸摸
     * @param event
     */
    public void handleTouch(MotionEvent event) {


        Point touchPoint=new Point((int)event.getX()-getLeft(),(int)event.getY());
        switch(event.getAction()){
        case MotionEvent.ACTION_DOWN:
            Prize prize = prizes.get(Math.round(prizes.size())/2);
            if(prize.isClick(touchPoint)){
                if(!flags){
                    setStartFlags(true);
                    prize.click();
                }
            }
            break ;
        default:
            break ;
        }
    }

//控制旋轉
    private void controllerTransfer() {
        if(count>MAX){
            countDown++;
            SystemClock.sleep(count*5);
        }else{
            SystemClock.sleep(count*2);
        }

        count++;
        if(countDown>2){
            if(lottery==current){
                countDown=0;
                count=0;
                setStartFlags(false);

                if(listener!=null){
                    //切換到主線程中運行
                    post(new Runnable() {

                        @Override
                        public void run() {
                            listener.onWinning(current);
                        }
                    });

                }
            }
        }
    }

至此,基本的自定義工作已經差不多了,使用demo如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <com.example.test.LotteryView
        android:id="@+id/nl"
         android:layout_width="match_parent"
        android:layout_height="match_parent"
        />


</RelativeLayout>
public class HomeActivity extends Activity {

    LotteryView nl;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.act_home);

        nl=(LotteryView) findViewById(R.id.nl);

        int[]prizesIcon={R.drawable.danfan,R.drawable.meizi,R.drawable.iphone,R.drawable.f015,R.drawable.arrow,R.drawable.f040,R.drawable.ipad,R.drawable.spree_icon,R.drawable.spree_success_icon};
        final List<Prize>prizes=new ArrayList<Prize>();
        for(int x=0;x<9;x++){
            Prize lottery=new Prize();
            lottery.setId(x+1);
            lottery.setName("Lottery"+(x+1));
            Bitmap bitmap = BitmapFactory.decodeResource(getResources(), prizesIcon[x]);
            lottery.setIcon(bitmap);
            if((x+1)%2==0){
                lottery.setBgColor(0xff4fccee);
            }else if(x==4){
                lottery.setBgColor(0xffffffff);
            }else{
                lottery.setBgColor(0xff00ff34);
            }

            prizes.add(lottery);
        }
        nl.setPrizes(prizes);
        nl.setOnTransferWinningListener(new OnTransferWinningListener() {

            @Override
            public void onWinning(int position) {
                Toast.makeText(getApplicationContext(), prizes.get(position).getName(), Toast.LENGTH_SHORT).show();
            }
        });
    }
}

運行效果非常流暢

LotteryView整體demo:

package com.example.test;

import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.PorterDuff.Mode;
import android.graphics.Rect;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;
import android.view.SurfaceView;


public class LotteryView extends SurfaceView implements Callback{

    /**
     * holder
     */
    private SurfaceHolder mHolder;


    private List<Prize>prizes;
    private boolean flags;

    private int lottery=6;   //設置中獎號碼

    private int current=2;   //抽獎開始的位置

    private int count=0;   //旋轉次數累計

    private int countDown;    //倒計次數,快速旋轉完成後,需要倒計多少次循環才停止


    private int transfer= 0xffff0000;

    private int MAX=50;   //最大旋轉次數

    private OnTransferWinningListener listener;

    public void setOnTransferWinningListener(OnTransferWinningListener listener){
        this.listener=listener;
    }

    public interface OnTransferWinningListener{
        /**
         * 中獎回調
         * @param position
         */
        void onWinning(int position);
    }


    /**
     * 設置中獎號碼
     * @param lottery
     */
    public void setLottery(int lottery) {
        if(prizes!=null&&Math.round(prizes.size()/2)==0){
            throw new RuntimeException("開始抽獎按鈕不能設置爲中獎位置!");
        }
        this.lottery = lottery;
    }

    /**
     * 設置轉盤顏色
     * @param transfer
     */
    public void setTransfer(int transfer) {
        this.transfer = transfer;
    }

    /**
     * 設置獎品集合
     * @param prizes
     */
    public void setPrizes(List<Prize>prizes){
        this.prizes=prizes;
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        handleTouch(event);
        return super.onTouchEvent(event);
    }

    /**
     * 觸摸
     * @param event
     */
    public void handleTouch(MotionEvent event) {


        Point touchPoint=new Point((int)event.getX()-getLeft(),(int)event.getY());
        switch(event.getAction()){
        case MotionEvent.ACTION_DOWN:
            Prize prize = prizes.get(Math.round(prizes.size())/2);
            if(prize.isClick(touchPoint)){
                if(!flags){
                    setStartFlags(true);
                    prize.click();
                }
            }
            break ;
        default:
            break ;
        }
    }
    private class SurfaceRunnable implements Runnable{
        @Override
        public void run() {
            while(flags){
                Canvas canvas=null;
                try {
                    canvas = mHolder.lockCanvas();

                    drawBg(canvas);

                    drawTransfer(canvas);

                    drawPrize(canvas);

                    controllerTransfer();
                } catch (Exception e) {
                    e.printStackTrace();
                }finally{
                    //涓轟簡璁╂瘡嬈$粯鍒跺浘褰㈡椂鑳藉欏哄埄榪涜錛屾渶濂藉皢瑙i攣鏀懼埌寮傚父涓繘琛屽鐞嗭紝涔熷氨鏄錛屽鏋渃anvas涓嶄負絀猴紝閮藉皢鍏跺叧闂紝璁╀笅涓�嬈″驚鐜兘澶熼『鍒╄繘琛岀粯鍒�
                    if(canvas!=null)
                        mHolder.unlockCanvasAndPost(canvas);    
                }
            }
        }
    }

    //繪製背景
    private void drawBg(Canvas canvas) {
        canvas.drawColor(Color.WHITE, Mode.CLEAR);
        int width = getMeasuredWidth()/3;
        int x1=0;
        int y1=0;

        int x2=0;
        int y2=0;

        int len = (int) Math.sqrt(prizes.size());

        for(int x=0;x<len*len;x++){

            Prize prize = prizes.get(x);

            int index=x;
            x1=getPaddingLeft()+width*(Math.abs(index)%len);
            y1=getPaddingTop()+width*(index/len);

            x2=x1+width;
            y2=y1+width;
            Rect rect=new Rect(x1,y1,x2,y2);

            Paint paint=new Paint();
            paint.setColor(prize.getBgColor());
            canvas.drawRect(rect, paint);
        }
    }

    //繪製旋轉的方塊
    private void drawTransfer(Canvas canvas) {
        int width = getMeasuredWidth()/3;
        int x1;
        int y1;

        int x2;
        int y2;
        int len = (int) Math.sqrt(prizes.size());
        current=next(current, len);
        x1=getPaddingLeft()+width*(Math.abs(current)%len);
        y1=getPaddingTop()+width*((current)/len);

        x2=x1+width;
        y2=y1+width;

        Rect rect=new Rect(x1,y1,x2,y2);
        Paint paint=new Paint();
        paint.setColor(transfer);
        canvas.drawRect(rect, paint);
    }

    //控制旋轉
    private void controllerTransfer() {
        if(count>MAX){
            countDown++;
            SystemClock.sleep(count*5);
        }else{
            SystemClock.sleep(count*2);
        }

        count++;
        if(countDown>2){
            if(lottery==current){
                countDown=0;
                count=0;
                setStartFlags(false);

                if(listener!=null){
                    //切換到主線程中運行
                    post(new Runnable() {

                        @Override
                        public void run() {
                            listener.onWinning(current);
                        }
                    });

                }
            }
        }
    }

    public void setStartFlags(boolean flags){
        this.flags=flags;
    }

    //繪製獎品
    private void drawPrize(Canvas canvas) {
        int width = getMeasuredWidth()/3;
        int x1=0;
        int y1=0;

        int x2=0;
        int y2=0;

        int len = (int) Math.sqrt(prizes.size());

        for(int x=0;x<len*len;x++){

            Prize prize = prizes.get(x);

            int index=x;
            x1=getPaddingLeft()+width*(Math.abs(index)%len);
            y1=getPaddingTop()+width*(index/len);

            x2=x1+width;
            y2=y1+width;
            Rect rect=new Rect(x1+width/6,y1+width/6,x2-width/6,y2-width/6);
            prize.setRect(rect);
            canvas.drawBitmap(prize.getIcon(), null, rect, null);

        }
    }


    public void start() {
        setLottery(getRandom());
        ExecutorService service = Executors.newCachedThreadPool();
        service.execute(new SurfaceRunnable());
    }

    //獲取隨機中獎數,實際開發中一般中獎號碼是服務器告訴我們的
    private int getRandom(){
        Random r=new Random();
        int nextInt =r.nextInt(prizes.size());
        if(nextInt%(Math.round(prizes.size()/2))==0){
            //隨機號碼等於中間開始位置,需要繼續搖隨機號
            return getRandom();
        }
        return nextInt;
    }

    //下一步
    public int next(int position,int len){
        int current=position;
        if(current+1<len){
            return ++current;
        }

        if((current+1)%len==0&&current<len*len-1){
            return current+=len;
        }

        if(current%len==0){
            return current-=len;
        }

        if(current<len*len){
            return --current;
        }

        return current;
    }


    public LotteryView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mHolder = this.getHolder();
        mHolder.addCallback(this);
    }

    public LotteryView(Context context) {
        this(context,null);
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width,
            int height) {
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        Canvas canvas=null;
        try {
            canvas = mHolder.lockCanvas();
            drawBg(canvas);
            drawPrize(canvas);

            Prize prize = prizes.get(Math.round(prizes.size()/2));
            prize.setListener(new Prize.OnClickListener() {

                @Override
                public void onClick() {
                    start();
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            if(canvas!=null)
                mHolder.unlockCanvasAndPost(canvas);    
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        setStartFlags(false);
    }

    /** 
     * 重新測量
     */  
    @Override  
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)  
    {  
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
        int width = Math.min(getMeasuredWidth(), getMeasuredHeight());  
        setMeasuredDimension(width, width);  
    }
}

下載地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章