自定义仪表盘PaneView

1、概述

最近学习自定义View,趁着周末做了一个仪表盘练练手,效果还可以,在此分享一下先上效果图(截图有点不清晰,凑合着看下吧)

项目在我的github上https://github.com/xsfelvis/PanelView  

自定义仪表盘PaneView自定义仪表盘PaneView


有图才能有真相,下面简要说一下如何实现的


2、实现

【分析有哪些属性需要】

在values/attr文件中进行声明,这些属性都是可以在xml中进行使用的,实现定制的,比如unit单位属性

[html] view plain copy
 自定义仪表盘PaneView自定义仪表盘PaneView
  1. <resources>  
  2.   
  3.   
  4.     <declare-styleable name="PanelView">  
  5.         <attr name="arcColor" format="color"/>  
  6.         <attr name="arcWidth" format="dimension"/>  
  7.         <attr name="secArcWidth" format="dimension"/>  
  8.         <attr name="android:text"/>  
  9.         <attr name="tikeCount" format="integer"/>  
  10.         <attr name="pointerColor" format="color"/>  
  11.         <attr name="Unit" format="string"/>  
  12.         <attr name="android:textSize"/>  
  13.         <attr name="AcrStartColor" format="color"/>  
  14.         <attr name="AcrEndColor" format="color"/>  
  15.         <attr name="textColor" format="color"/>  
  16.   
  17.     </declare-styleable>  
  18.   
  19. </resources>  

然后为了结构上更加工整,推荐单独写一个文件来处理这些属性,最好不要和自定义控件混在一起

这里在PanelViewAttr.Java中做了处理

[java] view plain copy
 自定义仪表盘PaneView自定义仪表盘PaneView
  1. package PanelView;  
  2.   
  3. import android.content.Context;  
  4. import android.content.res.TypedArray;  
  5. import android.util.AttributeSet;  
  6.   
  7. import com.xsf.panelview.R;  
  8.   
  9. import util.PxUtils;  
  10.   
  11. /** 
  12.  * Created by hzxushangfei on 2016/1/23. 
  13.  */  
  14. public class PanelViewAttr {  
  15.     private int mArcColor;  
  16.     private int mPointerColor;  
  17.     private int mTikeCount;  
  18.     private int mTextSize;  
  19.     private String mText = "";  
  20.     private int arcwidth;  
  21.     private int mScendArcWidth;  
  22.     private String unit;//单位  
  23.     private int acrStartColor;  
  24.     private int acrEndColor;  
  25.     private int textColor;  
  26.   
  27.   
  28.     public PanelViewAttr(Context context, AttributeSet attrs, int defStyleAttr) {  
  29.         TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PanelView, defStyleAttr, 0);  
  30.         mArcColor = ta.getColor(R.styleable.PanelView_arcColor, context.getResources().getColor(R.color.colorPrimaryDark));  
  31.         mPointerColor = ta.getColor(R.styleable.PanelView_pointerColor, context.getResources().getColor(R.color.PointerColor));  
  32.         mTikeCount = ta.getInt(R.styleable.PanelView_tikeCount, 12);  
  33.         mTextSize = ta.getDimensionPixelSize(PxUtils.spToPx(R.styleable.PanelView_android_textSize, context), 24);  
  34.         mText = ta.getString(R.styleable.PanelView_android_text);  
  35.         arcwidth = ta.getInt(R.styleable.PanelView_arcWidth, 3);  
  36.         mScendArcWidth = ta.getInt(R.styleable.PanelView_secArcWidth, 50);  
  37.         unit = ta.getString(R.styleable.PanelView_Unit);  
  38.         acrStartColor = ta.getColor(R.styleable.PanelView_AcrStartColor, context.getResources().getColor(R.color.GREEN));  
  39.         acrEndColor = ta.getColor(R.styleable.PanelView_AcrEndColor, context.getResources().getColor(R.color.RED));  
  40.         textColor = ta.getColor(R.styleable.PanelView_textColor, context.getResources().getColor(R.color.Yellow));  
  41.         ta.recycle();  
  42.     }  
  43.   
  44.   
  45.     public int getAcrEndColor() {  
  46.         return acrEndColor;  
  47.     }  
  48.   
  49.     public int getAcrStartColor() {  
  50.         return acrStartColor;  
  51.     }  
  52.   
  53.     public int getArcwidth() {  
  54.         return arcwidth;  
  55.     }  
  56.   
  57.     public int getmArcColor() {  
  58.         return mArcColor;  
  59.     }  
  60.   
  61.     public int getmPointerColor() {  
  62.         return mPointerColor;  
  63.     }  
  64.   
  65.     public int getmTikeCount() {  
  66.         return mTikeCount;  
  67.     }  
  68.   
  69.     public int getmTextSize() {  
  70.         return mTextSize;  
  71.     }  
  72.   
  73.     public String getmText() {  
  74.         return mText;  
  75.     }  
  76.   
  77.     public int getmScendArcWidth() {  
  78.         return mScendArcWidth;  
  79.     }  
  80.   
  81.     public String getUnit() {  
  82.         return unit;  
  83.     }  
  84.     public int getTextColor() {  
  85.         return textColor;  
  86.     }  
  87.   
  88. }  

