自定义View之--圆形进度条(流量球)

话不多说直接上效果和代码
这里写图片描述

package com.tuiliubao.tlb.view;

import android.animation.Animator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.RectF;
import android.graphics.Shader;
import android.os.Build;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.LinearInterpolator;

import com.tuiliubao.tlb.R;
import com.tuiliubao.tlb.utils.Constant;
import com.tuiliubao.tlb.utils.MiscUtil;

/**
 * 水波进度条
 * 双重水波纹效果
 * 可以自行设置外圈进度、水波颜色等
 * Created by Xl on 2017/2/26.
 */

public class WaveProgress extends View {

    private static final String TAG = WaveProgress.class.getSimpleName();

    //浅色波浪方向
    private static final int L2R = 0;
    private static final int R2L = 1;

    private int mDefaultSize;
    //圆心
    private Point mCenterPoint;
    //半径
    private float mRadius;
    //圆的外接矩形
    private RectF mRectF;
    //深色波浪移动距离
    private float mDarkWaveOffset;
    //浅色波浪移动距离
    private float mLightWaveOffset;
    //浅色波浪方向
    private boolean isR2L;
    //是否锁定波浪不随进度移动
    private boolean lockWave;

    //是否开启抗锯齿
    private boolean antiAlias;
    //最大值
    private float mMaxValue;
    //当前值
    private float mValue;
    //当前进度
    private float mPercent;

    //绘制提示
    private TextPaint mHintPaint;
    private CharSequence mHint;
    private int mHintColor;
    private float mHintSize;

    private Paint mPercentPaint;
    private float mValueSize;
    private int mValueColor;

    //圆环宽度
    private float mCircleWidth;
    //圆环
    private Paint mCirclePaint;
    //圆环颜色
    private int mCircleColor;
    //背景圆环颜色
    private int mBgCircleColor;

    //水波路径
    private Path mWaveLimitPath;
    private Path mWavePath;
    //水波高度
    private float mWaveHeight;
    //水波数量
    private int mWaveNum;
    //深色水波
    private Paint mWavePaint;
    //深色水波颜色
    private int mDarkWaveColor;
    //浅色水波颜色
    private int mLightWaveColor;

    //深色水波贝塞尔曲线上的起始点、控制点
    private Point[] mDarkPoints;
    //浅色水波贝塞尔曲线上的起始点、控制点
    private Point[] mLightPoints;

    //贝塞尔曲线点的总个数
    private int mAllPointCount;
    private int mHalfPointCount;

    private ValueAnimator mProgressAnimator;
    private long mDarkWaveAnimTime;
    private ValueAnimator mDarkWaveAnimator;
    private long mLightWaveAnimTime;
    private ValueAnimator mLightWaveAnimator;

    public WaveProgress(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        mDefaultSize = MiscUtil.dipToPx(context, Constant.DEFAULT_SIZE);
        mRectF = new RectF();
        mCenterPoint = new Point();

        initAttrs(context, attrs);
        initPaint();
        initPath();
    }

