Android超简单实现自定义抽奖转盘效果

目录

目录

效果展示

前言

●之前有一个项目需要用到转盘效果,但是那个时候思路不是很清晰,所以引用了一下别人的控件但是别人的源码还是比较复杂的,后来经过分析后使用一种简单的方式写出了之前的效果。
●如果有小伙伴想实现九宫格抽奖效果的话请看我的另一篇文章《Android超简单实现九宫格抽奖》

实现步骤

1.画转盘

  • 实现原理


  • 对应代码
public class LuckPan extends View {
    private Paint mPaintArc;//转盘扇形画笔
    private float mRadius;//圆盘的半径
    private RectF rectFPan;//构建转盘的矩形
    private String[] mItemStrs = {"俯卧撑30个","波比跳15个","卷腹30个","高抬腿30下","深蹲30下","开合跳30下"};
    private float mItemAnge;
    public LuckPan(Context context) {
        this(context,null);
    }

    public LuckPan(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public LuckPan(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mPaintArc = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaintArc.setStyle(Paint.Style.FILL);
    }
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mRadius = Math.min(w,h)/2*0.9f;
        //这里是将(0,0)点作为圆心
        rectFPan = new RectF(-mRadius,-mRadius,mRadius,mRadius);
        //每一个Item的角度
        mItemAnge = 360 / mItemStrs.length;
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(getWidth()/2,getHeight()/2);//为了操作方便将画布中心点设置为(0,0)
        drawPanItem(canvas);//画转盘
    }
    private void drawPanItem(Canvas canvas) {
        float startAng = 0;//扇形开始的角度
        for (int x = 1;x<= mItemStrs.length;x++){
        //这里我们通过判断奇数偶数来给转盘设置不同的颜色
            if(x%2 == 1){
                //是奇数
                mPaintArc.setColor(Color.WHITE);
            }else {
                //偶数
                mPaintArc.setColor(Color.parseColor("#F8864A"));
            }
            canvas.drawArc(rectFPan,startAng,mItemAnge,true,mPaintArc);//画扇形
            startAng+=mItemAnge;//每画完一次增加开始角度
        }
    }
}
  • 对应效果

    2.生成画文字路径
  • 实现原理


  • 对应代码
    在代码中我们需要添加几个属性。(其中为了展示方便省略了部分代码)
public class LuckPan extends View {
    //...省略部分
    private Paint mPaintItemStr;//转盘文字画笔
    private ArrayList<Path> mArcPaths;
    private RectF rectFStr;//构建文字圆盘的矩形
    private float mTextSize = 20;//文字大小
    private String[] mItemStrs = {"俯卧撑30个","波比跳15个","卷腹30个","高抬腿30下","深蹲30下","开合跳30下"};//绘制的文字数组
    public LuckPan(Context context) {
        this(context,null);
    }

    public LuckPan(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public LuckPan(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        //...省略部分代码
        mPaintItemStr = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaintItemStr.setColor(Color.parseColor("#ED2F2F"));//设置文字颜色
        mPaintItemStr.setStrokeWidth(3);
        mPaintItemStr.setTextAlign(Paint.Align.CENTER);//设置文字水平居中对齐
        mArcPaths = new ArrayList<>();
    }
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        rectFStr = new RectF(-mRadius/7*5,-mRadius/7*5,mRadius/7*5,mRadius/7*5);//构建文字路径的矩形半径为圆盘的五分之七
        mTextSize = mRadius/9;//根据圆盘的半径设置文字大小
        mPaintItemStr.setTextSize(mTextSize);//设置文字大小
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(getWidth()/2,getHeight()/2);//画布中心点设置为(0,0)
        drawPanItem(canvas);
        drawText(canvas);
    }
    //画文字
    private void drawText(Canvas canvas) {
        for(int x = 0;x<mItemStrs.length;x++){
            Path path = mArcPaths.get(x);
            canvas.drawTextOnPath(mItemStrs[x],path,0,0,mPaintItemStr);
        }
    }

    private void drawPanItem(Canvas canvas) {
        float startAng = 0;//扇形开始的角度
        for (int x = 1;x<= mItemStrs.length;x++){
            if(x%2 == 1){
                //是奇数
                mPaintArc.setColor(Color.WHITE);
            }else {
                //偶数
                mPaintArc.setColor(Color.parseColor("#F8864A"));
            }
            //以下是添加文字绘制路径的代码
            Path path = new Path();
            path.addArc(rectFStr,startAng,mItemAnge);//文字的路径圆形比盘的小
            mArcPaths.add(path);
            //==========================
            canvas.drawArc(rectFPan,startAng,mItemAnge,true,mPaintArc);
            startAng+=mItemAnge;
        }
    }
}
  • 对应效果


