android自定义View(仪表盘),多属性可设置
需求:以仪表盘的形式展示数据,要求区间分明,且灵活。
功能(可设置属性):
当前指示值
仪表盘种类是不超过半圆,还是超过半圆
大刻度份数和大刻度内小刻度的份数
指针和原点的颜色(默认为指针是红色,圆点是灰色)
外弧的色条,即数据区间
刻度和刻度值的颜色(默认为跟随外弧的颜色)
原点上方的Text字体的大小和颜色(默认为黑色,字体大小默认为半径的一定比例)
原点下方的Text字体的大小和颜色(默认为黑色,字体大小默认为半径的一定比例)
项目地址:https://github.com/AndroidCloud/PieDashboard 如有不足,欢迎各位issues和开支优化
GitHub地址:https://github.com/AndroidCloud
最终实现效果:
技术路线(简要技术思路,具体实现详见GitHub的Demo):
1,封装DashboradBean对象和HighlightCR对象。用于传递属性到View中进行绘制
public class DashboradBean { private int maxValue; //刻度盘最大值 private int minValue; //刻度盘最小值 private int bigSliceCount; //刻度盘大刻度区间数 private int smallSliceCount; //刻度盘大刻度中小刻度区间数 private int StartAngle; //开始角度 private int allAngle; //需要画的总角度 private List<HighlightCR> highlightCRList; //外弧色带的集合 private boolean isHalf; //是否是半圆以内 private int scaleColor; //刻度值的颜色,默认0时,颜色随色带变化 private int scaleTextColor; //刻度值的读数的颜色,默认0时,颜色随色带变化 private int centerPointColor;//中心原点的颜色 private int pointerColor; //指针的颜色 //get和set忽略
//外弧色带 public class HighlightCR { private int mStartAngle;//开始角度 private int mSweepAngle;//需要画的度数 private int mColor;//色带颜色 //get和set忽略
2,根据传递的属性对View进行绘制
首先是控制View的大小适配(根据设定的宽来适配高),如果总弧度小于半圆,View的高度取View的宽度的1/2再 多一点,以为画外弧的画笔要设置粗一点,所以为了绘制原点,需要留出部分位置。如果总弧度大于半圆, View的高度取值和宽度一样。
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int width=MeasureSpec.getSize(widthMeasureSpec); int height = 0; if (dashboradBean!=null){ //总弧度是半圆以内 if (dashboradBean.isHalf()){ height =(int) (width/2f+(width/2f/7.5f)); }else{ //总弧度大于半圆 height=width; } setMeasuredDimension(width, height); } }
3,进行绘制,分别绘制刻度盘的弧形,刻度盘的和刻度值,指针和中心原点,原点上方Text,原点下方Text
@Override protected void onDraw(Canvas canvas) { mRadius=getWidth()/2f; mHighlightRadius=mRadius/7.5f; mCenterX=mRadius; mCenterY=mRadius; super.onDraw(canvas); if (dashboradBean != null) { drawMeasures(canvas);/*** 刻度线和刻度值*/ drawStripe(canvas);/**刻度盘的弧形*/ drawTopTexts(canvas);/**画中心原点上方文本*/ drawEndTexts(canvas);/**画中心原点下方文本*/ drawPointer(canvas); /**画指针和中心原点*/ } }绘制刻度线和刻度值(刻度线的长度和刻度值的字体大小都是根据View的宽度来按照一定比例换算得来,以达到 适配的目的),原理是旋转画布后画好,在旋转回来,依次把所有刻度线和刻度值画好
/*** 刻度线和刻度值*/ private void drawMeasures(Canvas canvas) { paint.setStrokeCap(Paint.Cap.ROUND); paint.setStrokeWidth(2); Paint p=new Paint(); p.setStyle(Paint.Style.FILL); p.setStrokeWidth(1); p.setTextSize(mHighlightRadius / 1.2f); p.setAntiAlias(true); p.setTextAlign(Paint.Align.CENTER); float averageBigangle=((float)dashboradBean.getAllAngle()) /((float)dashboradBean.getBigSliceCount()); for (int i = 0; i <= dashboradBean.getBigSliceCount(); i++) { //绘制大刻度 float angle = i *averageBigangle+dashboradBean.getStartAngle() ; float[] point1 = getCoordinatePoint(mRadius-mHighlightRadius/0.85f, angle); float[] point2 = getCoordinatePoint(mRadius-mHighlightRadius/0.85f-mHighlightRadius/5f*2.8f, angle); if ( dashboradBean.getHighlightCRList() != null) { for (int j = 0; j < dashboradBean.getHighlightCRList().size(); j++) { HighlightCR highlightCR = dashboradBean.getHighlightCRList().get(j); if (highlightCR.getColor() == 0 || highlightCR.getSweepAngle() == 0) continue; if (angle>=highlightCR.getStartAngle()&&angle <= highlightCR.getStartAngle() + highlightCR.getSweepAngle()) { paint.setColor(highlightCR.getColor()); p.setColor(highlightCR.getColor()); break; } } } else { paint.setColor(Color.BLACK); p.setColor(Color.BLACK); } if (dashboradBean.getScaleColor()!=0){ paint.setColor(dashboradBean.getScaleColor()); } paint.setStrokeWidth(mHighlightRadius / 6f); canvas.drawLine(point1[0], point1[1], point2[0], point2[1], paint); //绘制圆盘上的数字 String number =trimFloat(((float) (dashboradBean.getMaxValue()-dashboradBean.getMinValue())) /((float)dashboradBean.getBigSliceCount())*i+(float)dashboradBean.getMinValue()) +""; if (dashboradBean.getScaleTextColor()!=0){ p.setColor(dashboradBean.getScaleTextColor()); } //旋转绘制 canvas.rotate(360f - (float) dashboradBean.getAllAngle() / 2f + (float) i * averageBigangle, mCenterX, mCenterY); canvas.drawText(number, mCenterX, mHighlightRadius * 2.75f, p); canvas.rotate(-360f+(float)dashboradBean.getAllAngle()/2f-(float)i*averageBigangle,mCenterX,mCenterY); } //绘制小的子刻度 float averageSmallangle=((float)dashboradBean.getAllAngle() )/(float)(dashboradBean.getSmallSliceCount()*dashboradBean.getBigSliceCount()); for (int i = 0; i < dashboradBean.getSmallSliceCount()*dashboradBean.getBigSliceCount(); i++) { float angle = i * averageSmallangle + dashboradBean.getStartAngle(); float[] point1 = getCoordinatePoint(mRadius-mHighlightRadius/0.85f, angle); float[] point2 = getCoordinatePoint(mRadius-mHighlightRadius/0.85f-mHighlightRadius/5f*1.4f, angle); if ( dashboradBean.getHighlightCRList() != null) { for (int j = 0; j < dashboradBean.getHighlightCRList().size(); j++) { HighlightCR highlightCR = dashboradBean.getHighlightCRList().get(j); if (highlightCR.getColor() == 0 || highlightCR.getSweepAngle() == 0) continue; if (angle>=highlightCR.getStartAngle()&&angle <= highlightCR.getStartAngle() + highlightCR.getSweepAngle()) { paint.setColor(highlightCR.getColor()); break; } } } else { paint.setColor(Color.BLACK); } if (dashboradBean.getScaleColor()!=0){ paint.setColor(dashboradBean.getScaleColor()); } paint.setStrokeWidth(mHighlightRadius/6f/2f); canvas.drawLine(point1[0], point1[1], point2[0], point2[1], paint); } }绘制刻度盘的外弧,包括粗的外弧和细的外弧(外弧的粗细程度都是根据View的宽度来按照一定比例换算得来, 以达到适配的目的)
/**刻度盘的弧形*/ private void drawStripe(Canvas canvas) { if (dashboradBean.getHighlightCRList()!=null){ for (int i = 0; i < dashboradBean.getHighlightCRList().size(); i++) { HighlightCR highlightCR = dashboradBean.getHighlightCRList().get(i); if (highlightCR.getColor() == 0 || highlightCR.getSweepAngle() == 0) continue; paint.setColor(highlightCR.getColor()); paint.setStrokeWidth(mHighlightRadius); paint.setStrokeCap(Paint.Cap.ROUND); paint.setStyle(Paint.Style.STROKE); RectF rectF1=new RectF(mHighlightRadius/2f,mHighlightRadius/2f,getWidth()-mHighlightRadius/2f,getWidth()-mHighlightRadius/2f); RectF rectF2=new RectF(mHighlightRadius/0.85f,mHighlightRadius/0.85f,getWidth()-mHighlightRadius/0.85f,getWidth()-mHighlightRadius/0.85f); canvas.drawArc(rectF1, highlightCR.getStartAngle(), highlightCR.getSweepAngle(), false, paint); paint.setStrokeWidth(mHighlightRadius/6f); canvas.drawArc(rectF2, highlightCR.getStartAngle(), highlightCR.getSweepAngle(), false, paint); } } }
绘制指针和原点,指针为一个封闭的三角形,指针的顶点根据传入的需要显示的当前值来进行计算(计算前先判 断该值是否在有效区间内,超过最大值显示最大值,超过最小值显示最小值)指针长度按照View的宽度的一 定比例换算,达到适配,原点为圆形。
/**画指针和中心原点*/ private void drawPointer(Canvas canvas) { float degree=0; if (realTimeValue>=dashboradBean.getMinValue()&&realTimeValue<=dashboradBean.getMaxValue()){ degree=(float)(dashboradBean.getStartAngle())+((realTimeValue-dashboradBean.getMinValue())/ (dashboradBean.getMaxValue()-dashboradBean.getMinValue()))*(float)dashboradBean.getAllAngle(); }else if(realTimeValue<dashboradBean.getMinValue()){ degree=(float)dashboradBean.getStartAngle(); }else{ degree=(float)(dashboradBean.getStartAngle())+(float)dashboradBean.getAllAngle(); } float point[]=getCoordinatePoint(mRadius-mHighlightRadius*2.4f,degree); paint.setStyle(Paint.Style.FILL); Path p=new Path(); p.moveTo(point[0], point[1]); float point_left[]=getCoordinatePoint(mHighlightRadius / 2f,degree-90); float point_right[]=getCoordinatePoint(mHighlightRadius / 2f,degree+90); p.lineTo(point_left[0], point_left[1]); p.lineTo(point_right[0], point_right[1]); p.close(); if(dashboradBean.getPointerColor()!=0){ paint.setColor(dashboradBean.getPointerColor()); }else{ paint.setColor(Color.RED); } canvas.drawPath(p, paint); if (dashboradBean.getCenterPointColor()!=0){ paint.setColor(dashboradBean.getCenterPointColor()); } else { paint.setColor(Color.parseColor("#A9AFAE")); } canvas.drawCircle(mCenterX,mCenterY,mHighlightRadius/1.65f,paint); }
最后绘制原点上方和下方的Text,字体绘制的位置和字体的大小都是根据View的宽度来按照一定比例换算得到的。
/**画中心原点下方文本*/ private void drawEndTexts(Canvas canvas) { if (EndTextSize==0){ paint.setTextSize(mHighlightRadius / 1.2f); }else { paint.setTextSize(EndTextSize); } if (EndTextColor==0){ paint.setColor(Color.BLACK); }else{ paint.setColor(EndTextColor); } paint.setStrokeWidth(1); paint.setStyle(Paint.Style.FILL); paint.setTextAlign(Paint.Align.CENTER); canvas.drawText(endText,mCenterX,mCenterY+3f*mHighlightRadius,paint); } /**画指针和中心原点*/ private void drawTopTexts(Canvas canvas) { if (TopTextSize==0){ paint.setTextSize(mHighlightRadius / 1.4f); }else { paint.setTextSize(TopTextSize); } if (TopTextColor==0){ paint.setColor(Color.BLACK); }else{ paint.setColor(TopTextColor); } paint.setStrokeWidth(1); paint.setStyle(Paint.Style.FILL); paint.setTextAlign(Paint.Align.CENTER); canvas.drawText(topText,mCenterX,mCenterY-mHighlightRadius,paint); }
4,在Activity中使用,Demo的第一组示例布局如下所示,宽度取屏幕的一半
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:layout_marginBottom="10dp" android:orientation="horizontal"> <com.example.vmmet.mypiedashboard.view.NewDashboardView android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" android:id="@+id/view1" /> <com.example.vmmet.mypiedashboard.view.NewDashboardView android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" android:id="@+id/view2" /> </LinearLayout>
如图中所示的第一个示例。
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); /**大刻度份数和小刻度份数(半圆)*/ setView_1_2();
/**大刻度份数和小刻度份数(半圆)*/ private void setView_1_2() { List<HighlightCR> highlights = new ArrayList<>(); highlights.add(new HighlightCR(180, 40, Color.parseColor("#4CAF50"))); highlights.add(new HighlightCR(220, 50, Color.parseColor("#EEC900"))); highlights.add(new HighlightCR(270, 90, Color.parseColor("#F44336"))); DashboradBean dashboradBean=new DashboradBean(); dashboradBean.setHighlightCRList(highlights); dashboradBean.setAllAngle(180); dashboradBean.setStartAngle(180); dashboradBean.setBigSliceCount(5); dashboradBean.setSmallSliceCount(3); dashboradBean.setMaxValue(100); dashboradBean.setMinValue(0); dashboradBean.setIsHalf(true); view1.setDashboradBean(dashboradBean); view1.setRealTimeValue(60); /////////////////////////////// DashboradBean dashboradBean2=new DashboradBean(); dashboradBean2.setHighlightCRList(highlights); dashboradBean2.setAllAngle(180); dashboradBean2.setStartAngle(180); dashboradBean2.setBigSliceCount(10); dashboradBean2.setSmallSliceCount(5); dashboradBean2.setMaxValue(200); dashboradBean2.setMinValue(100); dashboradBean2.setIsHalf(true); view2.setDashboradBean(dashboradBean2); view2.setRealTimeValue(120); }
做开发,需要脚踏实地,日积月累,愿你我共勉