Android自定义View实现炫酷的加速球效果

目录

目录.png

前言

今天学习了下Android中有关贝赛尔曲线相关的知识,正好结合之前所学的Animator相关的知识,就做了一个类似于360加速球的效果,在这里记录下方便以后使用。

效果展示

原理解析


首先构建一个圆形路径(中心点为控件中心),然后利用canvas.clipPath方法将画布裁剪为圆形。

利用贝赛尔曲线构造一条如上图所示的波浪线,波浪线分为三段(一起一伏为一段,每段长度为圆的直径长度),波浪线左端点到圆左端点为一段,圆中为一段,剩下的为一段。然后将波浪线的两端点与控件底部两点相连形成闭合路径。

然后将起始点A不断向右移动即可实现波浪的动画效果,而绿色部分的高度(进度)则是由相同的原理实现的,这里就不多做描述了。

代码展示

public class WaveView extends View {
    private Paint mPaintWavw;//波浪画笔
    private Paint mPaintProgress;//进度画笔
    private float mItemWaveLength;//波浪长度
    private float mRadius;//圆的半径
    private float mWaveHeight = 20;//波浪的高度
    private float mProgressTextSize = 130 ;//进度文字的大小
    private Path mPathWave;//波浪路径(不透明)
    private Path mPathWaveAlpha;//波浪路径(半透明)
    private Path mPathCircle;//圆形球路径
    private float mWave=0;//波浪偏移量(实现波浪效果的关键)
    private float mProgress = 0;//进度
    private Paint.FontMetricsInt mFontMetricsInt;
    private ObjectAnimator mWaveobjectAnimator;

    public WaveView(Context context) {
        this(context,null);
    }

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

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

    private void init() {
        setLayerType(LAYER_TYPE_SOFTWARE,null);
        mPaintWavw = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaintWavw.setColor(Color.parseColor("#33F92B"));
        mPaintWavw.setStyle(Paint.Style.FILL);

        mPaintProgress = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaintProgress.setStrokeWidth(10);
        mPaintProgress.setStrokeCap(Paint.Cap.ROUND);//设置笔帽为圆形
        mPaintProgress.setStrokeJoin(Paint.Join.ROUND);//设置拐角为圆形
        mPaintProgress.setColor(Color.WHITE);//将进度颜色设置为白色
        mPaintProgress.setTextAlign(Paint.Align.CENTER);

        mPathWave = new Path();
        mPathWaveAlpha = new Path();
        mPathCircle = new Path();
    }

    /**
     * 修改高度
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measureWidth(widthMeasureSpec),measuredHeight(heightMeasureSpec));
    }

    /**
     * 测量宽
     * @param widthMeasureSpec
     */
    private int measureWidth(int widthMeasureSpec) {
        int result ;
        int specMode = MeasureSpec.getMode(widthMeasureSpec);
        int specSize = MeasureSpec.getSize(widthMeasureSpec);
        if (specMode == MeasureSpec.EXACTLY){
            result = specSize;
        }else {
            result = 200;
            if (specMode == MeasureSpec.AT_MOST){
                result = Math.min(result,specSize);
            }
        }
        return result;
    }

    /**
     * 测量高
     * @param heightMeasureSpec
     */
    private int measuredHeight(int heightMeasureSpec) {
        int result ;
        int specMode = MeasureSpec.getMode(heightMeasureSpec);
        int specSize = MeasureSpec.getSize(heightMeasureSpec);
        if (specMode == MeasureSpec.EXACTLY){
            result = specSize;
        }else{
            result = 200;
            if(specMode == MeasureSpec.AT_MOST){
                result = Math.min(result,specSize);
            }
        }
        return  result;
    }

