Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android使用surfaceView自定義抽獎大轉盤

Android使用surfaceView自定義抽獎大轉盤

編輯:關於Android編程

使用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,如有發現問題請多多指點互相學習交流。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持本站。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved