美文网首页
[Android高级动画] 如何实现Roulette效果?

[Android高级动画] 如何实现Roulette效果?

作者: 小米Metre | 来源:发表于2019-06-12 10:47 被阅读0次

如何使用Android属性动画实现Roulette效果?

like this:

效果图
一、问题拆解

该效果可以拆分成三个小问题。

1、如何用数字绘制一个任意大小的圆形?
2、如何让指针旋转并停在指定数字?
3、如何实现先加速后减速的旋转效果?

二、具体实现
1、如何用数字绘制一个任意大小的圆形?

这个问题可转换成:如何将字符按照任意曲线的路径进行绘制?

a、绘制曲线:可以用Path类来实现,Path提供了多种曲线的绘制,圆、椭圆、矩形、二阶三阶贝塞尔曲线等等。
b、按曲线路径绘制字符:Canvas类提够了一个方法drawTextOnPath,可以根据曲线路径来绘制字符。

//绘制圆
mPath.addCircle(0,0,mRadius,Path.Direction.CW);
canvas.drawPath(mPath,mPaint);

//按曲线绘制字符
//mPath:曲线路径,hOffset:水平偏移值(字符之间的距离),vOffset:垂直偏移值(字符和曲线的距离)
canvas.drawTextOnPath("按照曲线绘制",mPath,hOffset,vOffset,mPaint);
2、如何让指针旋转并停在指定数字?

a、指针旋转:其实就是一张图片选择,Matrix提够了图片的旋转、缩放、平移、错切变换等方法。所以这里使用Matrix可以解决图片旋转的问题。

//设置旋转角度,90度
mMatrix.postRotate(90,dx ,dy);
//mBitmap:旋转图片
canvas.drawBitmap(mBitmap,mMatrix,mPaint);

b、指针停在指定数字:
要让指针停在指定位置,就要获取当前位置的角度,该角度可以通过计算获得。
假如,LP可投注数值为10个(1—10),那么数字之前的夹角就是 360/10 = 36。
那么假如,我们要获取n的角度,n_degrees = n*36

//每个号码的平均弧度值
float average = 360/10;
//计算winNumber(开奖号码)所在的弧度值
float winDegrees = (average * winNumber + average/2 );
3、如何实现先加速后减速的旋转效果?

这里android提供的插值器 AccelerateDecelerateInterpolator,就是先加速再减速插值器

//属性动画
ValueAnimator animator = ValueAnimator.ofFloat(0,360);
//动画执行5秒
animator.setDuration(1000*5);
animator.setRepeatCount(0);
//先加速再减速插值器
animator.setInterpolator(new AccelerateDecelerateInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        float value = (float) animation.getAnimatedValue();
        invalidate();
    }
});
三、Roulette的完整代码
package com.metre.roulette_demo;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.Typeface;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;

/**
 * Created by mi on 2019/6/10
 */
public class RouletteView extends View {
    public RouletteView(Context context) {
        this(context,null);
    }

