手把手带你画一个 时尚仪表盘 Android 自定义View

最后更新于:2022-04-01 10:01:17

拿到美工效果图,咱们程序员就得画得一模一样。 为了不被老板喷,只能多练啊。 听说你觉得前面几篇都so easy,那今天就带你做个相对比较复杂的。 转载请注明出处:http://blog.csdn.net/wingichoy/article/details/50468674 今天的效果图如下(左边是ui图 右边是实现图): ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-21_56efaea2d86a0.jpg) 自我感觉总体效果还不错,至少大概画得一样了。上一个动态图: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-21_56efaea2eb7a3.jpg) 其实这个效果实现起来也不是很难,就是计算坐标,弧度之类的可能会比较麻烦,这里分享写这个其中一张手稿,请无视掉很丑的字,其实做自定义view 还是要在纸上多画。所以希望大家也能这么画画,思路会很顺。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-21_56efaea30ede8.jpg) 好的了,废话不多说,快开始。 首先自定义属性  构造函数,测量什么的 你肯定已经很熟练 直接贴代码了,注释写的很清楚 ~~~ public class PanelView extends View { private int mWidth; private int mHeight; private int mPercent; //刻度宽度 private float mTikeWidth; //第二个弧的宽度 private int mScendArcWidth; //最小圆的半径 private int mMinCircleRadius; //文字矩形的宽 private int mRectWidth; //文字矩形的高 private int mRectHeight; //文字内容 private String mText = ""; //文字的大小 private int mTextSize; //设置文字颜色 private int mTextColor; private int mArcColor; //小圆和指针颜色 private int mMinCircleColor; //刻度的个数 private int mTikeCount; private Context mContext; public PanelView(Context context) { this(context, null); } public PanelView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public PanelView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mContext = context; TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.PanelView,defStyleAttr,0); mArcColor = a.getColor(R.styleable.PanelView_arcColor, Color.parseColor("#5FB1ED")); mMinCircleColor = a.getColor(R.styleable.PanelView_pointerColor,Color.parseColor("#C9DEEE")); mTikeCount = a.getInt(R.styleable.PanelView_tikeCount,12); mTextSize = a.getDimensionPixelSize(PxUtils.spToPx(R.styleable.PanelView_android_textSize,mContext),24); mText = a.getString(R.styleable.PanelView_android_text); mScendArcWidth = 50; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthSize = MeasureSpec.getSize(widthMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); if (widthMode == MeasureSpec.EXACTLY) { mWidth = widthSize; }else { mWidth = PxUtils.dpToPx(200,mContext); } if (heightMode == MeasureSpec.EXACTLY) { mHeight = heightSize; }else { mHeight = PxUtils.dpToPx(200,mContext); } Log.e("wing",mWidth+""); setMeasuredDimension(mWidth, mHeight); } ~~~ 自定义属性attr.xml ~~~ <?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="PanelView"> <attr name="arcColor" format="color"/> <attr name="arcWidth" format="dimension"/> <attr name="android:text"/> <attr name="tikeCount" format="integer"/> <attr name="pointerColor" format="color"/> <attr name="android:textSize"/> </declare-styleable> </resources> ~~~ 之后来重头戏,也就是绘制。就像画画一样,再复杂的view也是一笔一笔画出来的。所以我们把这个view分解。 大概分解成如下:**1.最外面的弧   2.里面的粗弧   3.中间小圆   4.最小的圆  5.刻度   6.指针  7.矩形  8.文字** 相信让你分开画一定难不倒你。那组合在一起 就是这个view啦。下面开始我们的ondraw() 按照这个分解来: 1.绘制最外面的弧   这里需要注意的一点是,如果想让这个圆在view里 记得减去画笔宽度的一半  因为半径是从圆心到画笔宽度的中间算的,所以这里画弧的矩形是  new RectF(strokeWidth, strokeWidth, mWidth - strokeWidth, mHeight - strokeWidth) ~~~ Paint p = new Paint(); int strokeWidth = 3; p.setStrokeWidth(strokeWidth); p.setAntiAlias(true); p.setStyle(Paint.Style.STROKE); p.setColor(mArcColor); //最外面线条 canvas.drawArc(new RectF(strokeWidth, strokeWidth, mWidth - strokeWidth, mHeight - strokeWidth), 145, 250, false, p); ~~~ 画出来是这样的效果。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-21_56efaea32df78.jpg) 2.绘制里面的粗弧,这里比较麻烦的就是需要分为四段,看图: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-21_56efaea33ef38.jpg) 因为大圆和里面粗弧的长短不一致,这里使用百分比来计算 所以会造成指针偏差,那么这里把 1、2两个部分固定来画,然后是3 充满的部分,用百分比来计算需要画多少度,最后是4 空白的部分。 首先把粗弧的矩形画出来,这里固定了比大弧半径少50(这里其实可以改进,你可以改成动态的让他更灵活),然后计算出百分比。 ~~~ RectF secondRectF = new RectF(strokeWidth + 50, strokeWidth + 50, mWidth - strokeWidth - 50, mHeight - strokeWidth - 50); float secondRectWidth = mWidth - strokeWidth - 50 - (strokeWidth + 50); float secondRectHeight = mHeight - strokeWidth - 50 - (strokeWidth + 50); float percent = mPercent / 100f; ~~~ 接下来绘制1弧,先算出fill充满部分的度数,因为是突出的,所以如果百分比为0,突出左端为白色 如果不为零,则和充满颜色统一。 ~~~ //充满的圆弧的度数 -5是大小弧的偏差 float fill = 250 * percent ; //空的圆弧的度数 float empty = 250 - fill; // Log.e("wing", fill + ""); if(percent==0){ p.setColor(Color.WHITE); } //画粗弧突出部分左端 canvas.drawArc(secondRectF,135,11,false,p); ~~~ 然后绘制2弧 也就是fill充满的弧, ~~~ canvas.drawArc(secondRectF, 145, fill, false, p); ~~~ 接下来是3弧,也就是empty未充满的弧,是白色的 ~~~ p.setColor(Color.WHITE); //画弧胡的未充满部分 canvas.drawArc(secondRectF, 145 + fill, empty, false, p); ~~~ 最后,画出右边突出的4弧, 如果百分比为100 那么和充满的颜色一致,否则为白色 ~~~ //画粗弧突出部分右端 if(percent == 1){ p.setColor(mArcColor); } canvas.drawArc(secondRectF,144+fill+empty,10,false,p); ~~~ 这样粗弧也就画完了 来看看效果,就画了两条弧线(实际是5条),就成型了。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-21_56efaea34f25e.jpg) 3.中间的小圆外圈,他的圆心不用多说 是整个view的中心 ~~~ p.setColor(mArcColor); //绘制小圆外圈 p.setStrokeWidth(3); canvas.drawCircle(mWidth / 2, mHeight / 2, 30, p); ~~~ 4.绘制内圆,圆心一样的,半径和画笔粗度改变一下 ~~~ //绘制小圆内圈 p.setColor(mMinCircleColor); p.setStrokeWidth(8); mMinCircleRadius = 15; canvas.drawCircle(mWidth / 2, mHeight / 2, mMinCircleRadius, p); ~~~ ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-21_56efaea3602ea.jpg) 5.刻度  刻度处理起来可能比较麻烦,用三角函数算坐标啊 循环画出来。。 这里提供一种比较简单的方法:旋转画布。 首先引入一个概念,什么叫旋转画布呢,就是把你的画布旋转。。经过测试,旋转以后,整个坐标轴都会对应旋转,一张图举例说明下。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-21_56efaea3752f2.jpg) 大概就是这个意思,画布旋转之后 坐标系也就旋转了,但是原来的图像还在,所以说你比如这个点 x,y旋转前在这个位置, 那么旋转后就是另外一个位置了,但是他们的坐标是相同的。 所以刻度也可以考这种方法画。我们只要画出最顶端的刻度 然后旋转就可以了。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-21_56efaea38a8de.jpg) 绘制第一段刻度, 然后总共是250的弧度  计算出每个刻度的度数     用250除以刻度数mTikeCount,就是每次旋转的度数。接下来把画布逐步旋转,按照原坐标绘制,即可绘制出右半部分刻度。  注意:为了让之后的绘制正常,务必把画布转回原来的位置 ~~~ //绘制刻度! p.setColor(mArcColor); //绘制第一条最上面的刻度 mTikeWidth = 20; p.setStrokeWidth(3); canvas.drawLine(mWidth / 2, 0, mWidth / 2, mTikeWidth, p); //旋转的角度 float rAngle = 250f / mTikeCount; //通过旋转画布 绘制右面的刻度 for (int i = 0; i < mTikeCount / 2; i++) { canvas.rotate(rAngle, mWidth / 2, mHeight / 2); canvas.drawLine(mWidth / 2, 0, mWidth / 2, mTikeWidth, p); } //现在需要将将画布旋转回来 canvas.rotate(-rAngle * mTikeCount / 2, mWidth / 2, mHeight / 2); ~~~ ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-21_56efaea39d43e.jpg) 左半部分同理,需要改变的度数为负 就好了 ~~~ //通过旋转画布 绘制左面的刻度 for (int i = 0; i < mTikeCount / 2; i++) { canvas.rotate(-rAngle, mWidth / 2, mHeight / 2); canvas.drawLine(mWidth / 2, 0, mWidth / 2, mTikeWidth, p); } //现在需要将将画布旋转回来 canvas.rotate(rAngle * mTikeCount / 2, mWidth / 2, mHeight / 2); ~~~ ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-21_56efaea3ad2a7.jpg) 6.指针   指针的绘制和刻度相似,先算出来百分比所占的度数 然后根据 是否大于50%来旋转画布。 指针的起终点是 总view高度的一半 粗弧矩形的一半 加上小圆,前面坐标讲解了那么,这个也一样,自己拿起笔算一算。 注意这里画布旋转我通过计算得出一个公式 250 * percent - 250/2。 如果小于50% 则为负   如果大于50%则为正,然后进行旋转。 切忌最后一定要将画布转回来。 ~~~ //绘制指针 p.setColor(mMinCircleColor); p.setStrokeWidth(4); //按照百分比绘制刻度 canvas.rotate(( 250 * percent - 250/2), mWidth / 2, mHeight / 2); canvas.drawLine(mWidth / 2, (mHeight / 2 - secondRectHeight / 2) + mScendArcWidth / 2 + 2, mWidth / 2, mHeight / 2 - mMinCircleRadius, p); //将画布旋转回来 canvas.rotate(-( 250 * percent - 250/2), mWidth / 2, mHeight / 2); ~~~ ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-21_56efaea3be503.jpg) 接下来就是画矩形和文字。没什么好说的了,坐标也是X周围mWidth/2   y轴自己根据圆心微调一个距离 ~~~ //绘制矩形 p.setStyle(Paint.Style.FILL); p.setColor(mArcColor); mRectWidth = 60; mRectHeight = 25; //文字矩形的最底部坐标 float rectBottomY = mHeight/2 + secondRectHeight/3+mRectHeight; canvas.drawRect(mWidth/2-mRectWidth/2,mHeight/2 + secondRectHeight/3,mWidth/2+mRectWidth/2,rectBottomY,p); p.setTextSize(mTextSize); mTextColor = Color.WHITE; p.setColor(mTextColor); float txtLength = p.measureText(mText); canvas.drawText(mText,(mWidth-txtLength)/2,rectBottomY + 40,p); super.onDraw(canvas); ~~~ 这样完成了整个view的绘制。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-21_56efaea3ceedb.jpg) 下面要做的就是为了方便使用者,提供一些设置属性的方法。 ~~~ /** * 设置百分比 * @param percent */ public void setPercent(int percent) { mPercent = percent; invalidate(); } /** * 设置文字 * @param text */ public void setText(String text){ mText = text; invalidate(); } /** * 设置圆弧颜色 * @param color */ public void setArcColor(int color){ mArcColor = color; invalidate(); } /** * 设置指针颜色 * @param color */ public void setPointerColor(int color){ mMinCircleColor = color; invalidate(); } /** * 设置文字大小 * @param size */ public void setTextSize(int size){ mTextSize = size; invalidate(); } /** * 设置粗弧的宽度 * @param width */ public void setArcWidth(int width){ mScendArcWidth = width; invalidate(); } ~~~ 大功告成!!!一个看似复杂的view  经过我们一步一步绘制遍完成了。 其实技术的养成也是这样,只要一步一步脚踏实地的去练习,我相信总有一天我能成为大神。 本项目地址 :[PanelView](https://github.com/githubwing/PanelView)   求关注  求评论  求star!!!!!!
';