Android 抽奖 转盘 动画 实现原理详解

Android动画之旅有一段时间没有写新的内容了。我之前想的是,边学习边开发一些有用的小Demo但是一写起来就根本停不下来了。先给大家展示一个成果,后续我会将每个项目的原码和原理分析发布出来。虽然我知道看的人可能不多,但是写东西总结对自己本身也是一种检验和复习。还是希望我的博客人可以越来越多。
先展示一下成果。本来是想写五个但是写着写着就写成了8个。
Android 抽奖 转盘 动画 实现原理详解_第1张图片
那么今天写一个最简单的大转盘开始,逐渐深入浅出给大家介绍一些Android View底层和Android动画的使用和原理。

那么我们先来看下大转盘的分解,然后逐步进行实现。
那么大家看到核心就是这个
这里写图片描述
夜神录的屏幕,然后又转的GIF 旋转起来感觉很慢,但是真实情况的转动还是很好的!这里为了大家更好的理解,所以还是放一个GIF吧。
做大转盘,首先我们需要一个转盘,而且我希望这个转盘,的奖项和区域是可以自定义的。因为很多时候我们的中奖率是有变化的,如果做成动态的,我们后台只需要简单的输入数据数组就可以,让我们新的转盘产生。

那么问题一来了,我没有转盘啊。怎么办那?而且又是动态的,那么没办法了,自己动手丰衣足食,我们先自己画一个圆盘,然后根据输入的比例,画出对应的扇形就可以了。
那么先给出自定义转盘类的代码。

/**
 * 项目名称:GameApplication
 * 类描述:
 * 创建人:xjl
 * 创建时间:2016/10/11 11:32
 * 修改人:Administrator
 * 修改时间:2016/10/11 11:32
 * 修改备注:
 */
public class RotationPanelView extends View {
    //View的宽和高
    private int mWidth;
    private int mHeight;
    //圆半径
    private int mRadio;
    //圆心坐标
    private int mCircleX;
    private int mCircleY;
    private Paint mPaint;
    private ArrayList bonusList;
    private ArrayList colorList;