    private void initAttrs(Context context, AttributeSet attrs) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.WaveProgress);

        antiAlias = typedArray.getBoolean(R.styleable.WaveProgress_antiAlias, true);
        mDarkWaveAnimTime = typedArray.getInt(R.styleable.WaveProgress_darkWaveAnimTime, Constant.DEFAULT_ANIM_TIME);
        mLightWaveAnimTime = typedArray.getInt(R.styleable.WaveProgress_lightWaveAnimTime, Constant.DEFAULT_ANIM_TIME);
        mMaxValue = typedArray.getFloat(R.styleable.WaveProgress_maxValue, Constant.DEFAULT_MAX_VALUE);
        mValue = typedArray.getFloat(R.styleable.WaveProgress_value, Constant.DEFAULT_VALUE);
        mValueSize = typedArray.getDimension(R.styleable.WaveProgress_valueSize, Constant.DEFAULT_VALUE_SIZE);
        mValueColor = typedArray.getColor(R.styleable.WaveProgress_valueColor, Color.BLACK);

        mHint = typedArray.getString(R.styleable.WaveProgress_hint);
        mHintColor = typedArray.getColor(R.styleable.WaveProgress_hintColor, Color.BLACK);
        mHintSize = typedArray.getDimension(R.styleable.WaveProgress_hintSize, Constant.DEFAULT_HINT_SIZE);

        mCircleWidth = typedArray.getDimension(R.styleable.WaveProgress_circleWidth, Constant.DEFAULT_ARC_WIDTH - 5);
        mCircleColor = typedArray.getColor(R.styleable.WaveProgress_circleColor, Color.GREEN);
        mBgCircleColor = typedArray.getColor(R.styleable.WaveProgress_bgCircleColor, Color.WHITE);

        mWaveHeight = typedArray.getDimension(R.styleable.WaveProgress_waveHeight, Constant.DEFAULT_WAVE_HEIGHT);
        mWaveNum = typedArray.getInt(R.styleable.WaveProgress_waveNum, 1);
        mDarkWaveColor = typedArray.getColor(R.styleable.WaveProgress_darkWaveColor, Color.GREEN);
        mLightWaveColor = typedArray.getColor(R.styleable.WaveProgress_lightWaveColor,
                getResources().getColor(android.R.color.holo_green_light));

        isR2L = typedArray.getInt(R.styleable.WaveProgress_lightWaveDirect, R2L) == R2L;
        lockWave = typedArray.getBoolean(R.styleable.WaveProgress_lockWave, false);

        typedArray.recycle();
    }

    private void initPaint() {
        mHintPaint = new TextPaint();
        // 设置抗锯齿,会消耗较大资源,绘制图形速度会变慢。
        mHintPaint.setAntiAlias(antiAlias);
        // 设置绘制文字大小
        mHintPaint.setTextSize(mHintSize);
        // 设置画笔颜色
        mHintPaint.setColor(mHintColor);
        // 从中间向两边绘制,不需要再次计算文字
        mHintPaint.setTextAlign(Paint.Align.CENTER);

        mCirclePaint = new Paint();
        mCirclePaint.setAntiAlias(antiAlias);
        mCirclePaint.setStrokeWidth(mCircleWidth);
        mCirclePaint.setStyle(Paint.Style.STROKE);
        mCirclePaint.setStrokeCap(Paint.Cap.ROUND);

        mWavePaint = new Paint();
        mWavePaint.setAntiAlias(antiAlias);
        mWavePaint.setStyle(Paint.Style.FILL);
        // TODO: 2017/7/5 渐变色
        LinearGradient gradient = new LinearGradient(250, 0, 250, 300, Color.GREEN, getResources().getColor(R.color.light), Shader.TileMode.MIRROR);
        mWavePaint.setShader(gradient);


        mPercentPaint = new Paint();
        mPercentPaint.setTextAlign(Paint.Align.CENTER);
        mPercentPaint.setAntiAlias(antiAlias);
        mPercentPaint.setColor(mValueColor);
        mPercentPaint.setTextSize(mValueSize);
    }

    private void initPath() {
        mWaveLimitPath = new Path();
        mWavePath = new Path();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(MiscUtil.measure(widthMeasureSpec, mDefaultSize),
                MiscUtil.measure(heightMeasureSpec, mDefaultSize));
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        Log.d(TAG, "onSizeChanged: w = " + w + "; h = " + h + "; oldw = " + oldw + "; oldh = " + oldh);
        int minSize = Math.min(getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - 2 * (int) mCircleWidth,
                getMeasuredHeight() - getPaddingTop() - getPaddingBottom() - 2 * (int) mCircleWidth);
        mRadius = minSize / 2;
        mCenterPoint.x = getMeasuredWidth() / 2;
        mCenterPoint.y = getMeasuredHeight() / 2;
        //绘制圆弧的边界
        mRectF.left = mCenterPoint.x - mRadius - mCircleWidth / 2;
        mRectF.top = mCenterPoint.y - mRadius - mCircleWidth / 2;
        mRectF.right = mCenterPoint.x + mRadius + mCircleWidth / 2;
        mRectF.bottom = mCenterPoint.y + mRadius + mCircleWidth / 2;
        Log.d(TAG, "onSizeChanged: 控件大小 = " + "(" + getMeasuredWidth() + ", " + getMeasuredHeight() + ")"
                + ";圆心坐标 = " + mCenterPoint.toString()
                + ";圆半径 = " + mRadius
                + ";圆的外接矩形 = " + mRectF.toString());
        initWavePoints();
        //开始动画
        setValue(mValue);
        startWaveAnimator();
    }

    private void initWavePoints() {
        //当前波浪宽度
        float waveWidth = (mRadius * 2) / mWaveNum;
        mAllPointCount = 8 * mWaveNum + 1;
        mHalfPointCount = mAllPointCount / 2;
        mDarkPoints = getPoint(false, waveWidth);
        mLightPoints = getPoint(isR2L, waveWidth);
    }

    /**
     * 从左往右或者从右往左获取贝塞尔点
     *
     * @return
     */
    private Point[] getPoint(boolean isR2L, float waveWidth) {
        Point[] points = new Point[mAllPointCount];
        //第1个点特殊处理,即数组的中点
        points[mHalfPointCount] = new Point((int) (mCenterPoint.x + (isR2L ? mRadius : -mRadius)), mCenterPoint.y);
        //屏幕内的贝塞尔曲线点
        for (int i = mHalfPointCount + 1; i < mAllPointCount; i += 4) {
            float width = points[mHalfPointCount].x + waveWidth * (i / 4 - mWaveNum);
            points[i] = new Point((int) (waveWidth / 4 + width), (int) (mCenterPoint.y - mWaveHeight));
            points[i + 1] = new Point((int) (waveWidth / 2 + width), mCenterPoint.y);
            points[i + 2] = new Point((int) (waveWidth * 3 / 4 + width), (int) (mCenterPoint.y + mWaveHeight));
            points[i + 3] = new Point((int) (waveWidth + width), mCenterPoint.y);
        }
        //屏幕外的贝塞尔曲线点
        for (int i = 0; i < mHalfPointCount; i++) {
            int reverse = mAllPointCount - i - 1;
            points[i] = new Point((isR2L ? 2 : 1) * points[mHalfPointCount].x - points[reverse].x,
                    points[mHalfPointCount].y * 2 - points[reverse].y);
        }
        //对从右向左的贝塞尔点数组反序,方便后续处理
        return isR2L ? MiscUtil.reverse(points) : points;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawCircle(canvas);
        drawLightWave(canvas);
        drawDarkWave(canvas);
        drawProgress(canvas);
    }

    /**
     * 绘制圆环
     *
     * @param canvas
     */
    private void drawCircle(Canvas canvas) {
        canvas.save();
        canvas.rotate(270, mCenterPoint.x, mCenterPoint.y);
        int currentAngle = (int) (360 * mPercent);
        //画背景圆环
        mCirclePaint.setColor(mBgCircleColor);
        canvas.drawArc(mRectF, currentAngle, 360 - currentAngle, false, mCirclePaint);
        //画圆环
        mCirclePaint.setColor(mCircleColor);
        canvas.drawArc(mRectF, 0, 360, false, mCirclePaint);
        canvas.restore();
    }

    /**
     * 绘制深色波浪(贝塞尔曲线)
     *
     * @param canvas
     */
    private void drawDarkWave(Canvas canvas) {
        mWavePaint.setColor(mDarkWaveColor);
        drawWave(canvas, mWavePaint, mDarkPoints, mDarkWaveOffset);
    }

    /**
     * 绘制浅色波浪(贝塞尔曲线)
     *
     * @param canvas
     */
    private void drawLightWave(Canvas canvas) {
        mWavePaint.setColor(mLightWaveColor);
        //从右向左的水波位移应该被减去
        drawWave(canvas, mWavePaint, mLightPoints, isR2L ? -mLightWaveOffset : mLightWaveOffset);
    }

    @TargetApi(Build.VERSION_CODES.KITKAT)
    private void drawWave(Canvas canvas, Paint paint, Point[] points, float waveOffset) {
        mWaveLimitPath.reset();
        mWavePath.reset();
        float height = lockWave ? 0 : mRadius - 2 * mRadius * mPercent;
        //moveTo和lineTo绘制出水波区域矩形
        mWavePath.moveTo(points[0].x + waveOffset, points[0].y + height);

        for (int i = 1; i < mAllPointCount; i += 2) {
            mWavePath.quadTo(points[i].x + waveOffset, points[i].y + height,
                    points[i + 1].x + waveOffset, points[i + 1].y + height);
        }
        mWavePath.lineTo(points[mAllPointCount - 1].x, points[mAllPointCount - 1].y + height);
        //不管如何移动,波浪与圆路径的交集底部永远固定,否则会造成上移的时候底部为空的情况
        mWavePath.lineTo(points[mAllPointCount - 1].x, mCenterPoint.y + mRadius);
        mWavePath.lineTo(points[0].x, mCenterPoint.y + mRadius);
        mWavePath.close();
        mWaveLimitPath.addCircle(mCenterPoint.x, mCenterPoint.y, mRadius, Path.Direction.CW);
        //取该圆与波浪路径的交集,形成波浪在圆内的效果
        mWaveLimitPath.op(mWavePath, Path.Op.INTERSECT);
        canvas.drawPath(mWaveLimitPath, paint);
    }

    private void drawProgress(Canvas canvas) {
        float y = mCenterPoint.y - (mPercentPaint.descent() + mPercentPaint.ascent()) / 2;
        canvas.drawText(String.format("%.0f%%", mPercent * 100), mCenterPoint.x, y, mPercentPaint);

        if (mHint != null) {
            float hy = mCenterPoint.y * 2 / 3 - (mHintPaint.descent() + mHintPaint.ascent()) / 2;
            canvas.drawText(mHint.toString(), mCenterPoint.x, hy, mHintPaint);
        }
    }

    public float getMaxValue() {
        return mMaxValue;
    }

    public void setMaxValue(float maxValue) {
        mMaxValue = maxValue;
    }

    /**
     * 设置当前值
     *
     * @param value
     */
    public void setValue(float value) {
        if (value > mMaxValue) {
            value = mMaxValue;
        }
        float start = mPercent;
        float end = value / mMaxValue;
        Log.d(TAG, "setValue, value = " + value + ";start = " + start + "; end = " + end);
        startAnimator(start, end, mDarkWaveAnimTime);
    }

    private void startAnimator(final float start, float end, long animTime) {
        Log.d(TAG, "startAnimator,value = " + mValue
                + ";start = " + start + ";end = " + end + ";time = " + animTime);
        //当start=0且end=0时,不需要启动动画
        if (start == 0 && end == 0) {
            return;
        }
        mProgressAnimator = ValueAnimator.ofFloat(start, end);
        mProgressAnimator.setDuration(animTime);
        mProgressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mPercent = (float) animation.getAnimatedValue();
                if (mPercent == 0.0f || mPercent == 1.0f) {
                    stopWaveAnimator();
                } else {
                    startWaveAnimator();
                }
                mValue = mPercent * mMaxValue;
//                if (BuildConfig.DEBUG) {
//                    Log.d(TAG, "onAnimationUpdate: percent = " + mPercent
//                            + ";value = " + mValue);
//                }
                invalidate();
            }
        });
        mProgressAnimator.start();
    }

    private void startWaveAnimator() {
        startLightWaveAnimator();
        startDarkWaveAnimator();
    }

    private void stopWaveAnimator() {
        if (mDarkWaveAnimator != null && mDarkWaveAnimator.isRunning()) {
            mDarkWaveAnimator.cancel();
            mDarkWaveAnimator = null;
        }
        if (mLightWaveAnimator != null && mLightWaveAnimator.isRunning()) {
            mLightWaveAnimator.cancel();
            mLightWaveAnimator = null;
        }
    }

    private void startLightWaveAnimator() {
        if (mLightWaveAnimator != null && mLightWaveAnimator.isRunning()) {
            return;
        }
        mLightWaveAnimator = ValueAnimator.ofFloat(0, 2 * mRadius);
        mLightWaveAnimator.setDuration(mLightWaveAnimTime);
        mLightWaveAnimator.setRepeatCount(ValueAnimator.INFINITE);
        mLightWaveAnimator.setInterpolator(new LinearInterpolator());
        mLightWaveAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mLightWaveOffset = (float) animation.getAnimatedValue();
                postInvalidate();
            }
        });
        mLightWaveAnimator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
                mLightWaveOffset = 0;
            }

            @Override
            public void onAnimationEnd(Animator animation) {

            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
        mLightWaveAnimator.start();
    }

    private void startDarkWaveAnimator() {
        if (mDarkWaveAnimator != null && mDarkWaveAnimator.isRunning()) {
            return;
        }
        mDarkWaveAnimator = ValueAnimator.ofFloat(0, 2 * mRadius);
        mDarkWaveAnimator.setDuration(mDarkWaveAnimTime);
        mDarkWaveAnimator.setRepeatCount(ValueAnimator.INFINITE);
        mDarkWaveAnimator.setInterpolator(new LinearInterpolator());
        mDarkWaveAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mDarkWaveOffset = (float) animation.getAnimatedValue();
                postInvalidate();
            }
        });
        mDarkWaveAnimator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
                mDarkWaveOffset = 0;
            }

            @Override
            public void onAnimationEnd(Animator animation) {

            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
        mDarkWaveAnimator.start();
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        stopWaveAnimator();
        if (mProgressAnimator != null && mProgressAnimator.isRunning()) {
            mProgressAnimator.cancel();
        }
    }
}

