今天咱来做一个自定义的抽奖盘,设计图大致如下:
image宏洋大神之前做过自定义抽奖盘(https://blog.csdn.net/lmj623565791/article/details/41722441),当时他用的是surfaceView,因为surfaceView的绘制放在子线程,频繁的绘制不会造成ui线程卡死,但是转盘的旋转动画设计的比较复杂,需要各种去计算旋转的弧度以及几时开启动画几时停下动画旋转的速度怎样控制,现在几年过去Android手机的性能大大提高,view里面绘制一些频繁的动画也不会有太大问题,而旋转的动画可以直接用动画来实现,实现起来就大大简便不少。
说说设计思路:点击抽奖的是一张图片,下面的转盘为一个自定义view,里面分为6个区域,每个区域画出不同的背景,然后画上相应的图片和文字,就行了。看起来很简单(确实也不难),稍微复杂一点的地方就在于要抽奖到指定的奖品的时候需要计算一下旋转的角度。这个做到再说。
OK,下面开始code。
首先当然是确认布局为正方形,并且计算出中心点位置及转盘半径。重写onSizeChanged()方法。
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth=Math.min(w,h);
float strokeWidth= bgPaint.getStrokeWidth();
radius= (int) ((mWidth-getPaddingLeft()-strokeWidth)/2);
singleZoneAngle=360/imgIds.length;
centerX=mWidth/2;
centerY=centerX;
drawRect=new RectF(getPaddingLeft()+strokeWidth,getPaddingTop()+strokeWidth,
mWidth-getPaddingRight()-strokeWidth,mWidth-getPaddingBottom()-strokeWidth);
setMeasuredDimension(mWidth,mWidth);
}
然后就是onDraw()方法了。
@Override
protected void onDraw(Canvas canvas) {
//绘制背景
drawBackground(canvas);
//绘制其他
float tmpAngle=startAngle;
for (int i=0;i<imgIds.length;i++){
//绘制单个扇形区域颜色
drawSingleZoneBg(canvas,tmpAngle,i);
//绘制单个扇形文字
drawSingleZoneText(canvas,tmpAngle,i);
//绘制单个扇形图片
drawSingleZoneImg(canvas,tmpAngle,i);
if (tmpAngle>360){
tmpAngle=(tmpAngle+singleZoneAngle)%360;
}else {
tmpAngle=tmpAngle+singleZoneAngle;
}
Log.e("test","开始的tmpAngle="+tmpAngle);
}
}
/**
* 绘制区域背景
* @param tmpAngle
* @param zoneIndex
*/
private void drawSingleZoneBg(Canvas canvas,float tmpAngle, int zoneIndex) {
imgPaint.setColor(colors[zoneIndex]);
Log.e("test","zoneIndex="+zoneIndex);
Log.e("test","startAngle="+startAngle);
canvas.drawArc(drawRect,tmpAngle,singleZoneAngle,true,imgPaint);
}
/**
* 绘制区域文字
* @param tmpAngle
* @param zoneIndex
*/
private void drawSingleZoneText(Canvas canvas,float tmpAngle, int zoneIndex) {
Path path=new Path();
path.addArc(drawRect,tmpAngle,singleZoneAngle);
float textWidth=textPaint.measureText(texts[zoneIndex]);
float hOffset= (2*radius*(float)Math.PI/(float)imgIds.length);
canvas.drawTextOnPath(texts[zoneIndex],path,(hOffset-textWidth)/2,dp2px(25),textPaint);
}
/**
* 绘制区域图片
* @param tmpAngle
* @param zoneIndex
*/
private void drawSingleZoneImg(Canvas canvas,float tmpAngle, int zoneIndex) {
int imgWidth=radius/3;
float angle= (float) ((tmpAngle+singleZoneAngle/2)*Math.PI/180);
float imgCenterX= centerX+(float) (Math.cos(angle)*radius*4/7);
float imgCenterY= centerY+(float) (Math.sin(angle)*radius*4/7);
canvas.drawBitmap(bitmaps[zoneIndex],null,new RectF(imgCenterX-imgWidth/2,imgCenterY-imgWidth/2,
imgCenterX+imgWidth/2,imgCenterY+imgWidth/2),null);
}
/**
* 绘制背景及外面圈圈
*/
private void drawBackground(Canvas canvas) {
canvas.drawColor(Color.WHITE); //可自定义
canvas.drawCircle(centerX,centerY,radius,bgPaint);
}
对外提供的转动到指定位置的方法:
/**
* 开始转动
* @param index 要转到的指定位置
*/
public void startRotate(int index){
if (rotateAnimator.isRunning()||rotateAnimator.isStarted()){
return;
}
startAngle=startAngle%360;
float minAngle=(3-index)*singleZoneAngle+singleZoneAngle/2+8;
float maxAngle=(4-index)*singleZoneAngle+singleZoneAngle/2-8;
//生成随机数 Math.random()表示生成[0,1]之间的任意小数
//生成任意非从0开始的小数区间[d1,d2)范围的随机数字(其中d1不等于0),
// 则只需要首先生成[0,d2-d1)区间的随机数字,然后将生成的随机数字区间加上d1即可。r.nextDouble() * (d2-d1) + 1;
destAngle= (float) (Math.random()*(maxAngle-minAngle)+minAngle)+360*2;
initAnimator();
rotateAnimator.start();
}
由于指针只要指到指定的位置区域即为选中了该区域,所以如果选中了某区域,指针只要转动到符合范围的角度,即是选中了该区域。所以需要找出规律来计算出符合要求的角度范围。
初始化动画:
private void initAnimator() {
rotateAnimator=ValueAnimator.ofFloat(startAngle,destAngle);
rotateAnimator.setDuration(3000);
rotateAnimator.setInterpolator(new LinearInterpolator());
rotateAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float angle= (float) animation.getAnimatedValue();
startAngle=angle;
invalidate();
}
});
}
完工,指定到谢谢参与区域试下:
ezgif-4-c6fcdbb40b4d.gif
代码都已上传至gitHub(https://github.com/yuliangC/LuckPlateView),欢迎start,谢谢。
网友评论