Android 抽奖转盘的实现

Posted Nipuream

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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() 
            @Override
            public void onAnimationUpdate(ValueAnimator animation) 
                int updateValue = (int) animation.getAnimatedValue();
                InitAngle = (updateValue % 360 + 360) % 360;
                ViewCompat.postInvalidateOnAnimation(RotatePan.this);
            
        );
        animtor.addListener(new AnimatorListenerAdapter() 
            @Override
            public 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°,这应该不难理解。

    @Override
        public 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),然后无非就是把这个距离转换成我们需要的角度,你可以把这个距离当作我们的周长来处理,也可以把这个距离当作我们总的旋转的角度来处理。之后就是随着时间的流逝,不断的刷新我们的界面了。

 @Override
    public 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);
    


    @Override
    public void computeScroll() 

        if(scroller.computeScrollOffset())
            setRotate(scroller.getCurrY());
        

        super.computeScroll();
    

    private class RotatePanGestureListener extends GestureDetector.SimpleOnGestureListener

        @Override
        public boolean onDown(MotionEvent e) 
            return super.onDown(e);
        

        @Override
        public boolean onDoubleTap(MotionEvent e) 
            return false;
        

        @Override
        public 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;
        

        @Override
        public 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 vector
        float 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.改变转盘数量
   <com.hr.nipuream.luckpan.view.RotatePan
        android: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"
        />


        <resources>
            <string-array name="names">
                <item>action</item>
                <item>adventure</item>
                <item>combat</item>
                <item>moba</item>
                <item>other</item>
                <item>role</item>
                <item>sports</item>
                <item>words</item>
            </string-array>

            <string-array name="icons">
                <item>action</item>
                <item>adventure</item>
                <item>combat</item>
                <item>moba</item>
                <item>other</item>
                <item>role</item>
                <item>sports</item>
                <item>words</item>
            </string-array>
        </resources>

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

代码

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

LuckPan.apk        apk下载地址

以上是关于Android 抽奖转盘的实现的主要内容,如果未能解决你的问题,请参考以下文章

Swift - iOS大转盘抽奖实现原理

Swift - iOS大转盘抽奖实现原理

微信小程序实现抽奖大转盘

微信小程序实现抽奖大转盘

求Java实现转盘转动功能,抽奖,坐等

微信小程序,通过摇一摇实现大转盘抽奖的效果代码怎么写?