最近項目中需要實作動态水波球的效果,在網上看了一些都不太理想。就自己簡單寫了一個ProgressCircleView,期待大神拍磚!我這裡繼承的TextView,主要是友善在中間顯示目前值。
首先有圖有真相:
3.0及其之後版本的效果
3.0之前版本的效果
效果差異主要取決于如下的代碼,因為3.0以後google預設開啟了硬體加速導緻canvas.clipPath(path);沒有效果,是以在這裡隻能先區分處理。高版本的畫了一個前景來處理,如有更好的辦法還請指教。
protected void onDraw(Canvas canvas) {
if (mHeight == 0) {
// 初始化
init();
// 開始波動
start();
}
if (android.os.Build.VERSION.SDK_INT > 10) {
// 3.0 或更高版本
drawForegroundCircle(canvas);
} else {
// 3.0以下版本
clipCircle(canvas);
}
super.onDraw(canvas);
}
主要的是這個方法用Path.rQuadTo去畫賽貝爾曲線(Path.rQuadTo和Path.quadTo的差別網上有很多這裡就不再贅述了):
/**
* 畫出水波線
* */
private Path dealPath() {
Path path = new Path();
path.moveTo(mStartX, mStartY);
for (int i = 0 ; i < mXs.length ; i++) {
path.rQuadTo(mXs[i], mYs[i], mXs[i]<<1, 0);
}
path.rQuadTo(mXs[0], mYs[0], mXs[0]<<1, 0);
path.rQuadTo(mXs[1], mYs[1], mXs[1]<<1, 0);
path.lineTo(mWidth, mHeight);
path.lineTo(0, mHeight);
path.lineTo(mStartX, mStartY);
path.close();
return path;
}
在Activity中使用的方法:
pcv0 = (ProgressCircleView) findViewById(R.id.progressCircleView4);
pcv0.setProgress(0.5f);
pcv0.setText("50%");
已下是ProgressCircleView的完整代碼:
/**
* 水波紋進度球
* */
public class ProgressCircleView extends TextView {
private int mHeight; //控件寬高
private int mWidth; //
private int mStartX; //起始位置坐标
private int mStartY; //
private Paint mPaint; // 邊框畫筆
private Paint mQuadPaint; // 水波紋畫筆
private Paint mForePaint; // 前景色畫筆
private int[] mXs;
private int[] mYs;
private int mStrokeWidth = 4; // 邊框寬度
private int mTranslationUnit; // 位移機關
/** 水波紋總寬度 */
private int mTotle;
/** 目前進度值 */
private float mProgress = 0.5f;
/** 目前進度對應的顔色值 */
private int mCurProgressColor = 0xFF00BE3C;
/** 漸變顔色 */
private int[] mProgressColors = new int[]{Color.RED, Color.BLUE, Color.CYAN, Color.GREEN};
public ProgressCircleView(Context context) {
super(context);
}
public ProgressCircleView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ProgressCircleView(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**
* 設定進度值
* @param progress float 取值範圍:0.1 - 1.0
* */
public void setProgress(float progress) {
this.mProgress = 1 - progress;
mCurProgressColor = interpRectColor(mProgressColors, progress); // 得到目前色值
mHeight = 0;
invalidate();
}
/**
* 初始化 得到一些資料
* */
private void init() {
mHeight = getHeight();
mWidth = getWidth();
mStartX = mStrokeWidth;
mStartY = (int) (mHeight * mProgress);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(Color.parseColor("#EEEEEE"));
mPaint.setStyle(Paint.Style.FILL);//設定為空心
mQuadPaint = new Paint();
mQuadPaint.setAntiAlias(true);
mQuadPaint.setColor(mCurProgressColor);
mQuadPaint.setStyle(Paint.Style.FILL);//設定實心
mForePaint = new Paint();
mForePaint.setAntiAlias(true);
mForePaint.setColor(Color.WHITE);
mForePaint.setStyle(Paint.Style.FILL);//設定實心
mPaint.setStrokeWidth(mStrokeWidth);
// 波紋坐标
mTotle = 0;
float scale = mProgress > 0.5f ? (1 - mProgress) * 2 : mProgress * 2;
float[] xFs = new float[]{0.35f, 0.2f, 0.45f, 0.6f};
float[] yFs = new float[]{0.15f, -0.12f, 0.2f, -0.25f};
mXs = new int[xFs.length];
mYs = new int[yFs.length];
for (int i = 0 ; i < xFs.length ; i++) {
mXs[i] = (int) (mWidth * xFs[i]);
mYs[i] = (int) (mHeight * yFs[i] * scale);
mTotle += mXs[i];
}
mTotle += mXs[0];
mTotle += mXs[1];
mTranslationUnit = mWidth / 50;
}
@SuppressLint("DrawAllocation") @Override
protected void onDraw(Canvas canvas) {
if (mHeight == 0) {
if (mWidth == 0) {
// 開始波動
start();
}
// 初始化
init();
}
if (android.os.Build.VERSION.SDK_INT > 10) {
// 3.0 或更高版本
drawForegroundCircle(canvas);
} else {
// 3.0以下版本
clipCircle(canvas);
}
super.onDraw(canvas);
}
private void clipCircle(Canvas canvas) {
canvas.save();
Path path = new Path();
path.addCircle(mWidth / 2, mHeight / 2, mWidth / 2 - (mStrokeWidth>>1), Direction.CW);
canvas.clipPath(path);
canvas.drawCircle(mWidth / 2, mHeight / 2, mWidth / 2, mPaint);
canvas.drawPath(dealPath(), mQuadPaint);
// canvas.drawCircle(mWidth / 2, mHeight / 2, mWidth / 2 - mStrokeWidth, mPaint);
canvas.restore();
}
private void drawForegroundCircle(Canvas canvas) {
Path path = new Path();
float radius = mWidth / 2f;
float panding = mStrokeWidth / 2f;
path = new Path();
path.moveTo(0, radius);
path.lineTo(0, 0);
path.lineTo(mWidth, 0);
path.lineTo(mWidth, mHeight);
path.lineTo(0, mHeight);
path.lineTo(0, radius);
//圓弧左邊中間起點是180,旋轉360度
path.arcTo(new RectF(panding, panding, mWidth - panding, mHeight - panding), 180, -359, true);
path.close();
canvas.drawCircle(mWidth / 2, mHeight / 2, mWidth / 2, mPaint);
canvas.drawPath(dealPath(), mQuadPaint);
// canvas.drawCircle(mWidth / 2, mHeight / 2, mWidth / 2 - mStrokeWidth, mPaint);
canvas.drawPath(path, mForePaint);
}
/**
* 畫出水波線
* */
private Path dealPath() {
Path path = new Path();
path.moveTo(mStartX, mStartY);
for (int i = 0 ; i < mXs.length ; i++) {
path.rQuadTo(mXs[i], mYs[i], mXs[i]<<1, 0);
}
path.rQuadTo(mXs[0], mYs[0], mXs[0]<<1, 0);
path.rQuadTo(mXs[1], mYs[1], mXs[1]<<1, 0);
path.lineTo(mWidth, mHeight);
path.lineTo(0, mHeight);
path.lineTo(mStartX, mStartY);
path.close();
return path;
}
/**
* 開始波動效果
* */
private void start() {
mStartX -= mTranslationUnit;
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
start();
}
}, 20);
invalidate();
if (-mStartX >= mTotle + mWidth + 1.2f * mStrokeWidth) {
mStartX = mStrokeWidth;
}
}
/**
* 擷取漸變顔色
* @param colors
* @param x
* @return
*/
private int interpRectColor(int[] colors, float p) {
if (p == 0) {
return colors[0];
}
int size = colors.length - 1;
float cp, up;
int a, r, g, b, cur;
up = 1f / size;
cur = (int) (p / up);
cp = (p - cur * up) / up;
if (cp == 0) {
cur--;
cp = 1;
}
a = ave(Color.alpha(colors[cur]), Color.alpha(colors[cur+1]), cp);
r = ave(Color.red(colors[cur]), Color.red(colors[cur+1]), cp);
g = ave(Color.green(colors[cur]), Color.green(colors[cur+1]), cp);
b = ave(Color.blue(colors[cur]), Color.blue(colors[cur+1]), cp);
return Color.argb(a, r, g, b);
}
private int ave(int s, int d, float p) {
return s + Math.round(p * (d - s));
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mHeight = 0;
invalidate();
}
}