电商中常见有各种抽奖活动,如旋转幸运抽奖盘,本博客写的就是就是幸运抽奖盘。首先看看效果图。
效果图看起来不是很流程,将就看看就行,真机运行是很流畅的。
下面看看幸运抽奖盘的自定义实现LuckyPan,注释代码中都有,
package com.hai.widget;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import com.hai.R;
/**
* 幸运抽奖盘
* Created by 黄海 on 2/2/2016.
*/
public class LuckyPan extends SurfaceView implements SurfaceHolder.Callback, Runnable {
private SurfaceHolder mHolder;
private Canvas mCanvas;
//绘制盘块的画笔
private Paint mArcPaint;
//盘块的奖项
private String[] mStrs = new String[]{"单反相机", "IPAD", "恭喜发财", "IPHONE", "服装一套", "恭喜发财"};
//奖项的图片
private int[] mImgs = new int[]{R.mipmap.danfan, R.mipmap.ipad, R.mipmap.xiaolian, R.mipmap.iphone, R.mipmap.meizi, R.mipmap.f015};
//与图片对应的bitmap数组
private Bitmap[] mImgsBitmap;
private Bitmap mBgBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.bg2);
//盘块的颜色
private int[] mColor = new int[]{0xFFFFC300, 0xFFF17E01, 0xFFFFC300, 0xFFF17E01, 0xFFFFC300, 0xFFF17E01};
private int mItemCount = 6;
//绘制文本的画笔
private Paint mTextPaint;
private float mTextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 20, getResources().getDisplayMetrics());
//用于绘制线程
private Thread t;
//线程的控制开关
private boolean isRunning;
//整个盘块的范围
private RectF mRange = new RectF();
//角度
private volatile float mStartAngle = 0;
//整个盘块的直径
private int mRadius;
//转盘的中心位置
private int mCenter;
//这里我们的padding直接以paddingLeft为准
private int mPadding;
//滚动的速度
private double mSpeed = 0;
//是否点击了停止按钮
private boolean isShouldEnd;
public LuckyPan(Context context) {
super(context, null);
}
public LuckyPan(Context context, AttributeSet attrs) {
super(context, attrs);
mHolder = getHolder();
mHolder.addCallback(this);
//可获得焦点
setFocusable(true);
setFocusableInTouchMode(true);
setKeepScreenOn(true);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = Math.min(getMeasuredWidth(), getMeasuredHeight());
mPadding = getPaddingLeft();
mCenter = width / 2;
mRadius = width - mPadding * 2;
setMeasuredDimension(width, width);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
//初始化绘制盘块的画笔
mArcPaint = new Paint();
mArcPaint.setAntiAlias(true);
mArcPaint.setDither(true);
//初始化绘制文字的画笔
mTextPaint = new Paint();
mTextPaint.setColor(0xffffffff);
mTextPaint.setTextSize(mTextSize);
mRange = new RectF(mPadding, mPadding, mPadding + mRadius, mPadding + mRadius);
mImgsBitmap = new Bitmap[mItemCount];
for (int i = 0; i < mItemCount; i++) {
mImgsBitmap[i] = BitmapFactory.decodeResource(getResources(), mImgs[i]);
}
isRunning = true;
t = new Thread(this);
t.start();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
isRunning = false;
}
@Override
public void run() {
while (isRunning) {
long start = System.currentTimeMillis();
draw();
long end = System.currentTimeMillis();
if (end - start < 50) {
try {
Thread.sleep(50 - (end - start));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
private void draw() {
try {
mCanvas = mHolder.lockCanvas();
if (mCanvas != null) {
//绘制背景
drawBg();
float tmpAngle = mStartAngle;
float sweepAngle = 360 / mItemCount;
for (int i = 0; i < mItemCount; i++) {
mArcPaint.setColor(mColor[i]);
//绘制盘块
mCanvas.drawArc(mRange, tmpAngle, sweepAngle, true, mArcPaint);
//绘制文本
drawText(tmpAngle, sweepAngle, mStrs[i]);
//绘制icon
drawIcon(tmpAngle, mImgsBitmap[i]);
tmpAngle += sweepAngle;
}
mStartAngle += mSpeed;
//如果点击了停止按钮
if (isShouldEnd) {
mSpeed -= 1;
}
if (mSpeed <= 0) {
mSpeed = 0;
isShouldEnd = false;
}
}
} catch (Exception e) {
} finally {
if (mCanvas != null) {
mHolder.unlockCanvasAndPost(mCanvas);
}
}
}
/**
* 转盘旋转开始,同时设置停止位置
*
* @param index
*/
public void luckyStart(int index) {
float angle = 360 / mItemCount;
//计算每一向的中奖范围
//1->150-210
//0->210-270
float from = 270 - (index + 1) * 60;
float end = from + angle;
float targetFrom = 2 * 360 + from;
float targetEnd = 2 * 360 + end;
/**
* <pre>
* v1->0且每次减1
* (v1+0)*(v1+1)/2=targetFrom
* </pre>
*/
float v1 = (float) ((-1 + Math.sqrt(1 + 8 * targetFrom)) / 2);
float v2 = (float) ((-1 + Math.sqrt(1 + 8 * targetEnd)) / 2);
mSpeed = v1 + Math.random() * (v2 - v1);
isShouldEnd = false;
}
public void luckyEnd() {
mStartAngle = 0;
isShouldEnd = true;
}
//转盘是否在旋转
public boolean isStart() {
return mSpeed != 0;
}
public boolean isShouldEnd() {
return isShouldEnd;
}
/**
* 绘制icon
*
* @param tmpAngle
* @param bitmap
*/
private void drawIcon(float tmpAngle, Bitmap bitmap) {
//设置图片的宽度为直径1/8
int imgWidth = mRadius / 8;
//Math.PI/180,这里的单位为弧度
float angle = (float) ((tmpAngle + 360 / mItemCount / 2) * Math.PI / 180);
int x = (int) (mCenter + mRadius / 2 / 2 * Math.cos(angle));
int y = (int) (mCenter + mRadius / 2 / 2 * Math.sin(angle));
//确定那个图片的位置
Rect rect = new Rect(x - imgWidth / 2, y - imgWidth / 2, x + imgWidth / 2, y + imgWidth / 2);
mCanvas.drawBitmap(bitmap, null, rect, null);
}
/**
* 绘制每个盘块的文本
*/
private void drawText(float tmpAngle, float sweepAngle, String mStr) {
Path path = new Path();
path.addArc(mRange, tmpAngle, sweepAngle);
//利用水平偏移量让文字居中
float textWidth = mTextPaint.measureText(mStr);
mTextPaint.setTextAlign(Paint.Align.CENTER);
int hOffset = (int) (mRadius * Math.PI / mItemCount / 2 - textWidth / 2);
int vOffset = mRadius / 2 / 6;//垂直偏移量
mCanvas.drawTextOnPath(mStr, path, 0, vOffset, mTextPaint);
}
/**
* 绘制背景
*/
private void drawBg() {
mCanvas.drawColor(0xffffffff);
mCanvas.drawBitmap(mBgBitmap, null, new RectF(mPadding / 2, mPadding / 2, getMeasuredWidth() - mPadding / 2, getMeasuredHeight() - mPadding / 2), null);
}
}
activity的代码如下:
package com.hai;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.PersistableBundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.volley.AuthFailureError;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.ImageRequest;
import com.android.volley.toolbox.StringRequest;
import com.android.volley.toolbox.Volley;
import com.hai.widget.LuckyPan;
import java.io.File;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.PriorityBlockingQueue;
public class MyActivity extends AppCompatActivity {
private LuckyPan mLuckyPan;
private ImageView mStartBtn;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.luckypan);
mLuckyPan = (LuckyPan) findViewById(R.id.id_luckyPan);
mStartBtn = (ImageView) findViewById(R.id.id_start_btn);
mStartBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!mLuckyPan.isStart()) {
mLuckyPan.luckyStart(0);
mStartBtn.setImageResource(R.mipmap.stop);
} else {
if (!mLuckyPan.isShouldEnd()) {
mLuckyPan.luckyEnd();
mStartBtn.setImageResource(R.mipmap.start);
}
}
}
});
}
}
布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/lay"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.hai.widget.LuckyPan
android:id="@+id/id_luckyPan"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:padding="20dp" />
<ImageView
android:id="@+id/id_start_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@mipmap/start" />
</RelativeLayout>
至此实现幸运抽奖盘的代码已经结束。luckypan的实现难点颇多,如文字的绘制、文字的位置控制和图片的绘制和居中等等。对于在自定义veiw方面不太深入的同学,有很多的学习之处。
最后补充下,本幸运抽奖盘的代码是借鉴zhy大神的代码完成,原作者视频解说地址http://www.imooc.com/view/444
luckypan是继承与SurfaceView的,因此这里附上继承SurfaceView的一般重写模版
package com.hai.widget;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
/**
* SurfaceViewTemplate的一般重写模版
* Created by 黄海 on 2/2/2016.
*/
public class SurfaceViewTemplate extends SurfaceView implements SurfaceHolder.Callback, Runnable {
private SurfaceHolder mHolder;
private Canvas mCanvas;
//用于绘制线程
private Thread t;
//线程的控制开关
private boolean isRunning;
public SurfaceViewTemplate(Context context) {
super(context, null);
}
public SurfaceViewTemplate(Context context, AttributeSet attrs) {
super(context, attrs);
mHolder = getHolder();
mHolder.addCallback(this);
//可获得焦点
setFocusable(true);
setFocusableInTouchMode(true);
setKeepScreenOn(true);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
isRunning = true;
t = new Thread(this);
t.start();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
isRunning = false;
}
@Override
public void run() {
while (isRunning) {
draw();
}
}
private void draw() {
try {
mCanvas = mHolder.lockCanvas();
if (mCanvas != null) {
}
} catch (Exception e) {
} finally {
}
}
}