Style代码

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- 是否开启抗锯齿 -->
    <attr name="antiAlias" format="boolean" />
    <!-- 圆弧起始角度,3点钟方向为0,顺时针递增,小于0或大于360进行取余 -->
    <attr name="startAngle" format="float" />
    <!-- 圆弧度数 -->
    <attr name="sweepAngle" format="float" />
    <!-- 设置动画时间 -->
    <attr name="animTime" format="integer" />
    <!-- 绘制内容的数值 -->
    <attr name="maxValue" format="float" />
    <attr name="value" format="float" />
    <!-- 绘制内容的单位 -->
    <attr name="unit" format="string|reference" />
    <attr name="unitSize" format="dimension" />
    <attr name="unitColor" format="color|reference" />
    <!-- 绘制内容相应的提示语 -->
    <attr name="hint" format="string|reference" />
    <attr name="hintSize" format="dimension" />
    <attr name="hintColor" format="color|reference" />
    <!-- 精度,默认为0 -->
    <attr name="precision" format="integer" />
    <attr name="valueSize" format="dimension" />
    <attr name="valueColor" format="color|reference" />
    <!-- 圆弧颜色,设置多个可实现渐变 -->
    <attr name="arcColor1" format="color|reference" />
    <attr name="arcColor2" format="color|reference" />
    <attr name="arcColor3" format="color|reference" />
    <!-- 背景圆弧颜色,默认白色 -->
    <attr name="bgArcColor" format="color|reference" />
    <!-- 圆弧宽度 -->
    <attr name="arcWidth" format="dimension" />
    <!-- 圆弧颜色, -->
    <attr name="arcColors" format="color|reference" />
    <!-- 文字的偏移量。相对于圆半径而言,默认三分之一 -->
    <attr name="textOffsetPercentInRadius" format="float" />

    <!-- 圆形进度条 -->
    <declare-styleable name="CircleProgressBar">
        <attr name="antiAlias" />
        <attr name="startAngle" />
        <attr name="sweepAngle" />
        <attr name="animTime" />
        <attr name="maxValue" />
        <attr name="value" />
        <attr name="precision" />
        <attr name="valueSize" />
        <attr name="valueColor" />
        <attr name="textOffsetPercentInRadius" />
        <!-- 绘制内容相应的提示语 -->
        <attr name="hint" />
        <attr name="hintSize" />
        <attr name="hintColor" />
        <!-- 绘制内容的单位 -->
        <attr name="unit" />
        <attr name="unitSize" />
        <attr name="unitColor" />
        <!-- 圆弧宽度 -->
        <attr name="arcWidth" />
        <attr name="arcColors" />
        <!-- 背景圆弧颜色 -->
        <attr name="bgArcColor" />
        <!-- 背景圆弧宽度 -->
        <attr name="bgArcWidth" format="dimension" />
    </declare-styleable>

    <declare-styleable name="DialProgress">
        <attr name="antiAlias" />
        <attr name="startAngle" />
        <attr name="sweepAngle" />
        <attr name="animTime" />
        <attr name="maxValue" />
        <attr name="value" />
        <attr name="precision" />
        <attr name="valueSize" />
        <attr name="valueColor" />
        <attr name="textOffsetPercentInRadius" />
        <!-- 绘制内容的单位 -->
        <attr name="unit" />
        <attr name="unitSize" />
        <attr name="unitColor" />
        <!-- 绘制内容相应的提示语 -->
        <attr name="hint" />
        <attr name="hintSize" />
        <attr name="hintColor" />
        <!-- 圆弧的宽度 -->
        <attr name="arcWidth" />
        <!-- 刻度的宽度 -->
        <attr name="dialWidth" format="dimension|reference" />
        <!-- 刻度之间的间隔 -->
        <attr name="dialIntervalDegree" format="integer" />
        <!-- 圆弧颜色, -->
        <attr name="arcColors" />
        <!-- 背景圆弧线颜色 -->
        <attr name="bgArcColor" />
        <!-- 刻度线颜色 -->
        <attr name="dialColor" format="color|reference" />
    </declare-styleable>

    <declare-styleable name="WaveProgress">
        <!-- 是否开启抗锯齿 -->
        <attr name="antiAlias" />
        <!-- 深色水波动画时间 -->
        <attr name="darkWaveAnimTime" format="integer" />
        <!-- 浅色水波动画时间 -->
        <attr name="lightWaveAnimTime" format="integer" />
        <!-- 最大值 -->
        <attr name="maxValue" />
        <!-- 当前值 -->
        <attr name="value" />
        <attr name="valueColor" />
        <attr name="valueSize" />
        <!-- 绘制内容相应的提示语 -->
        <attr name="hint" />
        <attr name="hintSize" />
        <attr name="hintColor" />
        <!-- 圆环宽度 -->
        <attr name="circleWidth" format="dimension" />
        <!-- 圆环颜色 -->
        <attr name="circleColor" format="color|reference" />
        <!-- 背景圆环颜色 -->
        <attr name="bgCircleColor" format="color|reference" />
        <!-- 锁定水波不随圆环进度改变,默认锁定在50%处 -->
        <attr name="lockWave" format="boolean" />
        <!-- 水波数量 -->
        <attr name="waveNum" format="integer" />
        <!-- 水波高度,峰值和谷值之和 -->
        <attr name="waveHeight" format="dimension" />
        <!-- 深色水波颜色 -->
        <attr name="darkWaveColor" format="color|reference" />
        <!-- 是否显示浅色水波 -->
        <attr name="showLightWave" format="boolean" />
        <!-- 浅色水波颜色 -->
        <attr name="lightWaveColor" format="color|reference" />
        <!-- 浅色水波的方向 -->
        <attr name="lightWaveDirect" format="enum">
            <enum name="L2R" value="0" />
            <enum name="R2L" value="1" />
        </attr>
    </declare-styleable>

    <declare-styleable name="PieView">
        <attr name="centerTextSize" format="dimension" />
        <attr name="dataTextSize" format="dimension" />
        <attr name="centerTextColor" format="color" />
        <attr name="dataTextColor" format="color" />
    </declare-styleable>