再然后在自定义view实现类 PanelView.java中获取

[java] view plain copy
 自定义仪表盘PaneView自定义仪表盘PaneView
  1. panelViewattr = new PanelViewAttr(context, attrs, defStyleAttr);  
通过get方法获取

[java] view plain copy
 自定义仪表盘PaneView自定义仪表盘PaneView
  1. mArcColor = panelViewattr.getmArcColor();  
  2.        mPointerColor = panelViewattr.getmPointerColor();  
  3.        mTikeCount = panelViewattr.getmTikeCount();  
  4.        mTextSize = panelViewattr.getmTextSize();  
  5.        mTextColor = panelViewattr.getTextColor();  
  6.        mText = panelViewattr.getmText();  
  7.        mArcWidth = panelViewattr.getArcwidth();  
  8.        mScendArcWidth = panelViewattr.getmScendArcWidth();  
  9.        unit = panelViewattr.getUnit();  
  10.        acrStartColor = panelViewattr.getAcrStartColor();  
  11.        acrEndColor = panelViewattr.getAcrEndColor();  

OK,这些都是小儿科,主要看onMeasure和OnDraw()方法

这里主要对exactly做了处理,其余的按照统一处理onMeasure()方法如下,推荐用一个方法来处理measure,不要把宽和高都写一遍,这样代码看起来会有点冗杂

[java] view plain copy
 自定义仪表盘PaneView自定义仪表盘PaneView
  1. @Override  
  2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  3.     int realWidth = startMeasure(widthMeasureSpec);  
  4.     int realHeight = startMeasure(heightMeasureSpec);  
  5.   
  6.     setMeasuredDimension(realWidth, realHeight);  
  7. }  
  8.   
  9. private int startMeasure(int msSpec) {  
  10.     int result = 0;  
  11.     int mode = MeasureSpec.getMode(msSpec);  
  12.     int size = MeasureSpec.getSize(msSpec);  
  13.     if (mode == MeasureSpec.EXACTLY) {  
  14.         result = size;  
  15.     } else {  
  16.         result = PxUtils.dpToPx(200, mContext);  
  17.     }  
  18.     //Log.d("xsf", "startMeasure " + result);  
  19.     return result;  
  20. }  

onMeasure之后决定了大小,重头戏在onDraw,直接看onraw方法

[java] view plain copy
 自定义仪表盘PaneView自定义仪表盘PaneView
  1. @Override  
  2. protected void onDraw(Canvas canvas) {  
  3.     super.onDraw(canvas);  
  4.     float percent = mPercent / 100f;  
  5.   
  6.     //最外面线条  
  7.     drawOutAcr(canvas);  
  8.     //绘制刻度  
  9.     drawerNum(canvas);  
  10.     //绘制粗圆弧  
  11.     drawInArc(canvas, percent);  
  12.     //绘制中间小圆和圆环  
  13.     drawInPoint(canvas);  
  14.     //绘制指针  
  15.     drawerPointer(canvas, percent);  
  16.     //绘制矩形和文字  
  17.     drawerRecAndText(canvas, percent);  
  18.       
  19. }  

这样写是不是显得逻辑非常清楚,不要把细节都放在ondraw方法中,用一个个函数封装是最好不过的了

首先 drawOutAcr(canvas)

画最外面的弧

[java] view plain copy
 自定义仪表盘PaneView自定义仪表盘PaneView
  1. private void drawOutAcr(Canvas canvas) {  
  2.         //最外面线条  
  3.         rectF1 = new RectF(mArcWidth, mArcWidth, getWidth() - mArcWidth, getHeight() - mArcWidth);  
  4.         canvas.drawArc(rectF1, START_ARC, DURING_ARC, false, paintOuter_Arc);  
  5.     }  

主要使用到canvas.drawArc方法,没啥好说的

执行完你会看到一个弧

自定义仪表盘PaneView


然后是画刻度drawerNum(canvas);

这个函数开始画刻度和数字

[java] view plain copy
 自定义仪表盘PaneView自定义仪表盘PaneView
  1. private void drawerNum(Canvas canvas) {  
  2.      canvas.save(); //记录画布状态  
  3.      canvas.rotate(-(180 - START_ARC + 90), getWidth() / 2, getHeight() / 2);  
  4.      float rAngle = DURING_ARC / mTikeCount;  
  5.      for (int i = 0; i < mTikeCount + 1; i++) {  
  6.          canvas.save(); //记录画布状态  
  7.          canvas.rotate(rAngle * i, getWidth() / 2, getHeight() / 2);  
  8.          canvas.drawLine(getWidth() / 2, mArcWidth, getWidth() / 220, paintOuter_Arc);//画刻度线  
  9.          canvas.drawText("" + i * 10, getWidth() / 2 - mArcWidth * 240, paintouter_Num);//画刻度  
  10.          canvas.restore();  
  11.      }  
  12.      canvas.restore();  
  13.  }  

