一、效果
幸运转盘
本篇属于demo性质,如果需要项目中使用,需要稍作修改
二、绘制原理
(1)公式推导
对于转盘而言,我们需要明确的是在初始状态下的每一个单元所对应的角度范围,另外我们赋予每个单元索引,最终推导出公式如下。
rangeDegree.start = perDegree/2 + (i-1) * perDegree; rangeDegree.end = perDegree/2 + (i) * perDegree;
(2)旋转坐标轴
当然,绘制的过程中我们需要修改坐标系,可以起到事半功倍的效果。
第一次将坐标系移动到(width/2,height/2),方便我们计算,这时就不要考虑相对位置等因素
第二次将坐标系旋转-90度,这样视觉上白色指针指向的位置就是X轴正方向
(3)计算旋转角度
3.1 一个要明白的道理,无论转盘曾今旋转过多少度,旋转到指定角度都以初始角度为参考点,而不是以上一次旋转的角度 (和三角函数有关 n*360 + targetDegree 的值和targetDegree的值是一样的)
//圆点起始角度 ,可以理解为index=0的起始角度,我们以index=0位参考点 float zeroStartDegree = -perDegree / 2; float endStartDegree = perDegree / 2; //从圆点计算,要旋转的角度 float targetDegree = (perDegree * (index - 1) + perDegree / 2); float rotateDegree = zeroStartDegree - targetDegree ;
3.2 保证顺时针旋转
while (rotateDegree<offsetDegree){ rotateDegree += 360; //防止逆时针旋转 (三角函数定理 n*360 + degree 和 degree最终夹角是等价的 ) } if(speedTime==0){ speedTime = 100L; } long count = duration / speedTime -1; //计算额外旋转圈数 while (count>=0){ rotateDegree += 360; //三角函数定理 n*360 + degree 和 degree最终夹角是等价的 count--; }
3.3 让指针指向区域内,而不是固定位置
float targetStartDegree = rotateDegree - perDegree/2; float targetEndDegree = rotateDegree + perDegree/2; float currentOffsetDegree = offsetDegree; // float targetOffsetDegree = (targetStartDegree + targetEndDegree)/2 ; //让指针指向有一定的随机性 float targetOffsetDegree = (float) (targetStartDegree + (targetEndDegree-targetStartDegree) * Math.random());
三、代码实现
public class LuckWheelView extends View { private final DisplayMetrics mDM; private TextPaint mArcPaint; private TextPaint mDrawerPaint; private int maxRadius; private float perDegree; private long duration = 5000L; private long speedTime = 1000L; //旋转一圈需要多少时间 public LuckWheelView(Context context) { this(context, null); } public LuckWheelView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public LuckWheelView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mDM = getResources().getDisplayMetrics(); initPaint(); setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { int index = (int) (Math.random() * items.size()); Log.d("LuckWheelView","setRotateIndex->" + items.get(index).text+", index="+index); setRotateIndex(index); } }); } private ValueAnimator animator = null; private void setRotateIndex(int index) { if(items==null || items.size()<= index){ return; } //圆点起始角度 ,可以理解为index=0的起始角度,我们以index=0位参考点 float zeroStartDegree = -perDegree / 2; float endStartDegree = perDegree / 2; //从圆点计算,要旋转的角度 float targetDegree = (perDegree * (index - 1) + perDegree / 2); float rotateDegree = zeroStartDegree - targetDegree ; while (rotateDegree<offsetDegree){ rotateDegree += 360; //防止逆时针旋转 (三角函数定理 n*360 + degree 和 degree最终夹角是等价的 ) } if(speedTime==0){ speedTime = 100L; } long count = duration / speedTime -1; //计算额外旋转圈数 while (count>=0){ rotateDegree += 360; //三角函数定理 n*360 + degree 和 degree最终夹角是等价的 count--; } float targetStartDegree = rotateDegree - perDegree/2; float targetEndDegree = rotateDegree + perDegree/2; float currentOffsetDegree = offsetDegree; // float targetOffsetDegree = (targetStartDegree + targetEndDegree)/2 ; //让指针指向有一定的随机性 float targetOffsetDegree = (float) (targetStartDegree + (targetEndDegree-targetStartDegree) * Math.random()); if(animator != null){ animator.cancel(); } animator = ValueAnimator .ofFloat(currentOffsetDegree,targetOffsetDegree) .setDuration(duration); animator.setInterpolator(new AccelerateDecelerateInterpolator()); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { offsetDegree = (float) animation.getAnimatedValue(); postInvalidate(); } }); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); offsetDegree = offsetDegree %360; } }); animator.start(); } private void initPaint() { // 实例化画笔并打开抗锯齿 mArcPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); mArcPaint.setAntiAlias(true); mArcPaint.setStyle(Paint.Style.STROKE); mArcPaint.setStrokeCap(Paint.Cap.ROUND); mDrawerPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); mDrawerPaint.setAntiAlias(true); mDrawerPaint.setStyle(Paint.Style.FILL); mDrawerPaint.setStrokeCap(Paint.Cap.ROUND); mDrawerPaint.setStrokeWidth(5); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); if (widthMode != MeasureSpec.EXACTLY) { widthSize = mDM.widthPixels / 2; } int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); if (heightMode != MeasureSpec.EXACTLY) { heightSize = widthSize / 2; } setMeasuredDimension(widthSize, heightSize); } private List<Item> items = new ArrayList<>(); { items.add(new Item(Color.BLUE,"Blue")); items.add(new Item(Color.MAGENTA,"MAGENTA")); items.add(new Item(Color.YELLOW,"YELLOW")); items.add(new Item(Color.RED,"RED")); items.add(new Item(Color.CYAN,"CYAN")); items.add(new Item(Color.BLACK,"BLACK")); } private RectF rectF = new RectF(); private float offsetDegree = 0; @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int width = getWidth(); int height = getHeight(); if (width == 0 || height == 0 || items==null || items.size()<=0) { perDegree = 0; return; } maxRadius = Math.min(width / 2, height / 2); rectF.left = -maxRadius; rectF.top = -maxRadius; rectF.right = maxRadius; rectF.bottom = maxRadius; int size = items.size(); int saveCount = canvas.save(); canvas.translate(width*1F/2,height*1F/2); //平移坐标轴到view中心点 canvas.rotate(-90); //逆时针旋转坐标轴 90度 canvas.drawCircle(0,0,maxRadius,mArcPaint); perDegree = 360*1F / size; // rangeDegree = start ->end // rangeDegree.start = perDegree/2 + (i-1) * perDegree; // rangeDegree.end = perDegree/2 + (i) * perDegree; for (int i=0;i<size;i++){ float startDegree = perDegree * (i - 1) + perDegree / 2 + offsetDegree; float endDegree = i * perDegree + perDegree/2 + offsetDegree; Item item = items.get(i); mDrawerPaint.setColor(item.color); // double startDegreeRandians = Math.toRadians(startDegree); //x1 // float x = (float) (maxRadius * Math.cos(startDegreeRandians)); // float y = (float) (maxRadius * Math.sin(startDegreeRandians)); // canvas.drawLine(0,0,x,y,mDrawerPaint); canvas.drawArc(rectF,startDegree,endDegree-startDegree,true,mDrawerPaint); } mDrawerPaint.setColor(Color.WHITE); canvas.drawLine(0,0,maxRadius/2,0,mDrawerPaint); canvas.restoreToCount(saveCount); } private static class Item{ Object tag; int color = Color.TRANSPARENT; String text; public Item(int color, String text) { this.color = color; this.text = text; } } }