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代码
<resources>
<attr name="antiAlias" format="boolean" />
<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" />
<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" />
<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 CREATOR = new Parcelable.Creator() {
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下载地址