自定义仪表盘PaneView
1、概述
最近学习自定义View,趁着周末做了一个仪表盘练练手,效果还可以,在此分享一下先上效果图(截图有点不清晰,凑合着看下吧)
项目在我的github上https://github.com/xsfelvis/PanelView
有图才能有真相,下面简要说一下如何实现的
2、实现
【分析有哪些属性需要】
在values/attr文件中进行声明,这些属性都是可以在xml中进行使用的,实现定制的,比如unit单位属性
- <resources>
- <declare-styleable name="PanelView">
- <attr name="arcColor" format="color"/>
- <attr name="arcWidth" format="dimension"/>
- <attr name="secArcWidth" format="dimension"/>
- <attr name="android:text"/>
- <attr name="tikeCount" format="integer"/>
- <attr name="pointerColor" format="color"/>
- <attr name="Unit" format="string"/>
- <attr name="android:textSize"/>
- <attr name="AcrStartColor" format="color"/>
- <attr name="AcrEndColor" format="color"/>
- <attr name="textColor" format="color"/>
- </declare-styleable>
- </resources>
然后为了结构上更加工整,推荐单独写一个文件来处理这些属性,最好不要和自定义控件混在一起
这里在PanelViewAttr.Java中做了处理
- package PanelView;
- import android.content.Context;
- import android.content.res.TypedArray;
- import android.util.AttributeSet;
- import com.xsf.panelview.R;
- import util.PxUtils;
- /**
- * Created by hzxushangfei on 2016/1/23.
- */
- public class PanelViewAttr {
- private int mArcColor;
- private int mPointerColor;
- private int mTikeCount;
- private int mTextSize;
- private String mText = "";
- private int arcwidth;
- private int mScendArcWidth;
- private String unit;//单位
- private int acrStartColor;
- private int acrEndColor;
- private int textColor;
- public PanelViewAttr(Context context, AttributeSet attrs, int defStyleAttr) {
- TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PanelView, defStyleAttr, 0);
- mArcColor = ta.getColor(R.styleable.PanelView_arcColor, context.getResources().getColor(R.color.colorPrimaryDark));
- mPointerColor = ta.getColor(R.styleable.PanelView_pointerColor, context.getResources().getColor(R.color.PointerColor));
- mTikeCount = ta.getInt(R.styleable.PanelView_tikeCount, 12);
- mTextSize = ta.getDimensionPixelSize(PxUtils.spToPx(R.styleable.PanelView_android_textSize, context), 24);
- mText = ta.getString(R.styleable.PanelView_android_text);
- arcwidth = ta.getInt(R.styleable.PanelView_arcWidth, 3);
- mScendArcWidth = ta.getInt(R.styleable.PanelView_secArcWidth, 50);
- unit = ta.getString(R.styleable.PanelView_Unit);
- acrStartColor = ta.getColor(R.styleable.PanelView_AcrStartColor, context.getResources().getColor(R.color.GREEN));
- acrEndColor = ta.getColor(R.styleable.PanelView_AcrEndColor, context.getResources().getColor(R.color.RED));
- textColor = ta.getColor(R.styleable.PanelView_textColor, context.getResources().getColor(R.color.Yellow));
- ta.recycle();
- }
- public int getAcrEndColor() {
- return acrEndColor;
- }
- public int getAcrStartColor() {
- return acrStartColor;
- }
- public int getArcwidth() {
- return arcwidth;
- }
- public int getmArcColor() {
- return mArcColor;
- }
- public int getmPointerColor() {
- return mPointerColor;
- }
- public int getmTikeCount() {
- return mTikeCount;
- }
- public int getmTextSize() {
- return mTextSize;
- }
- public String getmText() {
- return mText;
- }
- public int getmScendArcWidth() {
- return mScendArcWidth;
- }
- public String getUnit() {
- return unit;
- }
- public int getTextColor() {
- return textColor;
- }
- }
再然后在自定义view实现类 PanelView.java中获取
- panelViewattr = new PanelViewAttr(context, attrs, defStyleAttr);
- mArcColor = panelViewattr.getmArcColor();
- mPointerColor = panelViewattr.getmPointerColor();
- mTikeCount = panelViewattr.getmTikeCount();
- mTextSize = panelViewattr.getmTextSize();
- mTextColor = panelViewattr.getTextColor();
- mText = panelViewattr.getmText();
- mArcWidth = panelViewattr.getArcwidth();
- mScendArcWidth = panelViewattr.getmScendArcWidth();
- unit = panelViewattr.getUnit();
- acrStartColor = panelViewattr.getAcrStartColor();
- acrEndColor = panelViewattr.getAcrEndColor();
OK,这些都是小儿科,主要看onMeasure和OnDraw()方法
这里主要对exactly做了处理,其余的按照统一处理onMeasure()方法如下,推荐用一个方法来处理measure,不要把宽和高都写一遍,这样代码看起来会有点冗杂
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int realWidth = startMeasure(widthMeasureSpec);
- int realHeight = startMeasure(heightMeasureSpec);
- setMeasuredDimension(realWidth, realHeight);
- }
- private int startMeasure(int msSpec) {
- int result = 0;
- int mode = MeasureSpec.getMode(msSpec);
- int size = MeasureSpec.getSize(msSpec);
- if (mode == MeasureSpec.EXACTLY) {
- result = size;
- } else {
- result = PxUtils.dpToPx(200, mContext);
- }
- //Log.d("xsf", "startMeasure " + result);
- return result;
- }
onMeasure之后决定了大小,重头戏在onDraw,直接看onraw方法
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- float percent = mPercent / 100f;
- //最外面线条
- drawOutAcr(canvas);
- //绘制刻度
- drawerNum(canvas);
- //绘制粗圆弧
- drawInArc(canvas, percent);
- //绘制中间小圆和圆环
- drawInPoint(canvas);
- //绘制指针
- drawerPointer(canvas, percent);
- //绘制矩形和文字
- drawerRecAndText(canvas, percent);
- }
这样写是不是显得逻辑非常清楚,不要把细节都放在ondraw方法中,用一个个函数封装是最好不过的了
首先 drawOutAcr(canvas)
画最外面的弧
- private void drawOutAcr(Canvas canvas) {
- //最外面线条
- rectF1 = new RectF(mArcWidth, mArcWidth, getWidth() - mArcWidth, getHeight() - mArcWidth);
- canvas.drawArc(rectF1, START_ARC, DURING_ARC, false, paintOuter_Arc);
- }
主要使用到canvas.drawArc方法,没啥好说的
执行完你会看到一个弧
然后是画刻度drawerNum(canvas);
这个函数开始画刻度和数字
- private void drawerNum(Canvas canvas) {
- canvas.save(); //记录画布状态
- canvas.rotate(-(180 - START_ARC + 90), getWidth() / 2, getHeight() / 2);
- float rAngle = DURING_ARC / mTikeCount;
- for (int i = 0; i < mTikeCount + 1; i++) {
- canvas.save(); //记录画布状态
- canvas.rotate(rAngle * i, getWidth() / 2, getHeight() / 2);
- canvas.drawLine(getWidth() / 2, mArcWidth, getWidth() / 2, 20, paintOuter_Arc);//画刻度线
- canvas.drawText("" + i * 10, getWidth() / 2 - mArcWidth * 2, 40, paintouter_Num);//画刻度
- canvas.restore();
- }
- canvas.restore();
- }
要点:canvas.save 和canvas.restore来记录和恢复画布的状态
结合canvas.ratate旋转画布,画布旋转固定角度,画笔此时不需要变化,大大方便,这个技巧需要掌握
此时画出
然后执行drawInArc(canvas, percent);画粗圆弧
- private void drawInArc(Canvas canvas, float percent) {
- rectF2 = new RectF(mArcWidth + OFFSET, mArcWidth + OFFSET, getWidth() - mArcWidth - OFFSET, getHeight() - mArcWidth - OFFSET);
- canvas.drawArc(rectF2, START_ARC, DURING_ARC, false, paintInerArc);
- rectF3 = new RectF(mArcWidth + OFFSET, mArcWidth + OFFSET, getWidth() - mArcWidth - OFFSET, getHeight() - mArcWidth - OFFSET);
- shader = new LinearGradient(mArcWidth + OFFSET, mArcWidth + OFFSET,
- getWidth() - mArcWidth - OFFSET, getHeight() - mArcWidth - OFFSET, acrStartColor, acrEndColor, Shader.TileMode.REPEAT);
- paintInerArc_tranform.setShader(shader);
- canvas.drawArc(rectF3, START_ARC, <span style="color:#000099;">percent * DURING_ARC</span>, false, paintInerArc_tranform);
- }
这个涉及到了粗圆弧颜色状态的变化,首先画一个白色的圆弧,画笔设置粗一点,为白色
然后使用shader来变色,在相同的圆弧上根据百分比再覆盖一层颜色,我们画弧是从150度(START_ARC)为开始位置,持续了240度(DURING_ARC)
此时状态为
然后开始画圆环和圆点这个比较简单,注意下圆环使用空心画笔,圆点使用实心画笔即可
- private void drawInPoint(Canvas canvas) {
- canvas.drawCircle(getWidth() / 2, getHeight() / 2, mMinRingRadius, paintOuter_Arc);//中心小圆环
- canvas.drawCircle(getWidth() / 2, getHeight() / 2, mMinCircleRadius, paint_centerPoint_Pointer);//中心圆点
- }
圆弧颜色变化解决了,下面来解决指针跟随百分比变化
percent在0-1之间,实现-120- +120变化(DURING_ARC=240),那么转化成数学关系就是,变化角度angle = DURING_ARC*(percent-0.5),ok表达式搞定就好搞了
- private void drawerPointer(Canvas canvas, float percent) {
- canvas.save();
- float angel = DURING_ARC * (percent - 0.5f);
- canvas.rotate(angel, getWidth() / 2, getHeight() / 2);//指针与外弧边缘持平
- paint_centerPoint_Pointer.setStrokeWidth(mArcWidth);
- canvas.drawLine(getWidth() / 2, getHeight() / 2, getWidth() / 2, getHeight() / 2 - mArcWidth * 2 - OFFSET - mScendArcWidth, paint_centerPoint_Pointer);
- canvas.restore();
- }
根据百分比变化位置的指针也画出来了
最后画画文字和单位
- private void drawerRecAndText(Canvas canvas, float percent) {
- float length = 0;
- paint_text.setTextSize(mTextSize);
- length = paint_text.measureText(mText);
- canvas.drawText(mText, getWidth() / 2 - length / 2, (float) (getHeight() / 2 * (1 + Math.sqrt(2) / 3)), paint_text);
- paint_text.setTextSize(mTextSize * 1.5f);
- speed = StringUtil.floatFormat(120 * percent) +<span style="color:#336666;"> unit</span>;
- length = paint_text.measureText(speed);
- canvas.drawText(speed, getWidth() / 2 - length / 2, (float) (getHeight() / 2 * (1 + Math.sqrt(2) / 2)), paint_text);
- }
为了使文字画在中间需要使用path里面的measureText的方法,然后为了文字和速度字体有层次感,这里设置为文字的1.5倍大小
ok很简答吧!
转载:http://blog.****.net/xsf50717/article/details/50574160