Android自定义view实现多色进度条GradientProgressView的绘制
作者:人间正四月
前言
我们常使用shape实现渐变色,但是shape的极限却只有三色,如果有超过三种颜色的View的要求,那么我们就不得不去自定义View来实现这个需求。不过它的难度也是很低的,实现起来很轻松。接下来我分析一下我的思路。 先上图:
一、GradientProgressView准备工作
- color:渐变进度条的多种颜色
- 图形:如果是圆柱形就使用RectF,矩形就用Rect,一个用于显示原色进度,一个用于显示渐变进度条
- 渐变染色器:LinearGradient
- 宽高:mHeight、mWidth
- 圆柱的半径:mRadius 属性代码如下
private int colorStart; // 颜色:进度1 private int colorEnd; // 颜色:进度5 private int colorStart2;// 颜色:进度2 private int colorEnd0;// 颜色:进度4 private int colorCenter;// 颜色:进度3 private Paint mGradientPaint;// 渐变画笔 private Paint mPaint;// 默认画笔 private RectF mBackGroundRect; // 原色图形 private RectF mGradientRect;// 染色图形 private LinearGradient backGradient;// 渐变色 private int mHeight; private int mWidth; private int mRadius;
二、绘制
1.初始化属性
因为这个自定义View只为了满足公司需求,所有我并未将其的功能扩展开来,所有没有自定义属性,都是写死在代码中,后续有空再优化。 主要三步骤:初始化颜色,初始化画笔,初始化图形。 最好先写死需要的宽高,避免在使用的使用还未测量就进行绘制
private void init(Context context, @Nullable AttributeSet attrs){ // 初始化画笔 mGradientPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); colorStart = Color.parseColor("#63DC80"); colorStart2 = Color.parseColor("#A8DB5B"); colorCenter = Color.parseColor("#FFCE48"); colorEnd0 = Color.parseColor("#FE7B39"); colorEnd = Color.parseColor("#E8402B"); mPaint.setColor(Color.parseColor("#EDEDED")); if (attrs!=null){ } //设置抗锯齿 mGradientPaint.setAntiAlias(true); //设置防抖动 mGradientPaint.setDither(true); mGradientPaint.setStyle(Paint.Style.FILL); //设置抗锯齿 mPaint.setAntiAlias(true); //设置防抖动 mPaint.setDither(true); mPaint.setStyle(Paint.Style.FILL); mBackGroundRect = new RectF(); // 防止渲染还未完成就显示,使得宽高还未测量。先在初始化这里定义好跟xml中相同的宽高 mWidth = (int) (Resources.getSystem().getDisplayMetrics().widthPixels-context.getResources().getDimension(R.dimen.base_dp_56)); mHeight = (int) (context.getResources().getDimension(R.dimen.base_dp_8)); }
2.测量宽高
这一步一般来说已经是有标准的代码模板了,所以我这里也没什么创新的地方,照抄就完事了,这里便浅浅介绍一下测量的步骤吧。 其流程是:
- 先用
getMode()和getSize()
获取测量模式和宽高,再根据不同的模式获取宽高。 - 模式有三种:EXACTLY(精确值模式)、AT_MOST(最大值模式)、UNSPECIFIED(未指定模式)
- 如果是EXACTLY(精确值模式),那么该属性就是确定的,即当前View的宽高
- 如果是AT_MOST(最大值模式),那么该属性就是父容器的宽高
- 如果是UNSPECIFIED(未指定模式),那么该属性要么是0,要么是EXACTLY(精确值模式)下当前view的属性。 用图来说明,即为:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); /** * 从提供的测量规格中获取测量模式和宽高值 */ int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); Log.d("TAG", "onMeasure: "+widthMode); Log.d("TAG", "onMeasure: "+widthSize); if (widthMode == MeasureSpec.EXACTLY) { //测量模式是EXACTLY,说明view的宽度值是确定的 mWidth = widthSize; } else if (widthMode == MeasureSpec.AT_MOST) { /** * 测量模式是AT_MOST,说明view的宽度值最大是父View能提供的大小 * 比如开发者设置wrap_content,那可能是希望填充父View */ mWidth = Math.max(mWidth,widthSize); } if (heightMode == MeasureSpec.EXACTLY) { mHeight = heightSize; } else if (heightMode == MeasureSpec.AT_MOST) { mHeight = Math.min(mHeight,heightSize); } /** * 为了View能更好的展示,需要设置下宽高比 */ float times = mWidth / (float)mHeight; if (times < 2.5 ) { if (mHeight < DensityUtil.dip2px(getContext(),60)) { mHeight = DensityUtil.dip2px(getContext(),60); } mWidth = (int) (mHeight * 2.5); } mRadius = (int) getContext().getResources().getDimension(R.dimen.base_dp_20); //保存计算后的宽高 setMeasuredDimension(mWidth,mHeight); }
3.根据情况来画渐变色进度
这里因为需求原因,我将原本的进度条五色平分进度,所以这里用到已经测量好的宽度属性mWidth
。 这里的流程也很简单,简要说下
- 暴露方法给外界调用
- 获取进度
- 根据进度来给渐变染色器:LinearGradient初始化
- 同时给圆柱形RectF初始化宽高
- 最后调用
invalidate()
重新绘制该View
public void setAirType(String degree){ int level = mWidth / 5; switch (degree){ case "1": mGradientRect = new RectF(0,0,level,mHeight); // 因为渐变色集合,需要颜色大于等于2,这里取巧,设置相同颜色 backGradient = new LinearGradient(0, 0, level, 0, new int[]{colorStart,colorStart}, null, Shader.TileMode.CLAMP); break; case "2": mGradientRect = new RectF(0,0,level*2,mHeight); backGradient = new LinearGradient(0, 0, level*2, 0, new int[]{colorStart, colorStart2}, null, Shader.TileMode.CLAMP); break; case "3": mGradientRect = new RectF(0,0,level*3,mHeight); backGradient = new LinearGradient(0, 0, level*3, 0, new int[]{colorStart, colorStart2,colorCenter}, null, Shader.TileMode.CLAMP); break; case "4": mGradientRect = new RectF(0,0,level*4,mHeight); backGradient = new LinearGradient(0, 0, level*4, 0, new int[]{colorStart, colorStart2,colorCenter,colorEnd0}, null, Shader.TileMode.CLAMP); break; case "5": mGradientRect = new RectF(0,0,mWidth,mHeight); backGradient = new LinearGradient(0, 0, mWidth, 0, new int[]{colorStart, colorStart2,colorCenter,colorEnd0,colorEnd}, null, Shader.TileMode.CLAMP); break; } invalidate(); }
4.绘制
这里我采用最简单粗暴的方法,直接叠了两个圆柱进度条,一个是原色的进度条,一个是渐变色的进度条,这里大家可以自行优化,即在backGradient 属性初始化的时候进度调整等。
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); drawRoundRect(canvas); } private void drawRoundRect(Canvas canvas) { mBackGroundRect.left = 0; mBackGroundRect.top = 0; mBackGroundRect.bottom = mHeight; mBackGroundRect.right = mWidth; // 绘制底色圆角矩形 canvas.drawRoundRect(mBackGroundRect, mRadius, mRadius, mPaint); // 渐变绘图 mGradientPaint.setShader(backGradient); //绘制背景 圆角矩形 if (mGradientRect != null) { canvas.drawRoundRect(mGradientRect, mRadius, mRadius, mGradientPaint); } }
完整代码贴上,方便各位友友使用
public class GradientProgressView extends View { private int colorStart; // 颜色:进度1 private int colorEnd; // 颜色:进度5 private int colorStart2;// 颜色:进度2 private int colorEnd0;// 颜色:进度4 private int colorCenter;// 颜色:进度3 private List<Integer> colorList; // 颜色集合 private List<Float> colorSize; // 颜色位置 private Paint mGradientPaint;// 渐变画笔 private Paint mPaint;// 默认画笔 private RectF mBackGroundRect; // 原色图形 private RectF mGradientRect;// 染色图形 private LinearGradient backGradient;// 渐变色 private int mHeight; private int mWidth; private int mRadius; public GradientProgressView(Context context) { super(context); } public GradientProgressView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(context,attrs); } public GradientProgressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } private void init(Context context, @Nullable AttributeSet attrs){ // 初始化画笔 mGradientPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); colorStart = Color.parseColor("#63DC80"); colorStart2 = Color.parseColor("#A8DB5B"); colorCenter = Color.parseColor("#FFCE48"); colorEnd0 = Color.parseColor("#FE7B39"); colorEnd = Color.parseColor("#E8402B"); mPaint.setColor(Color.parseColor("#EDEDED")); if (attrs!=null){ } //设置抗锯齿 mGradientPaint.setAntiAlias(true); //设置防抖动 mGradientPaint.setDither(true); mGradientPaint.setStyle(Paint.Style.FILL); //设置抗锯齿 mPaint.setAntiAlias(true); //设置防抖动 mPaint.setDither(true); mPaint.setStyle(Paint.Style.FILL); mBackGroundRect = new RectF(); // 防止渲染还未完成就显示,使得宽高还未测量。先在初始化这里定义好跟xml中相同的宽高 mWidth = (int) (Resources.getSystem().getDisplayMetrics().widthPixels-context.getResources().getDimension(R.dimen.base_dp_56)); mHeight = (int) (context.getResources().getDimension(R.dimen.base_dp_8)); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); /** * 从提供的测量规格中获取测量模式和宽高值 */ int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); Log.d("TAG", "onMeasure: "+widthMode); Log.d("TAG", "onMeasure: "+widthSize); if (widthMode == MeasureSpec.EXACTLY) { //测量模式是EXACTLY,说明view的宽度值是确定的 mWidth = widthSize; } else if (widthMode == MeasureSpec.AT_MOST) { /** * 测量模式是AT_MOST,说明view的宽度值最大是父View能提供的大小 * 比如开发者设置wrap_content,那可能是希望填充父View */ mWidth = Math.max(mWidth,widthSize); } if (heightMode == MeasureSpec.EXACTLY) { mHeight = heightSize; } else if (heightMode == MeasureSpec.AT_MOST) { mHeight = Math.min(mHeight,heightSize); } /** * 为了View能更好的展示,需要设置下宽高比 */ float times = mWidth / (float)mHeight; if (times < 2.5 ) { if (mHeight < DensityUtil.dip2px(getContext(),60)) { mHeight = DensityUtil.dip2px(getContext(),60); } mWidth = (int) (mHeight * 2.5); } mRadius = (int) getContext().getResources().getDimension(R.dimen.base_dp_20); //保存计算后的宽高 setMeasuredDimension(mWidth,mHeight); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mWidth = w; mHeight = h; } public void setAirType(String degree){ int level = mWidth / 5; switch (degree){ case "1": mGradientRect = new RectF(0,0,level,mHeight); // 因为渐变色集合,需要颜色大于等于2,这里取巧,设置相同颜色 backGradient = new LinearGradient(0, 0, level, 0, new int[]{colorStart,colorStart}, null, Shader.TileMode.CLAMP); break; case "2": mGradientRect = new RectF(0,0,level*2,mHeight); backGradient = new LinearGradient(0, 0, level*2, 0, new int[]{colorStart, colorStart2}, null, Shader.TileMode.CLAMP); break; case "3": mGradientRect = new RectF(0,0,level*3,mHeight); backGradient = new LinearGradient(0, 0, level*3, 0, new int[]{colorStart, colorStart2,colorCenter}, null, Shader.TileMode.CLAMP); break; case "4": mGradientRect = new RectF(0,0,level*4,mHeight); backGradient = new LinearGradient(0, 0, level*4, 0, new int[]{colorStart, colorStart2,colorCenter,colorEnd0}, null, Shader.TileMode.CLAMP); break; case "5": mGradientRect = new RectF(0,0,mWidth,mHeight); backGradient = new LinearGradient(0, 0, mWidth, 0, new int[]{colorStart, colorStart2,colorCenter,colorEnd0,colorEnd}, null, Shader.TileMode.CLAMP); break; } invalidate(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); drawRoundRect(canvas); } private void drawRoundRect(Canvas canvas) { mBackGroundRect.left = 0; mBackGroundRect.top = 0; mBackGroundRect.bottom = mHeight; mBackGroundRect.right = mWidth; // 绘制底色圆角矩形 canvas.drawRoundRect(mBackGroundRect, mRadius, mRadius, mPaint); // 渐变绘图 mGradientPaint.setShader(backGradient); //绘制背景 圆角矩形 if (mGradientRect != null) { canvas.drawRoundRect(mGradientRect, mRadius, mRadius, mGradientPaint); } } }
xml调用
<com.itaem.blviewtest.GradientProgressView android:id="@+id/gradientProgress" android:layout_width="match_parent" android:layout_height="@dimen/base_dp_8" android:layout_marginTop="12dp" android:layout_marginHorizontal="@dimen/base_dp_44"/>
总结
还是因为时间问题,该自定义View只是单纯满足需求,并没有太大的扩展性功能,后续有时间我也行会出一篇后续将自定义View补上,实现一个功能性较为强大的渐变色进度条。 自定义View的魅力就是当你实现功能需求的时候的满足感,不亚于以前做完一道数学大题。那种畅快和欣喜,老实说我有点爱上自定义View了。
到此这篇关于Android自定义view实现多色进度条GradientProgressView的绘制的文章就介绍到这了,更多相关Android自定义view绘制多色进度条内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!