</resources>

这里写图片描述
这里写图片描述

package com.tuiliubao.tlb.view;

import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DrawFilter;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.Path;
import android.graphics.RectF;
import android.graphics.Shader;
import android.os.Build;
import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ProgressBar;

import com.tuiliubao.tlb.R;
import com.tuiliubao.tlb.utils.UiUtils;


/**
    单波水波纹效果进度球
 */
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public class PercentageBallView extends View {

    private Context mContext;

    private int mScreenWidth;
    private int mScreenHeight;

    private Paint mRingPaint;
    private Paint mCirclePaint;
    private Paint mWavePaint;
    private Paint flowPaint;

    private int mRingSTROKEWidth = 8;
    private int mCircleSTROKEWidth = 8;
    private int mLineSTROKEWidth = 1;

    private Handler mHandler;
    private long c = 0L;
    private boolean mStarted = false;
    private final float f = 0.033F;
    private float mAmplitude = 0.0F; // 振幅
    private float mWaterLevel = 0.0F;// 水高(0~1)
    private Path mPath;
    // 绘制文字显示在圆形中间,只是我没有设置,我觉得写在布局上也挺好的
    private String flowNum = "";// 2/10

    private int mGreenStart = getResources().getColor(R.color.green1);
    private int mGreenEnd = getResources().getColor(R.color.green2);
    int startX, endX, startY;
    // y = Asin(wx+b)+h
    private static final float STRETCH_FACTOR_A = 20;
    private static final int OFFSET_Y = 0;
    // 第一条水波移动速度
    private static final int TRANSLATE_X_SPEED_ONE = 5;
    // 第二条水波移动速度
    private static final int TRANSLATE_X_SPEED_TWO = 4;
    private float mCycleFactorW;
    private float[] mYPositions;
    private float[] mResetOneYPositions;
    private float[] mResetTwoYPositions;
    private int mXOffsetSpeedOne;
    private int mXOffsetSpeedTwo;
    private int mXOneOffset;
    private int mXTwoOffset;
    private DrawFilter mDrawFilter;

    /**
     * @param context
     */
    public PercentageBallView(Context context) {
        super(context);
        // TODO Auto-generated constructor stub
        mContext = context;
        init(mContext);
    }

    /**
     * @param context
     * @param attrs
     */
    public PercentageBallView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
        mContext = context;

        init(mContext);
    }

    /**
     * @param context
     * @param attrs
     * @param defStyleAttr
     */
    public PercentageBallView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
        init(mContext);
    }

    public void setmWaterLevel(float mWaterLevel, String str) {
        this.mWaterLevel = mWaterLevel;
        this.flowNum = str;
    }

    public void setmWaterWave(float mWaterLevel, String str, float mWater) {
        this.mWaterLevel = mWaterLevel;
        this.flowNum = str;
        this.mAmplitude = mWater;
    }

    private void init(Context context) {
        // 将dp转化为px,用于控制不同分辨率上移动速度基本一致
        mXOffsetSpeedOne = UiUtils.dipToPx(context, TRANSLATE_X_SPEED_ONE);
        mXOffsetSpeedTwo = UiUtils.dipToPx(context, TRANSLATE_X_SPEED_TWO);

        // 初始绘制波纹的画笔
        mWavePaint = new Paint();
        // 去除画笔锯齿
        mWavePaint.setAntiAlias(true);
        // 设置风格为实线
        mWavePaint.setStyle(Paint.Style.FILL);
        // 设置画笔颜色
        mWavePaint.setColor(mGreenStart);
        mDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
        // 外圈
        mRingPaint = new Paint();
        mRingPaint.setColor(mGreenEnd);
        mRingPaint.setStyle(Paint.Style.STROKE);
        mRingPaint.setAntiAlias(true);
        mRingPaint.setStrokeWidth(mRingSTROKEWidth);

        // 内圈
        mCirclePaint = new Paint();
        mCirclePaint.setColor(Color.WHITE);
        mCirclePaint.setStyle(Paint.Style.STROKE);
        mCirclePaint.setAntiAlias(true);
        mCirclePaint.setStrokeWidth(mCircleSTROKEWidth);
        // 文字
        flowPaint = new Paint();
        flowPaint.setColor(Color.WHITE);
        flowPaint.setStyle(Paint.Style.FILL);
        flowPaint.setAntiAlias(true);
        flowPaint.setTextSize(34);

        // 内填充
        mWavePaint = new Paint();
        mWavePaint.setStrokeWidth(1.0F);
        mWavePaint.setColor(mGreenEnd);
        LinearGradient gradient = new LinearGradient(250, 0, 250, 300, mGreenEnd, mGreenStart, Shader.TileMode.MIRROR);
        mWavePaint.setShader(gradient);

        mPath = new Path();

        mHandler = new Handler() {
            @Override
            public void handleMessage(android.os.Message msg) {
                if (msg.what == 0) {
                    invalidate();
                    if (mStarted) {
                        // 不断发消息给自己,使自己不断被重绘
                        mHandler.sendEmptyMessageDelayed(0, 60L);
                    }
                }
            }
        };
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int width = measure(widthMeasureSpec, true);
        int height = measure(heightMeasureSpec, false);
        if (width < height) {
            setMeasuredDimension(width, width);
        } else {
            setMeasuredDimension(height, height);
        }

    }

    /**
     * @param measureSpec
     * @param isWidth
     * @return
     * @category 测量
     */
    private int measure(int measureSpec, boolean isWidth) {
        int result;
        int mode = MeasureSpec.getMode(measureSpec);
        int size = MeasureSpec.getSize(measureSpec);
        int padding = isWidth ? getPaddingLeft() + getPaddingRight() : getPaddingTop() + getPaddingBottom();
        if (mode == MeasureSpec.EXACTLY) {
            result = size;
        } else {
            result = isWidth ? getSuggestedMinimumWidth() : getSuggestedMinimumHeight();
            result += padding;
            if (mode == MeasureSpec.AT_MOST) {
                if (isWidth) {
                    result = Math.max(result, size);
                } else {
                    result = Math.min(result, size);
                }
            }
        }
        return result;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        // TODO Auto-generated method stub
        super.onSizeChanged(w, h, oldw, oldh);
        mScreenWidth = w;
        mScreenHeight = h;
        // 用于保存原始波纹的y值
        mYPositions = new float[mScreenWidth];
        // 用于保存波纹一的y值
        mResetOneYPositions = new float[mScreenWidth];
        // 用于保存波纹二的y值
        mResetTwoYPositions = new float[mScreenWidth];

        // 将周期定为view总宽度
        mCycleFactorW = (float) (2 * Math.PI / mScreenWidth);

        // 根据view总宽度得出所有对应的y值
        for (int i = 0; i < mScreenWidth; i++) {
            mYPositions[i] = (float) (STRETCH_FACTOR_A * Math.sin(mCycleFactorW * i) + OFFSET_Y);
        }
    }

    @Override
    protected void onDraw(final Canvas canvas) {
        // TODO Auto-generated method stub
        super.onDraw(canvas);
        // 得到控件的宽高
        int width = getWidth();
        int height = getHeight();
        setBackgroundColor(Color.WHITE);// 可以自定义色值
        // 计算当前油量线和水平中线的距离
        float centerOffset = Math.abs(mScreenWidth / 2 * mWaterLevel - mScreenWidth / 4);
        // 计算油量线和与水平中线的角度
        float horiAngle = (float) (Math.asin(centerOffset / (mScreenWidth / 4)) * 180 / Math.PI);
        // 扇形的起始角度和扫过角度
        float startAngle, sweepAngle;
        if (mWaterLevel > 0.5F) {
            startAngle = 360F - horiAngle;
            sweepAngle = 180F + 2 * horiAngle;
        } else {
            startAngle = horiAngle;
            sweepAngle = 180F - 2 * horiAngle;
        }

        float num = flowPaint.measureText(flowNum);

        // 如果未开始(未调用startWave方法),绘制一个扇形
        if ((!mStarted) || (mScreenWidth == 0) || (mScreenHeight == 0)) {
            // 绘制,即水面静止时的高度
            RectF oval = new RectF(mScreenWidth / 4, mScreenHeight / 4, mScreenWidth * 3 / 4, mScreenHeight * 3 / 4);
            canvas.drawArc(oval, startAngle, sweepAngle, false, mWavePaint);
            return;
        }
        // 绘制,即水面静止时的高度
        RectF oval = new RectF(mScreenWidth / 4, mScreenHeight / 4, mScreenWidth * 3 / 4, mScreenHeight * 3 / 4);
        canvas.drawArc(oval, startAngle, sweepAngle, false, mWavePaint);

        if (this.c >= 8388607L) {
            this.c = 0L;
        }
        // 每次onDraw时c都会自增
        c = (1L + c);
        float f1 = mScreenHeight * (1.0F - (0.25F + mWaterLevel / 2)) - mAmplitude;
        // 当前油量线的长度
        float waveWidth = (float) Math.sqrt(mScreenWidth * mScreenWidth / 16 - centerOffset * centerOffset);
        // 与圆半径的偏移量
        float offsetWidth = mScreenWidth / 4 - waveWidth;

        final int top = (int) (f1 + mAmplitude);
        mPath.reset();


        // 起始振动X坐标,结束振动X坐标
        if (mWaterLevel > 0.50F) {
            startX = (int) (mScreenWidth / 4 + offsetWidth);
            endX = (int) (mScreenWidth / 2 + mScreenWidth / 4 - offsetWidth);
            startY = (int) (f1 - mAmplitude * Math.sin(Math.PI * (2.0F * (startX + this.c * width * this.f)) / width));
        } else {
            startX = (int) (mScreenWidth / 4 + offsetWidth - mAmplitude);
            endX = (int) (mScreenWidth / 2 + mScreenWidth / 4 - offsetWidth + mAmplitude);
        }
        // 波浪效果
//        while (startX < endX) {
//            startY = (int) (f1 - mAmplitude * Math.sin(Math.PI * (2.0F * (startX + this.c * width * this.f)) / width));
//            canvas.drawLine(startX, startY, startX, top, mWavePaint);
//            startX++;
//        }

//        RectF rectf = new RectF(110, 110, 340, 340);
//        canvas.drawArc(rectf, 320, 329, false, mRingPaint);
        canvas.drawCircle(mScreenWidth / 2, mScreenHeight / 2, mScreenWidth / 4 + mRingSTROKEWidth / 2, mRingPaint);

        canvas.drawCircle(mScreenWidth / 2, mScreenHeight / 2, mScreenWidth / 4, mCirclePaint);

        // 放到这里绘制文字,不然会因为前面的绘图遮挡住文字
        canvas.drawText(flowNum, mScreenWidth * 4 / 8 - num / 2, mScreenHeight * 4 / 8, flowPaint);
        canvas.restore();

        /**              双重波纹实现                **/
        canvas.setDrawFilter(mDrawFilter);
        resetPositonY();
        for (int i = 0; i < mScreenWidth; i++) {

            // 减400只是为了控制波纹绘制的y的在屏幕的位置,大家可以改成一个变量,然后动态改变这个变量,从而形成波纹上升下降效果
            // 绘制第一条水波纹
            canvas.drawLine(i, mScreenHeight - mResetOneYPositions[i] - 400, i, mScreenHeight,mWavePaint);

            // 绘制第二条水波纹
            canvas.drawLine(i, mScreenHeight - mResetTwoYPositions[i] - 400, i, mScreenHeight, mWavePaint);
        }

        // 改变两条波纹的移动点
        mXOneOffset += mXOffsetSpeedOne;
        mXTwoOffset += mXOffsetSpeedTwo;

        // 如果已经移动到结尾处,则重头记录
        if (mXOneOffset >= mScreenWidth) {
            mXOneOffset = 0;
        }
        if (mXTwoOffset > mScreenWidth) {
            mXTwoOffset = 0;
        }

        // 引发view重绘,一般可以考虑延迟20-30ms重绘,空出时间片
        postInvalidate();
    }

    @Override
    public Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();
        SavedState ss = new SavedState(superState);
        ss.progress = (int) c;
        return ss;
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        SavedState ss = (SavedState) state;
        super.onRestoreInstanceState(ss.getSuperState());
        c = ss.progress;
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        // 关闭硬件加速,防止异常unsupported operation exception
        this.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
    }

    /**
     * 开始波动
     */
    public void startWave() {
        if (!mStarted) {
            this.c = 0L;
            mStarted = true;
            this.mHandler.sendEmptyMessage(0);
        }
    }

    /**
     * @category 停止波动
     */
    public void stopWave() {
        if (mStarted) {
            this.c = 0L;
            mStarted = false;
            this.mHandler.removeMessages(0);
        }
    }

    /**
     * @category 保存状态
     */
    static class SavedState extends BaseSavedState {
        int progress;

        /**
         * Constructor called from {@link ProgressBar#onSaveInstanceState()}
         */
        SavedState(Parcelable superState) {
            super(superState);
        }

        /**
         * Constructor called from {@link #CREATOR}
         */
        private SavedState(Parcel in) {
            super(in);
            progress = in.readInt();
        }

        @Override
        public void writeToParcel(Parcel out, int flags) {
            super.writeToParcel(out, flags);
            out.writeInt(progress);
        }

        public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
            public SavedState createFromParcel(Parcel in) {
                return new SavedState(in);
            }

            public SavedState[] newArray(int size) {
                return new SavedState[size];
            }
        };
    }

    private void resetPositonY() {
        // mXOneOffset代表当前第一条水波纹要移动的距离
        int yOneInterval = mYPositions.length - mXOneOffset;
        // 使用System.arraycopy方式重新填充第一条波纹的数据
        System.arraycopy(mYPositions, mXOneOffset, mResetOneYPositions, 0, yOneInterval);
        System.arraycopy(mYPositions, 0, mResetOneYPositions, yOneInterval, mXOneOffset);

        int yTwoInterval = mYPositions.length - mXTwoOffset;
        System.arraycopy(mYPositions, mXTwoOffset, mResetTwoYPositions, 0,
                yTwoInterval);
        System.arraycopy(mYPositions, 0, mResetTwoYPositions, yTwoInterval, mXTwoOffset);
    }
}

代码下载地址
CSDN下载地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值