    /**
     * 当控件大小发生改变的时候对一些数值进行调整以适应控件大小
     * @param w
     * @param h
     * @param oldw
     * @param oldh
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mRadius = (float) (Math.min(w, h)*0.9/2);
        mItemWaveLength = mRadius *2;//一段波浪的长度
        mPathCircle.addCircle(w/2,h/2,mRadius, Path.Direction.CW);
        mProgressTextSize = mRadius*0.7f;//当控件大小发生变化时,动态修改文字大小
        mPaintProgress.setTextSize(mProgressTextSize);
        mFontMetricsInt = mPaintProgress.getFontMetricsInt();
        mWaveHeight = mRadius/8;//当控件大小发生变化时,动态修改波浪高度
        invalidate();
        startWaveAnim();
    }

    private void startWaveAnim() {
        mWaveobjectAnimator = ObjectAnimator.ofFloat(this, "wave", 0, mItemWaveLength).setDuration(4000);
        mWaveobjectAnimator.setRepeatCount(ValueAnimator.INFINITE);
        mWaveobjectAnimator.setInterpolator(new LinearInterpolator());
        mWaveobjectAnimator.start();
    }

    /**
     * 设置波浪偏移量(波浪的动画效果核心是靠这个实现的)
     * @param wave
     */
    public void setWave(float wave){
        mWave = wave;
        invalidate();
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.save();
        canvas.clipPath(mPathCircle);//将画布裁剪为圆形
        canvas.drawColor(Color.parseColor("#3233F92B"));
        mPathWave.reset();
        mPathWaveAlpha.reset();
        //将波浪路径起始点移至圆形球的左侧一段波浪长度(即上下起伏为一段)的位置处
        mPathWave.moveTo(getWidth()/2-mRadius-mItemWaveLength+mWave,getHeight()/2+mRadius+mWaveHeight-mProgress*(mRadius*2+mWaveHeight*2));
        mPathWaveAlpha.moveTo(getWidth()/2-mRadius-mItemWaveLength+mWave+mItemWaveLength/8,getHeight()/2+mRadius+mWaveHeight-mProgress*(mRadius*2+mWaveHeight*2));

        float half = mItemWaveLength / 4;
        for(float x= -mItemWaveLength;x<getWidth()+mItemWaveLength;x+=mItemWaveLength){
            mPathWave.rQuadTo(half/2,-mWaveHeight,half,0);//贝赛尔曲线实现波浪
            mPathWave.rQuadTo(half/2,mWaveHeight,half,0);
            mPathWaveAlpha.rQuadTo(half/2,-mWaveHeight,half,0);//贝赛尔曲线实现波浪
            mPathWaveAlpha.rQuadTo(half/2,mWaveHeight,half,0);
        }
        mPathWave.lineTo(getWidth(),getHeight());
        mPathWave.lineTo(0,getHeight());
        mPathWave.close();//制造闭合路径
        mPathWaveAlpha.lineTo(getWidth(),getHeight());
        mPathWaveAlpha.lineTo(0,getHeight());
        mPathWaveAlpha.close();//制造闭合路径
        mPaintWavw.setColor(Color.parseColor("#7a33F92B"));//设置后面的波浪为半透明
        canvas.drawPath(mPathWaveAlpha, mPaintWavw);
        mPaintWavw.setColor(Color.parseColor("#33F92B"));//设置前面的波浪为不透明
        canvas.drawPath(mPathWave, mPaintWavw);
        canvas.drawText((int)(mProgress*100)+"%",getWidth()/2,getHeight()/2+((mFontMetricsInt.bottom-mFontMetricsInt.top)/2-mFontMetricsInt.bottom),mPaintProgress);
        canvas.restore();
    }

    /**
     * 设置进度(不带动画)
     * @param progress
     */
    public void setProgress(float progress){
        mProgress = progress;
        invalidate();
    }

    /**
     * 设置进度带动画
     * @param progress
     */
    public void setProgressWithAnim(float progress){
        ObjectAnimator.ofFloat(this, "progress", 0, progress).setDuration(5000).start();
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if(mWaveobjectAnimator!=null){
            //当控件移除时取消动画
            mWaveobjectAnimator.cancel();
        }
    }

使用方法

这里只有两个方法:
setProgress(float progress):设置进度(不加上升动画)
setProgressWithAnim(float progress):设置进度(加上升动画)
项目源码:https://github.com/myml666/WaveDemo

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

推荐阅读更多精彩内容

  • 效果图如下: 一。view的组成 看到上图效果,大概可以确定这个view由三部分组成 1.绕一圈的正方形 2.中间...
    小郑阅读 383评论 0 0
  • 一、Unity简介 1. Unity界面 Shift + Space : 放大界面 Scene界面按钮渲染模式2D...
    MYves阅读 8,158评论 0 22
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,952评论 4 60
  • 【Android 动画】 动画分类补间动画(Tween动画)帧动画(Frame 动画)属性动画(Property ...
    Rtia阅读 6,066评论 1 38
  • 最近有一部很火的电视剧《我的前半生》,主线大概是家庭主妇被弃后逆流而上的故事。本来很平淡的生活,老婆的疑心太重,没...
    家家有999本难念的经阅读 762评论 0 0