編輯:關於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,如有發現問題請多多指點互相學習交流。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持本站。
Android 5.0 是 Google 於 2014 年 10 月 15 日(美國太平洋時間)發布的全新 Android 操作系統,英文名為Lollipop,翻譯過來就
在之前的Android超精准計步器開發-Dylan計步中的首頁用到了一個自定義控件,和QQ運動的界面有點類似,還有動畫效果,下面就來講一下這個View是如何繪制的。1.先
一些復雜語言如泰語、緬甸語、印地語,經常會看到一些帶有虛線圈圈字符。這是一種正常的處理機制。對於那些不能單獨存在的字符,在顯示時額外添加虛線圈,以提供對於這
oauth2.0授權界面,大致流程圖:前提准備:在新浪開放平台申請appkey和appsecret:http://open.weibo.com/. 熟悉oauth2.0協