3.设置动画实现转动

  • 实现原理


  • 对应代码
    这里为了看的清楚也是省略了部分代码。
public class LuckPan extends View {
    //省略部分代码....
    private int mRepeatCount = 4;//转几圈
    private int mLuckNum = 2;//最终停止的位置
    private ObjectAnimator objectAnimator;
    public LuckPan(Context context) {
        this(context,null);
    }

    public LuckPan(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public LuckPan(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }
    //省略部分代码....
    public void startAnim(){
        if(objectAnimator!=null){
            objectAnimator.cancel();
        }
        objectAnimator = ObjectAnimator.ofFloat(this, "rotation", 0, mRepeatCount*360+mLuckNum*mItemAnge);
        objectAnimator.setDuration(4000);
        objectAnimator.start();
    }
}
  • 对应效果

    4.纠正初始角度
    通过上面的代码我们已经基本实现了转盘效果了,但是我们发现转盘初始的角度不是在第一个Item上。
  • 调整原理


  • 对应代码
    为了观看方便这里只展示onDraw里的代码
@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(getWidth()/2,getHeight()/2);//画布中心点设置为(0,0)
        canvas.rotate(-90);//将画布旋转-90度
        drawPanItem(canvas);
        drawText(canvas);
    }
  • 对应效果



    这里我们发现在旋转了-90度后虽然指向了第一个Item但是却没有指到扇形的中间,因此我们需要继续旋转画布为-ItemAngle/2的度数即代码实现为:

public class LuckPan extends View {
    private float mOffsetAngle = 0;//圆盘偏移角度(我们需要添加此属性)
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mItemAnge = 360 / mItemStrs.length;
        mOffsetAngle = mItemAnge/2;//设置偏移角度
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(getWidth()/2,getHeight()/2);//画布中心点设置为(0,0)
        canvas.rotate(-90-mOffsetAngle);//将画布旋转(-90-mOffsetAngle)度
        drawPanItem(canvas);
        drawText(canvas);
    }
}
  • 对应效果

    5.纠正下一次旋转位置为上一次结束位置以及纠正结束位置
    我们虽然纠正了初始位置但是我们发现转盘的下一次旋转角度却不是上一次结束的位置,而且停止的位置也不正确,下面我们就来纠正下这两个问题。
  • 实现代码
    同样为了方便观看这里只展示部分代码。
public class LuckPan extends View {
    private float mStartAngle = 0;//存储圆盘开始的位置
    private ObjectAnimator objectAnimator;
    public void startAnim(){
        if(objectAnimator!=null){
            objectAnimator.cancel();
        }
        float v = mItemAnge*mLuckNum-mStartAngle%360;//如果转过一次了那下次旋转的角度就需要减去上一次多出的,否则结束的位置会不断增加的
        objectAnimator = ObjectAnimator.ofFloat(this, "rotation", mStartAngle, mStartAngle+mRepeatCount*360+v);
        objectAnimator.setDuration(4000);
        objectAnimator.start();
        mStartAngle += mRepeatCount*360+v;//将上一次的角度加进来以达到下次开始就是上次结束的位置
    }
}
  • 对应效果



    我们发现虽然第二次开始的位置是上一次结束的位置,但是最终停止的位置却不对,因为我们设置的是2,而停止的却是倒数第2个,其实这主要是因为转盘是顺时针旋转的原因,在这里有两种解决方案:
    1.将结束位置设置为负数:(这种方法就不展示代码了,直接将mLuckNum设置为负数即可)
    2.将旋转角度变为逆时针:(就是将旋转角度变为负数)
    代码展示:

//这里其实就是将角度计算那部分+变成-
public void startAnim(){
        if(objectAnimator!=null){
            objectAnimator.cancel();
        }
        float v = mItemAnge*mLuckNum+mStartAngle%360;//如果转过一次了那下次旋转的角度就需要减去上一次多出的,否则结束的位置会不断增加的
        objectAnimator = ObjectAnimator.ofFloat(this, "rotation", mStartAngle, mStartAngle-mRepeatCount*360-v);
        objectAnimator.setDuration(4000);
        objectAnimator.start();
        mStartAngle -= mRepeatCount*360+v;
    }
  • 对应效果

    6.添加动态设置参数的方法
    就是动态的设置Item所显示的内容,及停止的位置。
  • 对应代码
 /**
     * 设置转盘数据
     * @param items
     */
    public void setItems(String[] items){
        mItemStrs = items;
        mOffsetAngle=0;
        mStartAngle=0;
        mOffsetAngle = 360/items.length/2;//根据item的数量动态调整圆盘偏移角度
        invalidate();
    }
    /**
     * 设置转盘数据
     */
    public void setLuckNumber(int luckNumber){
        mLuckNum = luckNumber;
    }
    