    public RouletteView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public RouletteView(Context context, AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr,0);

    }

    public RouletteView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init();
    }
    //画笔
    private Paint mPaint;
    //曲线
    private Path mPath;
    //Matrix(图片旋转缩放平移错切)
    private Matrix mMatrix;
    //指针图片
    private Bitmap mBitmap;
    private boolean onRunning;
    //屏幕中心点坐标
    private float mCentX;
    private float mCentY;
    //开奖号码
    private int winNumber;
    //圆的半径
    private float mRadius = 400;

    //当前角度
    private float mCurrentRotateRadius = -90;

    //默认开奖指针开始位置
    private float defaultPointer = 360 - 90;
    //圆周长
    private int mCircleRadius = 360;
    //指针旋转次数(一次动画内)
    private int mRotateCount = 10;
    //指针旋转次数
    private int mBetCount = 27;

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        this.mCentX = w/2;
        this.mCentY = h/2;
    }

    private void init(){
        mPaint = new Paint();
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(4);
        mPaint.setColor(Color.GRAY);
        mPaint.setAntiAlias(true);

        mPath = new Path();
        mMatrix = new Matrix();

        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inSampleSize = 8;//缩小图片

        mBitmap = BitmapFactory.decodeResource(getResources(),R.mipmap.arrow,options);

    }

    //开始开奖
    public void startOpenWin(){

        if(onRunning)return;

        winNumber = (int) (Math.random()*mBetCount+1);
        startAnimator();
    }
    //启动动画
    private void startAnimator(){

        //每个号码的平均弧度值
        float average = mCircleRadius/mBetCount;

        //计算开奖号码所在的弧度值
        float winDegrees = (average * (winNumber-1)) - 90 + ( winNumber >= 10 ? average : average/2 );

        //当弧度值小于0
        if(winDegrees < 0){
            winDegrees = mCircleRadius + winDegrees;
        }

        //最大变化值: (360 x 一次动画指针旋转的次数)+ 开奖号码所在的弧度
        float maxChangeValue = mCircleRadius * mRotateCount + winDegrees;

        Log.d("RouletteView","startAnimator | winDegrees-------->"+winDegrees);
        Log.d("RouletteView","startAnimator | maxChangeValue---->"+maxChangeValue);


        //属性动画
        ValueAnimator animator = ValueAnimator.ofFloat(defaultPointer,maxChangeValue);

        //动画执行5秒
        animator.setDuration(1000*5);
        animator.setRepeatCount(0);
        //先加速再减速插值器
        animator.setInterpolator(new AccelerateDecelerateInterpolator());

        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {

                float value = (float) animation.getAnimatedValue();
                //转换成360度弧值
                mCurrentRotateRadius = convertRadius(value);

                Log.d("RouletteView","AnimatorUpdateListener | mCurrentRotateRadius--->"+mCurrentRotateRadius);

                invalidate();
            }
        });

        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                onRunning = false;

                //记录上一次动画的指针
                defaultPointer = mCurrentRotateRadius;

            }
        });

        animator.start();

        onRunning = true;
    }

    //转换成0-360度弧值
    private float convertRadius(float value){

        Log.d("RouletteView","convertRadius | value--->"+value);

         if( value < mCircleRadius )return value;

        float tmp = value - mCircleRadius;

        return convertRadius(tmp);
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //画布移到中心
        canvas.translate(mCentX,mCentY);

        mPaint.setStyle(Paint.Style.STROKE);
        mPath.reset();
        //绘制圆
        mPath.addCircle(0,0,mRadius,Path.Direction.CW);

        canvas.drawPath(mPath,mPaint);

        //绘制开奖号码
        mPaint.setStyle(Paint.Style.FILL);
        canvas.drawText(""+winNumber,-4,-500,mPaint);

        //画布逆时针旋转90
        canvas.rotate(-90);

        @SuppressLint("DrawAllocation")
        PathMeasure pathMeasure = new PathMeasure(mPath,false);

        //圆周长
        float distance = pathMeasure.getLength();


        mPaint.setTextSize(60);
        mPaint.setTypeface(Typeface.DEFAULT_BOLD);


        //水平偏移值(数字之间的距离)
        float hOffset = distance/mBetCount;

        //垂直偏移值(和圆的距离)
        float vOffset = -6;

        //绘制27个值
        for(int j = 0 ; j < mBetCount;j++){

            canvas.drawTextOnPath(""+(j+1),mPath,j*hOffset,vOffset,mPaint);
        }


        drawPointer(canvas);

    }

    //绘制指针
    private void drawPointer(Canvas canvas){

        float dx = mBitmap.getWidth()/2;
        float dy = mBitmap.getHeight()/10;

        mMatrix.reset();
        mMatrix.postRotate(mCurrentRotateRadius,dx ,dy);
        mMatrix.postTranslate(-dx,-dy);

        canvas.drawBitmap(mBitmap,mMatrix,mPaint);
    }
}

相关文章

网友评论

      本文标题:[Android高级动画] 如何实现Roulette效果?

      本文链接:https://www.haomeiwen.com/subject/czskfctx.html