    //设置画布抗锯齿
    private PaintFlagsDrawFilter pfd = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);

    public RotationPanelView(Context context) {
        super(context);
        init();
    }

    public RotationPanelView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public RotationPanelView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    //这里的队列的长度需要小于8
    public void setArrayList(ArrayList bonusList) {
        this.bonusList = bonusList;
    }


    private void init() {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (checkData()) {
            canvas.setDrawFilter(pfd);
            mPaint.setColor(Color.BLACK);
            mPaint.setStyle(Paint.Style.STROKE);
            canvas.drawCircle(mCircleX, mCircleY, mRadio, mPaint);
            //设置浮点类型的矩形,也是我们所画扇形的区域。也就是四边与圆相切的正方形
            RectF rectF = new RectF(mCircleX - mRadio, mCircleY - mRadio, mCircleX + mRadio, mCircleY + mRadio);

            float tempRadio = 0.0f;
            //为每个扇形设置角度和颜色
            for(int i=0;i//减90度 使扇形从12点方向开始
                float begainRadio=360.0f*tempRadio-90.0f;
                tempRadio=tempRadio+bonusList.get(i).getBonusRates();
                float endRadio=360*bonusList.get(i).getBonusRates();
                mPaint.setColor(colorList.get(i));
                mPaint.setStyle(Paint.Style.FILL);
                canvas.drawArc(rectF,begainRadio,endRadio,true,mPaint);
            }
            invalidate();
        }

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //测试当前View的宽和高 以方便后面设置我们扇形的区域
        mWidth = getWidth();
        mHeight = getHeight();
        mCircleX = mWidth / 2;
        mCircleY = mHeight / 2;
        mRadio = mCircleX - 10;
    }
    //对输入的数据进行判断,如果数据错误,不会绘制圆周,
    private boolean checkData() {
        if (bonusList != null && bonusList.size() > 0 && bonusList.size() < 8) {
            float temp = 0.0f;
            for (int i = 0; i < bonusList.size(); i++) {
                temp = temp + bonusList.get(i).getBonusRates();
            }
            //看看加入数据比例之和是否等于1 由于是浮点数可能产生误差,所以这里不能写"==1"
            if (Math.abs(temp - 1.0f) < 0.01f) {
                colorList = new ArrayList<>();
                //设置颜色列表,我知道的颜色不多,可以自行改成你喜欢的颜色列表
                colorList.add(Color.RED);
                colorList.add(Color.BLUE);
                colorList.add(Color.YELLOW);
                colorList.add(Color.GREEN);
                colorList.add(Color.GRAY);
                colorList.add(0xFF123456);
                colorList.add(0xFF456123);
                colorList.add(0xFF456456);
                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

}

那么有了我们圆形圆盘,剩下的我们只需要让这个圆盘转起来就可以了。
那么这里我使用的是,Android动画的对象动画,包括了Z轴旋转,和开始转动转盘加速,快结束的时候转盘减速。
在来看下布局


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@mipmap/table"
    android:orientation="vertical">

    <TextView
        android:text="开心转转转"
        android:layout_marginTop="10dp"
        android:textSize="30sp"
        android:gravity="center_horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    
    <TextView
        android:text="|"
        android:layout_marginTop="10dp"
        android:textSize="30sp"
        android:gravity="center_horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <game.xjl.draw_card.RotationPanelView
        android:layout_marginTop="5dp"
        android:id="@+id/rotation_panel_view"
        android:layout_width="400dp"
        android:layout_height="400dp"
        android:layout_gravity="center"
        />

    <TextView
        android:layout_marginTop="20dp"
        android:id="@+id/the_result"
        android:textColor="#FF0000"
        android:textSize="35sp"
        android:gravity="center"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="恭喜您中奖啦"
        />

LinearLayout>

然后就是我们的使用啦。这里我们为了产生结果,和更好的真实性即随机效果,我设置了二次随机,即第一次我们随机产生一个中几等奖,然后在这个基础上,在随机一个在这个中奖区域的角度,那么这样转起来,仿真效果就会很好。我在网上也看了一些例子,大多是没有速度变化效果,然后生硬的停在一个中奖区域的中间。这样的用户体验很差,做产品,要做就做好。既是小产品。

/**
 * 项目名称:GameApplication
 * 类描述:
 * 创建人:xjl
 * 创建时间:2016/10/11 14:05
 * 修改人:Administrator
 * 修改时间:2016/10/11 14:05
 * 修改备注:
 */
public class RotationPanelActivity extends Activity implements View.OnClickListener {

    private RotationPanelView rotation_panel_view;
    private ArrayList bonusBeens;
    private TextView the_result;
    private int randomBonusResult; //所中的奖等
    private float randomBonusRadio;//随机角度


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_rotation_panel);
        initData();
        initView();
   }

    private void initData() {
        bonusBeens = new ArrayList<>();
        //初始化中奖数据,只需要让中奖数据相加等于1即可
        BonusBean bonusBean = new BonusBean();
        bonusBean.setBonusRates(0.2f);
        bonusBeens.add(bonusBean);

        BonusBean bonusBean1 = new BonusBean();
        bonusBean1.setBonusRates(0.2f);
        bonusBeens.add(bonusBean1);

        BonusBean bonusBean2 = new BonusBean();
        bonusBean2.setBonusRates(0.20f);
        bonusBeens.add(bonusBean2);

        BonusBean bonusBean3 = new BonusBean();
        bonusBean3.setBonusRates(0.15f);
        bonusBeens.add(bonusBean3);

        BonusBean bonusBean4 = new BonusBean();
        bonusBean4.setBonusRates(0.25f);
        bonusBeens.add(bonusBean4);

    }


    private void initView() {
        the_result = (TextView) findViewById(R.id.the_result);
        rotation_panel_view = (RotationPanelView) findViewById(R.id.rotation_panel_view);
        rotation_panel_view.setOnClickListener(this);
        rotation_panel_view.setArrayList(bonusBeens);
    }

    @Override
    public void onClick(View v) {
        if (R.id.rotation_panel_view == v.getId()) {
            initAnimationAndSetRotationCount(10);
        }
    }
    //开放仿真转圈度数,要不转一下,只有一圈就尴尬了。所以想先转多少圈在显示中奖结果,自己定。
    private void initAnimationAndSetRotationCount(int count) {
        Random random = new Random();
        //第一次随机产生中奖结果
        randomBonusResult = random.nextInt(bonusBeens.size()) + 1;
        //二次随机产生一个停在目标中奖区域的随机度数
        randomBonusRadio = random.nextFloat();
        float tempFloat = 0.0f;
        for (int i = 0; i < randomBonusResult; i++) {
            tempFloat = tempFloat + bonusBeens.get(i).getBonusRates();
        }
        tempFloat = tempFloat - bonusBeens.get(randomBonusResult - 1).getBonusRates() * randomBonusRadio;
        tempFloat = 360.0f * tempFloat;
        //Andorid对象动画 设置我们的View  动画类型。由于自定义View是顺时针画的,所以为了停在我们的中奖区域使用逆时针旋转。即中奖度数取反。
        ObjectAnimator animator = ObjectAnimator.ofFloat(rotation_panel_view, "rotation", 0f,- (360f * count + tempFloat));
        if (count == 0) {
            animator.setDuration(1000);
        } else {
            //根据设置的圈数 设置动画持续时间 即每圈0.5秒
            animator.setDuration(500 * count);
        }
        animator.start();
        rotation_panel_view.setOnClickListener(null);
        animator.addListener(new AnimatorListenerAdapter() {

            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                rotation_panel_view.setOnClickListener(RotationPanelActivity.this);
                the_result.setText("恭喜您" + randomBonusResult + "奖");
            }

            @Override
            public void onAnimationStart(Animator animation) {
                super.onAnimationStart(animation);
            }
        });
    }

}

你可能感兴趣的:(AndroidUI设计,Android多线程,android开发)