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

来源:互联网 发布:数据缺失的解决方案 编辑:程序博客网 时间:2024/05/29 18:01

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

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下载地址

原创粉丝点击