前段时间看见朋友做了一个大转盘,效果很好,耐不住寂寞的我也操刀上阵,亲自实现一下这个抽奖,然后带个大奖回家给充气的gf。。如果你的工资不是很高,或者最近有点缺钱花,那么你就更应该好好来看看这个大转盘,因为我这个大转盘有相当丰厚奖品,只有想不到,没有中不到的大奖,不过兑换奖品就到相关中奖单位去领取,最终解释权也归中奖的相关单位。。
效果图你已经看到了,没错你没看错,你中了大奖了。。
先把代码贴出来
布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.apple.Custom.LuckActivity">
<com.example.apple.Custom.LuckyView
android:id="@+id/lucky"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp" />
<android.support.v7.widget.AppCompatEditText
android:id="@+id/end_text"
android:layout_gravity="center"
android:layout_width="50dp"
android:layout_height="wrap_content"
android:inputType="number"
android:text="3" />
<Button
android:id="@+id/bt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:padding="10dp"
android:text="开始" />
</LinearLayout>
activity里面没啥东西
final LuckyView mlucky = (LuckyView) findViewById(R.id.lucky);
final AppCompatEditText editText = (AppCompatEditText) findViewById(R.id.end_text);
findViewById(R.id.bt).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String text = editText.getText().toString();
try {
mlucky.start(Integer.parseInt(text));
} catch (Exception e) {
}
}
});
重点都在这里,自定义view里面
public class LuckyView extends View {
final String TAG = this.getClass().getSimpleName();
int width, hight, mRadius, centerX, centerY, startAngle = 0, size;
private Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint mArcPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
Paint bgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
Paint bgIndcator = new Paint(Paint.ANTI_ALIAS_FLAG);
float textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 15f, getResources().getDisplayMetrics());
int[] icons = {R.mipmap.ic_launcher, R.mipmap.ic_launcher, R.mipmap.ic_launcher, R.mipmap.ic_launcher, R.mipmap.ic_launcher, R.mipmap.ic_launcher};
Bitmap[] bitmaps = null;
int[] mArcColors = {0xFFFFC300, 0XFFF17E01, 0xFFFFC300, 0XFFF17E01, 0xFFFFC300, 0XFFF17E01};
String[] titles = {"中国银行", "建设银行", "农业银行", "工商银行", "邮政", "成都银行"};
RectF mRectf = new RectF();
private boolean isStart;
double speed = 0;
private int sweepAngle;
private int tagetCenter;
public LuckyView(Context context) {
this(context, null);
}
public LuckyView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public LuckyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
initBitmap();
}
public void setIcons(int[] icons, int[] colors, String[] titles) {
this.icons = icons;
this.mArcColors = colors;
this.titles = titles;
initBitmap();
}
private void initBitmap() {
if (icons != null && icons.length > 2) {
bitmaps = new Bitmap[icons.length];
size = icons.length;
for (int i = 0; i < icons.length; i++) {
bitmaps[i] = BitmapFactory.decodeResource(getResources(), icons[i]);
}
}
}
private void init() {
mTextPaint.setDither(true);
mTextPaint.setTextSize(textSize);
mTextPaint.setColor(Color.WHITE);
mArcPaint.setDither(true);
bgIndcator.setColor(Color.RED);
bgIndcator.setStrokeCap(Paint.Cap.ROUND);
bgIndcator.setStrokeWidth(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, getResources().getDisplayMetrics()));
bgPaint.setColor(Color.BLACK);
bgPaint.setStyle(Paint.Style.STROKE);
bgPaint.setStrokeWidth(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, getResources().getDisplayMetrics()));
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int mode = MeasureSpec.getMode(heightMeasureSpec);
width = MeasureSpec.getSize(widthMeasureSpec);
hight = MeasureSpec.getSize(heightMeasureSpec);
if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.UNSPECIFIED) {
hight = width;
}
mRadius = (width - getPaddingLeft() - getPaddingRight()) / 2;
centerX = width / 2;
centerY = hight / 2;
mRectf.set(getPaddingLeft(), getPaddingTop(),
width - getPaddingRight(),
hight - getPaddingBottom());
setMeasuredDimension(width, hight);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.restore();
if (icons != null && icons.length > 2 && mArcColors != null && mArcColors.length > 0) {
drowBG(canvas);
luckydraw(canvas);
drawIndecator(canvas);
}
}
public void start(int index) {
if (index <= 0 || index > size) {
return;
}
int tagetFrom = 360 + 270 - index * sweepAngle;
tagetCenter = tagetFrom + sweepAngle / 2;
speed =(float) (Math.sqrt(1 * 1 + 8 * 1 * tagetCenter) - 1) / 2;
startAngle = 0;
Log.e(TAG, "start: 目标 " + tagetCenter);
if (isStart) {
isStart = false;
handler.sendEmptyMessage(2);
} else {
isStart = true;
handler.sendEmptyMessage(1);
}
}
@Override
public void invalidate() {
super.invalidate();
}
private void luckydraw(Canvas canvas) {
sweepAngle = 360 / size;
int tempAngle = startAngle;
for (int i = 0; i < size; i++) {
mArcPaint.setColor(mArcColors[i % mArcColors.length]);
canvas.drawArc(mRectf, tempAngle, sweepAngle, true, mArcPaint);
drawText(canvas, titles[i], tempAngle);
drawIcon(canvas, bitmaps[i], tempAngle);
tempAngle += sweepAngle;
}
}
private void drawIndecator(Canvas canvas) {
canvas.drawLine(centerX, centerY, centerX, centerY / 2, bgIndcator);
canvas.drawCircle(centerX, centerY, mRadius / 5, bgIndcator);
}
private void drawIcon(Canvas canvas, Bitmap bitmap, int tempAngle) {
int imgWidth = mRadius / 4;
float angle = (float) ((tempAngle + 360 / (2 * size)) * Math.PI / 180);
int x = (int) (centerX + mRadius * 2 / 3 * Math.cos(angle));
int y = (int) (centerY + mRadius * 2 / 3 * Math.sin(angle));
Rect rect = new Rect(x - imgWidth / 2, y - imgWidth / 2, x + imgWidth / 2, y + imgWidth / 2);
canvas.drawBitmap(bitmap, null, rect, null);
}
private void drawText(Canvas canvas, String title, int tempAngle) {
Path mPath = new Path();
mPath.addArc(mRectf, tempAngle, sweepAngle);
float w = mTextPaint.measureText(title);
int h = (int) ((2 * Math.PI * mRadius / size - w) / 2);
canvas.drawTextOnPath(title, mPath, h, mRadius / 8, mTextPaint);
}
private void drowBG(Canvas canvas) {
canvas.drawCircle(centerX, centerY, mRadius, bgPaint);
}
private Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case 1:
if (isStart) {
startAngle += speed;
handler.sendEmptyMessageDelayed(1, 100);
}
break;
case 2:
if (isStart) return false;
startAngle += speed;
if (speed > 0) {
speed--;
if (startAngle < tagetCenter) {
if (speed < 2) {
speed = 1;
}
}
}
if (speed <= 0) {
speed = 0;
isStart = false;
return isStart;
}
handler.sendEmptyMessageDelayed(2, 100);
break;
default:
}
invalidate();
return false;
}
});
代码都全部放出来了,是不是很简单,还是简单说下吧。。
private void drawText(Canvas canvas, String title, int tempAngle) {
Path mPath = new Path();
mPath.addArc(mRectf, tempAngle, sweepAngle);
float w = mTextPaint.measureText(title);
int h = (int) ((2 * Math.PI * mRadius / size - w) / 2);
canvas.drawTextOnPath(title, mPath, h, mRadius / 8, mTextPaint);
}
这里绘制圆弧文本, canvas.drawTextOnPath(),用到这个方法,这里有几个参数,第一个就不说了,第二个Path,这个文本需要按照path走,因为我们普通文本绘制出来都是水平的。第三和第四个就相当与坐标,在数学上圆的周长是2*Math.PI*R, 除以item的个数,就是每一个item的圆角对应的圆弧的长度,我们去圆弧的一半减去文本的一半,就是文本在圆弧上开始绘制的位置了。那么第四个就是这个绘制文本的path圆弧离最外面圆的距离,好了这俩个坐标有了,文本绘制就定位好了。
绘制背景圆弧就不说了,太简单了,那我们就来看看绘制icon
private void drawIcon(Canvas canvas, Bitmap bitmap, int tempAngle) {
int imgWidth = mRadius / 4;
float angle = (float) ((tempAngle + 360 / (2 * size)) * Math.PI / 180);
int x = (int) (centerX + mRadius * 2 / 3 * Math.cos(angle));
int y = (int) (centerY + mRadius * 2 / 3 * Math.sin(angle));
Rect rect = new Rect(x - imgWidth / 2, y - imgWidth / 2, x + imgWidth / 2, y + imgWidth / 2);
canvas.drawBitmap(bitmap, null, rect, null);
}
这里无非也就是计算坐标,tempAngle看全局代码就知道,是绘制圆弧的开始角度,我们就是控制圆弧的开始角度,来达到转盘转动,不断的变化,不断更新,就有了转动的动画效果。angle = (float) ((tempAngle + 360 / (2 * size)) * Math.PI / 180)这里除以2倍size,如果不这样,那么绘制的icon就在每次的圆弧开始位置。当然也可以在tempAngle+sweepAngle/2,效果一样。通过sin,cos计算坐标即可。。
绘制这块也就这几个点值得提一下,那么还有一个如何控制每次都让我们设置的大奖到达我们指定的地方呢,我这里处理的是按照数据学的等差数列计算,效果还将就吧
public void start(int index) {
if (index <= 0 || index > size) {
return;
}
int tagetFrom = 360 + 270 - index * sweepAngle;
tagetCenter = tagetFrom + sweepAngle / 2;
speed = (float) (Math.sqrt(1 * 1 + 8 * 1 * tagetCenter) - 1) / 2;
startAngle = 0;
Log.e(TAG, "start: 目标 " + tagetCenter);
if (isStart) {
isStart = false;
handler.sendEmptyMessage(2);
} else {
isStart = true;
handler.sendEmptyMessage(1);
}
}
private Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case 1:
if (isStart) {
startAngle += speed;
handler.sendEmptyMessageDelayed(1, 100);
}
break;
case 2:
if (isStart) return false;
startAngle += speed;
if (speed > 0) {
speed--;
if (startAngle < tagetCenter) {
if (speed < 2) {
speed = 1;
}
}
}
if (speed <= 0) {
speed = 0;
isStart = false;
return isStart;
}
handler.sendEmptyMessageDelayed(2, 100);
break;
default:
}
invalidate();
return false;
}
});
绘制圆弧是从右边水平开始,我们的中奖指针在竖直方向上半个圆上,那么如果要第一个item是大奖,我们就需要startAngle = 270,旋转270,这个时候选中了奖品但是选中的奖品的边缘,我现在的要求是在大奖的正中间,startAngle = 270-sweepAngle/2,让这个大奖少旋转半个sweepAngle。我们知道了我们最终要旋转到什么位置,要需要一个动画,而且我们也知道这个抽奖转动速度越来越慢,最后为0,这个时候就可以用等差数列,如:9,8,7,6,5,4,3,2,1,0。这几个数加起来 公式:和=(首项+末项)×项数÷2,这里和就是我们的目标角度tagetCenter,首项是0,末项 = 项数-1,好了,公式已经出来了,就这么玩吧