Android 抽奖转盘的实现

** 本篇文章已授权公众号 guolin_blog (郭霖)独家发布 **

序言

最近需要实现一个抽奖的控件,我简单搜索了下,感觉要不很多细节地方没有处理,要么,根本就不能用。索性想自己实现个,从千图网搜了下,挑选了个自己比较喜欢的出来,psd打开后效果如下:


这里写图片描述



最终实现效果如下:


点击Go按钮自动滚动:


这里写图片描述



随手势滚动:


这里写图片描述



实现的效果还不错,因为是模拟器加录制,画面可能会有些卡顿,真机其实蛮顺畅的,下面简单的讲讲实现的步骤。


实现

  • 1,绘制。

    首先第一个我们要它给画出来,但是要注意的就是Android所对应的坐标系的问题。


    这里写图片描述

   for(int i= 0;i<6;i++){if(i%2 == 0){canvas.drawArc(rectF,angle,60,true,dPaint);}else{canvas.drawArc(rectF,angle,60,true,sPaint);}angle += 60;}for(int i=0;i<6;i++){drawIcon(width/2, height/2, radius, InitAngle, i, canvas);InitAngle += 60;}for(int i=0;i<6;i++){drawText(InitAngle+30,strs[i], 2*radius, textPaint, canvas,rectF);InitAngle += 60;}

其中有两个地方需要注意下,第一个就是画弧的地方第一个角度是起始角度,第二个是弧的角度,并不是结束的角度,所以是固定值60。第二个地方就是计算具体的x,y的值的时候要根据弧度去计算,不能根据角度。

  • 2.使用属性动画让其自动旋转。

如果用SurfaceView去进行重绘旋转存在一些问题,比如旋转的角度不好控制,旋转的速度不好控制。但是用属性动画,这个问题就很好解决了。

  ValueAnimator animtor = ValueAnimator.ofInt(InitAngle,DesRotate);animtor.setInterpolator(new AccelerateDecelerateInterpolator());animtor.setDuration(time);animtor.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {int updateValue = (int) animation.getAnimatedValue();InitAngle = (updateValue % 360 + 360) % 360;ViewCompat.postInvalidateOnAnimation(RotatePan.this);}});animtor.addListener(new AnimatorListenerAdapter() {@Overridepublic void onAnimationEnd(Animator animation) {super.onAnimationEnd(animation);int pos = InitAngle / 60;if(pos >= 0 && pos <= 3){pos = 3 - pos;}else{pos = (6-pos) + 3;}if(l != null)l.endAnimation(pos);}});animtor.start();

用动画最重要的就是,如何计算出结束动画后的位置,那么把最终旋转的总角度%360°就得到最后一圈实际旋转的角度,再除以60就得到了到底选择了几个位置,因为一个位置占据60°,这应该不难理解。

    @Overridepublic void onAnimationEnd(Animation animation) {int pos = startDegree % 360 / 60;if(pos >= 0 && pos <= 3){pos = 3 - pos;}else{pos = (6-pos) + 3;}if(l != null)l.endAnimation(pos);}