要点:canvas.save 和canvas.restore来记录和恢复画布的状态

结合canvas.ratate旋转画布,画布旋转固定角度,画笔此时不需要变化,大大方便,这个技巧需要掌握

此时画出

自定义仪表盘PaneView


然后执行drawInArc(canvas, percent);画粗圆弧

[java] view plain copy
 自定义仪表盘PaneView自定义仪表盘PaneView
  1. private void drawInArc(Canvas canvas, float percent) {  
  2.        rectF2 = new RectF(mArcWidth + OFFSET, mArcWidth + OFFSET, getWidth() - mArcWidth - OFFSET, getHeight() - mArcWidth - OFFSET);  
  3.   
  4.        canvas.drawArc(rectF2, START_ARC, DURING_ARC, false, paintInerArc);  
  5.   
  6.        rectF3 = new RectF(mArcWidth + OFFSET, mArcWidth + OFFSET, getWidth() - mArcWidth - OFFSET, getHeight() - mArcWidth - OFFSET);  
  7.        shader = new LinearGradient(mArcWidth + OFFSET, mArcWidth + OFFSET,  
  8.                getWidth() - mArcWidth - OFFSET, getHeight() - mArcWidth - OFFSET, acrStartColor, acrEndColor, Shader.TileMode.REPEAT);  
  9.        paintInerArc_tranform.setShader(shader);  
  10.        canvas.drawArc(rectF3, START_ARC, <span style="color:#000099;">percent * DURING_ARC</span>, false, paintInerArc_tranform);  
  11.    }  

这个涉及到了粗圆弧颜色状态的变化,首先画一个白色的圆弧,画笔设置粗一点,为白色

然后使用shader来变色,在相同的圆弧上根据百分比再覆盖一层颜色,我们画弧是从150度(START_ARC)为开始位置,持续了240度(DURING_ARC)

此时状态为

自定义仪表盘PaneView


然后开始画圆环和圆点这个比较简单,注意下圆环使用空心画笔,圆点使用实心画笔即可

[java] view plain copy
 自定义仪表盘PaneView自定义仪表盘PaneView
  1. private void drawInPoint(Canvas canvas) {  
  2.         canvas.drawCircle(getWidth() / 2, getHeight() / 2, mMinRingRadius, paintOuter_Arc);//中心小圆环  
  3.         canvas.drawCircle(getWidth() / 2, getHeight() / 2, mMinCircleRadius, paint_centerPoint_Pointer);//中心圆点  
  4.     }  

自定义仪表盘PaneView


圆弧颜色变化解决了,下面来解决指针跟随百分比变化

percent在0-1之间,实现-120- +120变化(DURING_ARC=240),那么转化成数学关系就是,变化角度angle = DURING_ARC*(percent-0.5),ok表达式搞定就好搞了

[java] view plain copy
 自定义仪表盘PaneView自定义仪表盘PaneView
  1. private void drawerPointer(Canvas canvas, float percent) {  
  2.       canvas.save();  
  3.       float angel = DURING_ARC * (percent - 0.5f);  
  4.       canvas.rotate(angel, getWidth() / 2, getHeight() / 2);//指针与外弧边缘持平  
  5.       paint_centerPoint_Pointer.setStrokeWidth(mArcWidth);  
  6.       canvas.drawLine(getWidth() / 2, getHeight() / 2, getWidth() / 2, getHeight() / 2 - mArcWidth * 2 - OFFSET - mScendArcWidth, paint_centerPoint_Pointer);  
  7.       canvas.restore();  
  8.   }  

根据百分比变化位置的指针也画出来了

自定义仪表盘PaneView

最后画画文字和单位

[java] view plain copy
 自定义仪表盘PaneView自定义仪表盘PaneView
  1. private void drawerRecAndText(Canvas canvas, float percent) {  
  2.     float length = 0;  
  3.     paint_text.setTextSize(mTextSize);  
  4.   
  5.     length = paint_text.measureText(mText);  
  6.     canvas.drawText(mText, getWidth() / 2 - length / 2, (float) (getHeight() / 2 * (1 + Math.sqrt(2) / 3)), paint_text);  
  7.       
  8.     paint_text.setTextSize(mTextSize * 1.5f);  
  9.     speed = StringUtil.floatFormat(120 * percent) +<span style="color:#336666;"> unit</span>;  
  10.     length = paint_text.measureText(speed);  
  11.   
  12.     canvas.drawText(speed, getWidth() / 2 - length / 2, (float) (getHeight() / 2 * (1 + Math.sqrt(2) / 2)), paint_text);  
  13. }  

为了使文字画在中间需要使用path里面的measureText的方法,然后为了文字和速度字体有层次感,这里设置为文字的1.5倍大小

自定义仪表盘PaneView


ok很简答吧!


转载:http://blog.****.net/xsf50717/article/details/50574160