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

作者: itfitness | 来源:发表于2019-02-26 17:29 被阅读223次

    目录

    目录.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

    相关文章

      网友评论

        本文标题:Android自定义View实现炫酷的加速球效果

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