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

目录

《Android自定义View实现炫酷的加速球效果》 目录.png

前言

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

效果展示

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

原理解析

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

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

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

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

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

然后将起始点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

    原文作者:itfitness
    原文地址: https://www.jianshu.com/p/752f71743007
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