往常惯例,先上图, 不会的童鞋真的只需要十分钟就可以学会
效果比较简单,就是绘制6段圆弧、然后指针的旋转、弧线型的下标文字,中心的数字随进度条变化。
这里我直接贴 onDraw 里面的方法了。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawInstrument(canvas);//进度底色
drawText(canvas);//进度条下标文字
drawPointer(canvas);//指针
drawSecond(canvas);//第二层
drawCalorie(canvas);//绘制中间的卡路里
}
接下来就一个一个方法看吧:
1、drawInstrument():
/*
* 绘制6段圆弧
* */
private void drawInstrument(Canvas canvas) {
//70度一个弧形,中间隔5度
canvas.drawArc(mBigRectF, offset[0], 70, false, mPaintBig);//绘制圆弧,不含圆心
canvas.drawArc(mBigRectF, offset[1], 70, false, mPaintBig);//绘制圆弧,不含圆心
canvas.drawArc(mBigRectF, offset[2], 70, false, mPaintBig);//绘制圆弧,不含圆心
canvas.drawArc(mSmallRectF, offset[0], 70, false, mPaintSmall);//绘制圆弧,不含圆心
canvas.drawArc(mSmallRectF, offset[1], 70, false, mPaintSmall);//绘制圆弧,不含圆心
canvas.drawArc(mSmallRectF, offset[2], 70, false, mPaintSmall);//绘制圆弧,不含圆心
}
就是 canvas 类里面一个基本方法 canvas.drawArc()绘制圆弧,第一个参数是矩阵(控制圆弧的区域),第二个参数是开始时候得角度(UI给的),第三个参数70是圆弧的度数,第四个参数是是否绘制圆心,第五个参数是画笔。
其中画笔是在构造方法里面初始化,代码我就不在这贴了。矩阵是在 onMeasure 里面初始化,代码如下:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidth = MeasureSpec.getSize(widthMeasureSpec);
mHeight = MeasureSpec.getSize(heightMeasureSpec);
int i = (mWidth - mHeight) / 2;
mBigRectF = new RectF(10 + i, 10, mWidth - 10 - i, mHeight - 10);//外圆弧
mTextRectF = new RectF(40 + i, 40, mWidth - 40 - i, mHeight - 40);//文字弧线
mSmallRectF = new RectF(70 + i, 70, mWidth - 70 - i, mHeight - 70);//内圆弧
mBigRectF.offset(0, 80);
mSmallRectF.offset(0, 80);
mTextRectF.offset(0, 80);
mMatrix = new Matrix();
mMatrix.postTranslate(mWidth / 2 - mGreenBitmap.getWidth() / 2, mHeight / 4);
}
在这里 初始化了三个 RectF,用户绘制弧相关,Matrix是用与指针旋转的,后面我会说明。
噢,对了,这里有个疑问,在 onMeasure 方法里面初始化的这三个变量,在 studio 里面报黄色警告,说避免在 draw/layout 期间初始化变量,但是要获取到控件的宽高之后再初始化,onDraw里面肯定更加不合适。有木有大牛知道的请告知,onSizeChanged?
2、drawText():
/**
* 绘制圆弧下标 文字
*/
private void drawText(Canvas canvas) {
mTextPaint.setTextSize(28);
Path path = new Path();
path.addArc(mTextRectF, offset[0], 20);
canvas.drawTextOnPath("0", path, 10f, 10f, mTextPaint);
path.reset();
path.addArc(mTextRectF, offset[1] - 15, 20);
canvas.drawTextOnPath(“1000”, path, 10f, 10f, mTextPaint);
path.reset();
path.addArc(mTextRectF, offset[2] - 15, 20);
canvas.drawTextOnPath(“2000”, path, 10f, 10f, mTextPaint);
path.reset();
path.addArc(mTextRectF, offset[3] - 20, 20);
canvas.drawTextOnPath(“3000”, path, 10f, 10f, mTextPaint);
}
由于这里的下标文字是弧线走势,所以这里用了 canvas 的另一个方法drawTextOnPath(),也是基本方法,我就不多赘述了。
3、drawPointer():
/*
* 绘制指针
* */
private void drawPointer(Canvas canvas) {
mMatrix.reset();
mMatrix.postTranslate(mWidth / 2 - mGreenBitmap.getWidth() / 2, mHeight / 4);
if (progress > 666) {
//红色
mMatrix.postRotate(38, mWidth / 2f, mHeight / 2f + 80);
mMatrix.postRotate(70 * (progress - 666) / 333f, mWidth / 2f, mHeight / 2f + 80);
canvas.drawBitmap(mRedBitmap, mMatrix, null);
} else if (progress > 333) {
//黄色
mMatrix.postRotate(-35, mWidth / 2f, mHeight / 2f + 80);
mMatrix.postRotate(70 * (progress - 333) / 333f, mWidth / 2f, mHeight / 2f + 80);
canvas.drawBitmap(mYellowBitmap, mMatrix, null);
} else {
//绿色
mMatrix.postRotate(-108, mWidth / 2f, mHeight / 2f + 80);
mMatrix.postRotate(70 * progress / 333f, mWidth / 2f, mHeight / 2f + 80);
canvas.drawBitmap(mGreenBitmap, mMatrix, null);
}
}
progress就是用来控制进
这里我们使用了三个不同颜色的 icon 作为指针,在不同的进度显示不同的颜色。然后由于指针不是纯色的,所以不能用 drawable.setTint()方法来控制指针的颜色,要不然用一个指针就行了。Matrix控制 bitmap的位置的偏移旋转,记得使用Matrix的时候要用Matrix.postXXX方法, 使用 setXXX 系列的方法会覆盖上次的设置。
Matrix:The Matrix class holds a 3x3 matrix for transforming coordinates. 这里给Matrix做个简单的说明,就是用来控制矩阵变化的,感兴趣的小伙伴请自行 google。
4、drawSecond():
/*
*
*动态进度条
**/
private void drawSecond(Canvas canvas) {
//不能修改 mPaint 画笔的颜色复用 mPaint,初步估计 canvas.drawArc 方法为异步
if (progress > 666) {
//红色
canvas.drawArc(mBigRectF, offset[2], 70 * (progress - 666) * 3 / 1000, false, mPaintRed);
} else if (progress > 333) {
//黄色
canvas.drawArc(mBigRectF, offset[1], 70 * (progress - 333) * 3 / 1000, false, mPaintYellow);
} else {
//绿色
canvas.drawArc(mBigRectF, offset[0], 70 * progress * 3 / 1000, false, mPaintGreen);
}
}
这里踩了一个坑,一开始我的想法是所有的圆弧都用同一支画笔 mPaint,因为画笔的参数都是一样的,就只有颜色不同,在需要绘制不同颜色的时候调用 mPaint.setColor()方法即可。比如说我先用 mPaint 画笔画了一段灰色的圆弧,然后调用 mPaint.setColor(Color.BLUE)方法将画笔设置为蓝色,然后再绘制一段蓝色弧线,但是最终的结果是两段圆弧的颜色都是蓝色的。由于 canvas.drawArc()方法是native方法,所以我没有找到原因,初步估计 canvas.drawArc()方法是异步原因引起的,有木有大牛知道原因~~~
5、drawCalorie():
/*
*
* 圆弧中间文字
* */
private void drawCalorie(Canvas canvas) {
mTextPaint.setTextSize(56);
String num = String.valueOf((int) (mFullEat * progress / 1000f));
int numberWidth = (int) mTextPaint.measureText(num);
mTextPaint.setTextSize(28);
int textWidth = (int) mTextPaint.measureText("千卡");
mTextPaint.setTextSize(56);
canvas.drawText(num, (mWidth - numberWidth - textWidth) / 2, mHeight / 2 + 80 - 20, mTextPaint);
mTextPaint.setTextSize(28);
canvas.drawText("千卡", (mWidth - numberWidth - textWidth) / 2 + numberWidth + 5, mHeight / 2 + 80 - 20, mTextPaint);
String state = progress > 333 ? (progress > 666 ? "吃多了" : "正合适") : "还需吃";
mTextPaint.setTextSize(32);
canvas.drawText(state, (mWidth - mTextPaint.measureText(state)) / 2, mHeight / 2 + 80 + 30, mTextPaint);
}
这里没什么好说的,代码简洁明了。就是计算了几个宽高,让文字居中而已。
好,控件画完了。
接下来,只需要添加一个方法,修改progress的值,然后调用invalidate()重新走 ondraw()方法即可。
private void setProgress(int progress) {
this.progress = progress;
invalidate();
}
嗯~然后还要动起来,所以我把这个方法的权限设置为 pravite。
public void setData(int hasEat, int fullEat, boolean needAnimator) {
progress = hasEat * 1000 / fullEat;
mFullEat = fullEat;
if (progress > 1000)
progress = 1000;
if (progress < 0)
progress = 0;
if (needAnimator) {
ObjectAnimator objectAnimator = ObjectAnimator.ofInt(this, "progress", progress);
objectAnimator.setDuration(2000);
objectAnimator.setInterpolator(new DecelerateInterpolator());
objectAnimator.start();
} else {
setProgress(progress);
}
}
就酱紫,很简单的代码逻辑。哦,对了,ObjectAnimator动画用到了反射,代码混淆的时候记得keep 这个类不被混淆哦。
~~Over
对了,代码比较简单,我就不传 github 的,直接贴代码吧,两百行不到
/**
* Author: Diamond_Lin
* Version V1.0
* Date: 2017/6/26 上午10:06
* Description:
* Modification History:
* Date Author Version Description
* -----------------------------------------------------------------------------------
* 2017/6/26 Diamond_Lin 1.0 1.0
* Why & What is modified:
*/
public class InstrumentView extends View {
private Paint mPaintBig, mPaintSmall, mPaintYellow, mPaintGreen, mPaintRed;
private int mWidth;
private int mHeight;
private RectF mBigRectF, mSmallRectF, mTextRectF;
private TextPaint mTextPaint;
private int[] offset = {-198, -125, -52, 18};//分别是第一段起点,第二段起点,第三段起点,第三段终点
private Bitmap mGreenBitmap;
private Bitmap mYellowBitmap;
private Bitmap mRedBitmap;
private int progress = 0;
private int mFullEat;
private Matrix mMatrix;
public InstrumentView(Context context) {
this(context, null);
}
public InstrumentView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public InstrumentView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPaintBig = new Paint();//外环画笔
mPaintBig.setAntiAlias(true);//使用抗锯齿功能
mPaintBig.setColor(Color.argb(128, 0, 0, 0)); //设置画笔的颜色
mPaintBig.setStyle(Paint.Style.STROKE);//设置画笔类型为描边
mPaintBig.setStrokeWidth(16);
mPaintYellow = new Paint();//黄色
mPaintYellow.setAntiAlias(true);//使用抗锯齿功能
mPaintYellow.setColor(Color.rgb(0xf7, 0xb5, 0x00)); //设置画笔的颜色
mPaintYellow.setStyle(Paint.Style.STROKE);//设置画笔类型为描边
mPaintYellow.setStrokeWidth(16);
mPaintGreen = new Paint();//绿色
mPaintGreen.setAntiAlias(true);//使用抗锯齿功能
mPaintGreen.setColor(Color.rgb(0x5c, 0xd1, 0xb4)); //设置画笔的颜色
mPaintGreen.setStyle(Paint.Style.STROKE);//设置画笔类型为描边
mPaintGreen.setStrokeWidth(16);
mPaintRed = new Paint();//红色
mPaintRed.setAntiAlias(true);//使用抗锯齿功能
mPaintRed.setColor(Color.rgb(0xff, 0x64, 0x6f)); //设置画笔的颜色
mPaintRed.setStyle(Paint.Style.STROKE);//设置画笔类型为描边
mPaintRed.setStrokeWidth(16);
mPaintSmall = new Paint();//内环画笔
mPaintSmall.setAntiAlias(true);//使用抗锯齿功能
mPaintSmall.setColor(Color.argb(128, 0, 0, 0)); //设置画笔的颜色
mPaintSmall.setStyle(Paint.Style.STROKE);//设置画笔类型为描边
mPaintSmall.setStrokeWidth(8);
mTextPaint = new TextPaint();
mTextPaint.setAntiAlias(true);
mTextPaint.setColor(Color.WHITE);
mTextPaint.setTextSize(28);
mGreenBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_pointer_green);//绿色指针
mYellowBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_pointer_yellow);//黄色指针
mRedBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_pointer_red);//红色指针
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidth = MeasureSpec.getSize(widthMeasureSpec);
mHeight = MeasureSpec.getSize(heightMeasureSpec);
int i = (mWidth - mHeight) / 2;
mBigRectF = new RectF(10 + i, 10, mWidth - 10 - i, mHeight - 10);
mTextRectF = new RectF(40 + i, 40, mWidth - 40 - i, mHeight - 40);
mSmallRectF = new RectF(70 + i, 70, mWidth - 70 - i, mHeight - 70);
mBigRectF.offset(0, 80);
mSmallRectF.offset(0, 80);
mTextRectF.offset(0, 80);
mMatrix = new Matrix();
mMatrix.postTranslate(mWidth / 2 - mGreenBitmap.getWidth() / 2, mHeight / 4);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.e("__-progress", progress + "");
drawInstrument(canvas);//进度底色
drawText(canvas);//文字
drawPointer(canvas);//指针
drawSecond(canvas);//第二层
drawCalorie(canvas);//绘制中间的卡路里
}
/*
*
* 圆弧中间文字
* */
private void drawCalorie(Canvas canvas) {
mTextPaint.setTextSize(56);
String num = String.valueOf((int) (mFullEat * progress / 1000f));
int numberWidth = (int) mTextPaint.measureText(num);
mTextPaint.setTextSize(28);
int textWidth = (int) mTextPaint.measureText("千卡");
mTextPaint.setTextSize(56);
canvas.drawText(num, (mWidth - numberWidth - textWidth) / 2, mHeight / 2 + 80 - 20, mTextPaint);
mTextPaint.setTextSize(28);
canvas.drawText("千卡", (mWidth - numberWidth - textWidth) / 2 + numberWidth + 5, mHeight / 2 + 80 - 20, mTextPaint);
String state = progress > 333 ? (progress > 666 ? "吃多了" : "正合适") : "还需吃";
mTextPaint.setTextSize(32);
canvas.drawText(state, (mWidth - mTextPaint.measureText(state)) / 2, mHeight / 2 + 80 + 30, mTextPaint);
}
/*
*
*动态进度条
**/
private void drawSecond(Canvas canvas) {
//不能修改 mPaint 画笔的颜色复用 mPaint,初步估计 canvas.drawArc 方法为异步
if (progress > 666) {
//红色
canvas.drawArc(mBigRectF, offset[2], 70 * (progress - 666) * 3 / 1000, false, mPaintRed);
} else if (progress > 333) {
//黄色
canvas.drawArc(mBigRectF, offset[1], 70 * (progress - 333) * 3 / 1000, false, mPaintYellow);
} else {
//绿色
canvas.drawArc(mBigRectF, offset[0], 70 * progress * 3 / 1000, false, mPaintGreen);
}
}
/*
* 绘制指针
* */
private void drawPointer(Canvas canvas) {
mMatrix.reset();
mMatrix.postTranslate(mWidth / 2 - mGreenBitmap.getWidth() / 2, mHeight / 4);
if (progress > 666) {
//红色
mMatrix.postRotate(38, mWidth / 2f, mHeight / 2f + 80);
mMatrix.postRotate(70 * (progress - 666) / 333f, mWidth / 2f, mHeight / 2f + 80);
canvas.drawBitmap(mRedBitmap, mMatrix, null);
} else if (progress > 333) {
//黄色
mMatrix.postRotate(-35, mWidth / 2f, mHeight / 2f + 80);
mMatrix.postRotate(70 * (progress - 333) / 333f, mWidth / 2f, mHeight / 2f + 80);
canvas.drawBitmap(mYellowBitmap, mMatrix, null);
} else {
//绿色
mMatrix.postRotate(-108, mWidth / 2f, mHeight / 2f + 80);
mMatrix.postRotate(70 * progress / 333f, mWidth / 2f, mHeight / 2f + 80);
canvas.drawBitmap(mGreenBitmap, mMatrix, null);
}
}
/**
* 绘制圆弧标记 文字
*/
private void drawText(Canvas canvas) {
mTextPaint.setTextSize(28);
Path path = new Path();
path.addArc(mTextRectF, offset[0], 20);
canvas.drawTextOnPath("0", path, 10f, 10f, mTextPaint);
path.reset();
path.addArc(mTextRectF, offset[1] - 15, 20);
canvas.drawTextOnPath(String.valueOf(mFullEat / 3), path, 10f, 10f, mTextPaint);
path.reset();
path.addArc(mTextRectF, offset[2] - 15, 20);
canvas.drawTextOnPath(String.valueOf(mFullEat / 3 * 2), path, 10f, 10f, mTextPaint);
path.reset();
path.addArc(mTextRectF, offset[3] - 20, 20);
canvas.drawTextOnPath(String.valueOf(mFullEat), path, 10f, 10f, mTextPaint);
}
/*
*
* 绘制6段圆弧
* */
private void drawInstrument(Canvas canvas) {
//70度一个弧形,中间隔5度
canvas.drawArc(mBigRectF, offset[0], 70, false, mPaintBig);//绘制圆弧,不含圆心
canvas.drawArc(mBigRectF, offset[1], 70, false, mPaintBig);//绘制圆弧,不含圆心
canvas.drawArc(mBigRectF, offset[2], 70, false, mPaintBig);//绘制圆弧,不含圆心
canvas.drawArc(mSmallRectF, offset[0], 70, false, mPaintSmall);//绘制圆弧,不含圆心
canvas.drawArc(mSmallRectF, offset[1], 70, false, mPaintSmall);//绘制圆弧,不含圆心
canvas.drawArc(mSmallRectF, offset[2], 70, false, mPaintSmall);//绘制圆弧,不含圆心
}
public void setData(int hasEat, int fullEat, boolean needAnimator) {
progress = hasEat * 1000 / fullEat;
mFullEat = fullEat;
if (progress > 1000)
progress = 1000;
if (progress < 0)
progress = 0;
if (needAnimator) {
ObjectAnimator objectAnimator = ObjectAnimator.ofInt(this, "progress", progress);
objectAnimator.setDuration(2000);
objectAnimator.setInterpolator(new DecelerateInterpolator());
objectAnimator.start();
} else {
setProgress(progress);
}
}
private void setProgress(int progress) {
this.progress = progress;
invalidate();
}
}