布局代码:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.itfitness.luckpan.MainActivity">
    <com.itfitness.luckpan.widget.LuckPan
        android:id="@+id/pan"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
    <ImageView
        android:id="@+id/img_start"
        android:src="@mipmap/ic_luckdrawstart"
        android:layout_centerInParent="true"
        android:layout_width="130dp"
        android:layout_height="130dp" />
</RelativeLayout>

Avtivity代码:

public class MainActivity extends AppCompatActivity {
    private LuckPan pan;
    private ImageView imgStart;
    private String[] mItemStrs = {"123","撒大声道1","撒大声道2","撒旦说","撒大声道3","哥哥哥","对应效果","对应代码"};
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        pan = (LuckPan) findViewById(R.id.pan);
        imgStart = (ImageView) findViewById(R.id.img_start);
        pan.setItems(mItemStrs);
        pan.setLuckNumber(2);
        imgStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                pan.startAnim();
            }
        });
    }
}
  • 对应效果

    7.添加结束回调函数
  • 对应代码
    回调接口代码:
public interface LuckPanAnimEndCallBack {
    void onAnimEnd(String str);
}

LuckPan代码:这里为了方便省略部分代码

public class LuckPan extends View {
    private ObjectAnimator objectAnimator;
    private LuckPanAnimEndCallBack luckPanAnimEndCallBack;

    public LuckPanAnimEndCallBack getLuckPanAnimEndCallBack() {
        return luckPanAnimEndCallBack;
    }

    public void setLuckPanAnimEndCallBack(LuckPanAnimEndCallBack luckPanAnimEndCallBack) {
        this.luckPanAnimEndCallBack = luckPanAnimEndCallBack;
    }

    public void startAnim(){
//        mLuckNum = random.nextInt( mItemStrs.length);//随机生成结束位置
        if(objectAnimator!=null){
            objectAnimator.cancel();
        }
        float v = mItemAnge*mLuckNum+mStartAngle%360;//如果转过一次了那下次旋转的角度就需要减去上一次多出的,否则结束的位置会不断增加的
        objectAnimator = ObjectAnimator.ofFloat(this, "rotation", mStartAngle, mStartAngle-mRepeatCount*360-v);
        objectAnimator.setDuration(4000);
        //在动画的监听事件中增加我们实现的回调函数
        objectAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                if(luckPanAnimEndCallBack!=null){
                    luckPanAnimEndCallBack.onAnimEnd(mItemStrs[mLuckNum]);
                }
            }
        });
        objectAnimator.start();
        mStartAngle -= mRepeatCount*360+v;
    }
}

Activity代码:

public class MainActivity extends AppCompatActivity {
    private LuckPan pan;
    private ImageView imgStart;
    private String[] mItemStrs = {"123","撒大声道1","撒大声道2","撒旦说","撒大声道3","哥哥哥","对应效果","对应代码"};
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        pan = (LuckPan) findViewById(R.id.pan);
        imgStart = (ImageView) findViewById(R.id.img_start);
        pan.setItems(mItemStrs);
        pan.setLuckNumber(2);
        pan.setLuckPanAnimEndCallBack(new LuckPanAnimEndCallBack() {
            @Override
            public void onAnimEnd(String str) {
                Toast.makeText(MainActivity.this, str, Toast.LENGTH_SHORT).show();
            }
        });
        imgStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                pan.startAnim();
            }
        });
    }
}
  • 对应效果


项目源码:https://github.com/myml666/LuckPan

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,016评论 4 62
  • 1 CALayer IOS SDK详解之CALayer(一) http://doc.okbase.net/Hell...
    Kevin_Junbaozi阅读 5,119评论 3 23
  • 我想写一首诗, 不写大风和乌云, 不写飞雨和秋叶, 诗里只写你。 写你的唇勾起的性感, 写你的手挑起的温柔, 写你...
    廿九诗阅读 90评论 0 0
  • 儿子跑门口把饮水机的空桶拎到了客厅,他“觊觎”这个“玩具”很久了。因为考虑安全问题,饮水机一直被餐椅挡着,他想碰也...
    魏娅慧阅读 336评论 0 0
  • 今天,被这个问题纠缠不休,这个念头时不时的冒出来。理性,梦想,生活,现实。纠结着… 不配谈梦想的时候就老老实实的待...
    月小楼336阅读 138评论 0 0