但是问题又来了,Android所对应的坐标系,0的位置应该是最底下,而指针的位置是在最上面,所以,我们结合上面的坐标系来看,还需要处理下,如上面的代码所示。

  • 3.利用Scroller和GestureDetector对手势进行处理。

    触摸事件的处理,最后到底允不允许转盘随手势滑动呢?其实貌似做成这样也就可以了,但是最后还是实现了下,用到了GestureDetector 和 Scroller这个类。其实做法有很多,首先获取我们的滑动的距离,Math.sqrt(dx * dx + dy * dy),然后无非就是把这个距离转换成我们需要的角度,你可以把这个距离当作我们的周长来处理,也可以把这个距离当作我们总的旋转的角度来处理。之后就是随着时间的流逝,不断的刷新我们的界面了。

 @Overridepublic boolean onTouchEvent(MotionEvent event) {boolean consume = mDetector.onTouchEvent(event);if(consume){getParent().requestDisallowInterceptTouchEvent(true);return true;}return super.onTouchEvent(event);}public void setRotate(int rotation){rotation = (rotation % 360 + 360) % 360;InitAngle = rotation;ViewCompat.postInvalidateOnAnimation(this);}@Overridepublic void computeScroll() {if(scroller.computeScrollOffset()){setRotate(scroller.getCurrY());}super.computeScroll();}private class RotatePanGestureListener extends GestureDetector.SimpleOnGestureListener{@Overridepublic boolean onDown(MotionEvent e) {return super.onDown(e);}@Overridepublic boolean onDoubleTap(MotionEvent e) {return false;}@Overridepublic boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {float centerX = (RotatePan.this.getLeft() + RotatePan.this.getRight())*0.5f;float centerY = (RotatePan.this.getTop() + RotatePan.this.getBottom())*0.5f;float scrollTheta = vectorToScalarScroll(distanceX, distanceY, e2.getX() - centerX, e2.getY() -centerY);int rotate = InitAngle -(int) scrollTheta / FLING_VELOCITY_DOWNSCALE;setRotate(rotate);return true;}@Overridepublic boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {float centerX = (RotatePan.this.getLeft() + RotatePan.this.getRight())*0.5f;float centerY = (RotatePan.this.getTop() + RotatePan.this.getBottom())*0.5f;float scrollTheta = vectorToScalarScroll(velocityX, velocityY, e2.getX() - centerX, e2.getY() -centerY);scroller.abortAnimation();scroller.fling(0, InitAngle , 0, (int) scrollTheta / FLING_VELOCITY_DOWNSCALE,0, 0, Integer.MIN_VALUE, Integer.MAX_VALUE);return true;}}private float vectorToScalarScroll(float dx, float dy, float x, float y) {// get the length of the vectorfloat l = (float) Math.sqrt(dx * dx + dy * dy);// decide if the scalar should be negative or positive by finding// the dot product of the vector perpendicular to (x,y).float crossX = -y;float crossY = x;float dot = (crossX * dx + crossY * dy);float sign = Math.signum(dot);return l * sign;}


  • 4.剩余问题处理

    还存在个问题,如果没有手势去操作转盘,那我们很容易判断它所旋转的角度,但是有手势的参与,我们很容易旋转到转盘中两个分片中间的位置,那么,我们在让它旋转之前,要简单处理下,避免这种事情发生。

   //TODO 为了每次都能旋转到转盘的中间位置int offRotate = DesRotate % 360 % 60;DesRotate -= offRotate;DesRotate += 30;

这样不管手势怎么操作,我最终都是旋转到分片的中间位置了。


  • 5.转动到指定某个区域
   /*** 开始转动* @param pos 如果 pos = -1 则随机,如果指定某个值,则转到某个指定区域*/public void startRotate(int pos){int lap = (int) (Math.random()*12) + 4;int angle = 0;if(pos < 0){angle = (int) (Math.random() * 360);}else{int initPos  = queryPosition();if(pos > initPos){angle = (pos - initPos)*60;lap -= 1;angle = 360 - angle;}else if(pos < initPos){angle = (initPos - pos)*60;}else{//nothing to do.}}

好多人加我QQ,问我怎么转动到指定位置,所以更新了下代码,上传到github上去了,这里做个日志。如果传的是 -1 则随机转动,如果传的是大于0,则转动到指定位置。


  • 6.改变转盘数量
   id="@+id/rotatePan"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="78dp"android:layout_centerHorizontal="true"luckpan:pannum="8"luckpan:names="@array/names"luckpan:icons="@array/icons"/><string-array name="names"><item>actionitem><item>adventureitem><item>combatitem><item>mobaitem><item>otheritem><item>roleitem><item>sportsitem><item>wordsitem>string-array><string-array name="icons"><item>actionitem><item>adventureitem><item>combatitem><item>mobaitem><item>otheritem><item>roleitem><item>sportsitem><item>wordsitem>string-array>

将pannum改为你想要的数量,然后names和icons定义在arrays.xml文件中, 其中arrays.xml中的数量要和转盘的数量一致。理论上可以改为转盘数量为N的情况,但是综合来看还是6个和8个转盘数量最适宜,而且很多人也只问我怎么改成8个转盘,所以对这两种情况做了适配,如果后期还有别的需求在加吧,github上代码已经更新,请重新下载。

代码

最后,代码已经上传到github上去了。地址:https://github.com/Nipuream/LuckPan         欢迎Star

LuckPan.apk        apk下载地址


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部