Android实现轻量线性与百分比图表的方法

Android实现轻量线性与百分比图表的方法,第1张

概述前言经常要用到图表统计数据,在WEB开发中图表绘制是一件简单的事情,因为有比较多的开源方案。但在Android中开源方案并不多。但目前github上有多个关于图表的框架,比如MPAndroidChart很好,但是很大,没必要因为一

前言

经常要用到图表统计数据,在WEB开发中图表绘制是一件简单的事情,因为有比较多的开源方案。但在AndroID中开源方案并不多。但目前github上有多个关于图表的框架,比如MPAndroIDChart很好,但是很大,没必要因为一个小的图标让工程项目扩大很多,另外有些轻量级的框架,但是个人感觉都很难满足自己的需求,再者就算很好的框架,那也是别人的,只有自己动手写起来,了解前前后后的坑,自己才能成长,而且在写的过程,我们能发现更多的细节,比如绘制的时候内存分配的问题,Canvas直接绘制和通过Bitmap绘制等等,所以这篇文章的目的:

      1.是给大家提供自定义view绘制的思路

      2.滑动自定义view的部分区域怎么实现

      3.path动画绘制的实现

      4.熟悉canvas的API,总之能直接动手了,那就自定义view就通关了,所以就写这篇文章主要是鼓励大家多去实现。

效果图


线性图表实现的思路:

线性表是最基本、最简单、也是最常用的一种数据结构。线性表中数据元素之间的关系是一对一的关系,即除了第一个和最后一个数据元素之外,其它数据元素都是首尾相接的,注意,这句话只适用大部分线性表,而不是全部。

由于屏幕的宽度有限,所以我们一屏经过计算,最好显示的7个点,所以我们首先需要对我们的vIEw宽度进行计算,首先拿到屏幕的宽度,然后再进行/7,得到每个间隔的宽度,然后乘以我们x的坐标点的个数,其中的onMeasure的方法

 int wIDthParentMeasureMode = MeasureSpec.getMode(wIDthMeasureSpec); int wIDthParentMeasureSize = MeasureSpec.getSize(wIDthMeasureSpec); int heightParentMeasureMode = MeasureSpec.getMode(heightmeasureSpec); int heightParentMeasureSize = MeasureSpec.getSize(heightmeasureSpec); int resultWIDthSize = 0; int resultHeightSize = 0; int resultWIDthMode = MeasureSpec.EXACTLY;//用来对childVIEw进行计算的 int resultHeightmode = MeasureSpec.EXACTLY; int paddingWIDth = getpaddingleft() + getpaddingRight(); int paddingHeight = getpaddingtop() + getpaddingBottom(); VIEwGroup.LayoutParams thisLp = getLayoutParams(); switch (wIDthParentMeasureMode) {  //父类不加限制给子类  case MeasureSpec.UnspecIFIED:   //这个代表在布局写死了宽度   if (thisLp.wIDth > 0) {    resultWIDthSize = thisLp.wIDth;    resultWIDthMode = MeasureSpec.EXACTLY;   } else {    resultWIDthSize = (int) (getYMaxTextWIDth() + mXinterval * mXdots.length);    resultWIDthMode = MeasureSpec.UnspecIFIED;   }   break;  case MeasureSpec.AT_MOST:   //这个代表在布局写死了宽度   if (thisLp.wIDth > 0) {    resultWIDthSize = thisLp.wIDth;    resultWIDthMode = MeasureSpec.EXACTLY;   } else if (thisLp.wIDth == VIEwGroup.LayoutParams.MATCH_PARENT) {    resultWIDthSize = Math.max(0,wIDthParentMeasureSize - paddingWIDth);    resultWIDthMode = MeasureSpec.AT_MOST;   } else if (thisLp.wIDth == VIEwGroup.LayoutParams.WRAP_CONTENT) {    resultWIDthSize = (int) (getYMaxTextWIDth() + mXinterval * mXdots.length);    resultWIDthMode = MeasureSpec.AT_MOST;   }   break;  case MeasureSpec.EXACTLY:   //这个代表在布局写死了宽度   if (thisLp.wIDth > 0) {    resultWIDthSize = Math.min(wIDthParentMeasureSize,thisLp.wIDth);    resultWIDthMode = MeasureSpec.EXACTLY;   } else if (thisLp.wIDth == VIEwGroup.LayoutParams.MATCH_PARENT) {    resultWIDthSize = wIDthParentMeasureSize;    resultWIDthMode = MeasureSpec.EXACTLY;   } else if (thisLp.wIDth == VIEwGroup.LayoutParams.WRAP_CONTENT) {    resultWIDthSize = (int) (getYMaxTextWIDth() + mXinterval * mXdots.length);    resultWIDthMode = MeasureSpec.AT_MOST;   }   break; }
 switch (heightParentMeasureMode) {  //父vIEw不加限制  case MeasureSpec.UnspecIFIED:   //这个代表在布局写死了宽度   if (thisLp.height > 0) {    resultHeightSize = thisLp.height;    resultHeightmode = MeasureSpec.EXACTLY;   } else {    resultHeightSize = (int) (getYMaxTextHeight() + mYvisibleNum * mYinterval + getXMaxTextHeight());    resultHeightmode = MeasureSpec.UnspecIFIED;   }   break;  case MeasureSpec.AT_MOST:   if (thisLp.height > 0) {    resultHeightSize = heightParentMeasureSize;    resultHeightmode = MeasureSpec.EXACTLY;   } else if (thisLp.height == VIEwGroup.LayoutParams.MATCH_PARENT) {    resultHeightSize = Math.max(0,heightParentMeasureSize - paddingHeight);    resultHeightmode = MeasureSpec.AT_MOST;   } else if (thisLp.height == VIEwGroup.LayoutParams.WRAP_CONTENT) {    resultHeightSize = (int) (getYMaxTextHeight() + mYvisibleNum * mYinterval + getXMaxTextHeight());    resultHeightmode = MeasureSpec.UnspecIFIED;   }   break;  case MeasureSpec.EXACTLY:   //这个代表在布局写死了宽度   if (thisLp.height > 0) {    resultHeightSize = Math.min(heightParentMeasureSize,getMeasureDWIDth());    resultHeightmode = MeasureSpec.EXACTLY;   } else if (thisLp.wIDth == VIEwGroup.LayoutParams.MATCH_PARENT) {    resultHeightSize = heightParentMeasureSize;    resultHeightmode = MeasureSpec.EXACTLY;   } else if (thisLp.wIDth == VIEwGroup.LayoutParams.WRAP_CONTENT) {    resultHeightSize = (int) (getYMaxTextHeight() + mYvisibleNum * mYinterval + getXMaxTextHeight());    resultHeightmode = MeasureSpec.AT_MOST;   }   break; } setMeasuredDimension(MeasureSpec.makeMeasureSpec(resultWIDthSize,resultWIDthMode),MeasureSpec.makeMeasureSpec(resultHeightSize,resultHeightmode));

设置好了尺寸,我们就可以绘制界面,这里我们onDraw的时候,就依次绘制横线和竖线,在绘制横线的时候,将Y坐标的数字一起绘制上去,同理绘制竖线的时候,把x坐标的数字绘制上去,折线的画根据数字计算出坐标点,然后创建一个path,首先moveto(firstX,firstY) ,然后lineto下面的点就可以了,最后绘制上path,然而这样的话,我们在滑动的时候,会发现这个vIEw都会跟着一起滚动了,那么我们怎样才能实现vIEw的部分pinned呢?在这个时候,我们就需要先创建一个bitmap,将需要滑动的部分绘制到这个bitmap上去,然后bitmap在绘制到这个canvas上的时候,保持固定的位置就行了,好了再说就懵逼了,还是上代码吧:

 float temptableleftpadding = getYMaxTextWIDth(); if (mBitmap == null || mYNumCanvas == null) {  mBitmap = Bitmap.createBitmap((int) (getMeasureDWIDth() - getYMaxTextWIDth()),getMeasuredHeight(),Bitmap.Config.ARGB_8888);  mYNumCanvas = new Canvas(mBitmap); } mYNumCanvas.drawcolor(color.transparent,PorterDuff.Mode.CLEAR); mYNumCanvas.translate(mScrollPosX,0);//这段代码就是来实现滑动的 *** 作 //绘制横线 for (int y = 0,size = mYdots.length; y < size; y++) {  String tempText = String.valueOf(mYdots[mYdots.length - 1 - y]);  mYNumCanvas.drawline(0,(float) (mYinterval * y),(float) (mXdots.length * mXinterval),mXlinePaint);  canvas.drawText(tempText,getYMaxTextWIDth() - mYNumPaint.measureText(tempText),getYMaxTextHeight() + (float) (mYinterval * y),mYNumPaint); } //绘制竖线 for (int x = 0,size = mXdots.length; x <= size; x++) {  mYNumCanvas.drawline((float) (mXinterval * x),(float) (mXinterval * x),(float) (mYinterval * mYvisibleNum),mXlinePaint);  if (x >= 1) {   String tempText = mXdots[x - 1];   mYNumCanvas.drawText(tempText,(float) (mXinterval * x) - mYNumPaint.measureText(tempText) / 2,(float) (mYvisibleNum * mYinterval + getYMaxTextHeight()),mYNumPaint);  } } if (isAnimationopen)//是否需要开启动画绘制,这个后面会解释实现方式  mYNumCanvas.drawPath(mlineDrawPath,mlinePaint); else  mYNumCanvas.drawPath(mlinePath,mlinePaint); canvas.drawBitmap(mBitmap,temptableleftpadding,getYMaxTextHeight() / 2,null);

上面的mScrollPosX是根据手势监听类GestureDetector来获取的:

@OverrIDepublic boolean ontouchEvent(MotionEvent event) { if (!isAnimationopen || isDrawOver)  return mGestureDetector.ontouchEvent(event); return super.ontouchEvent(event);}

然而绘制了,我们感觉还缺少了什么,嗯,没错就是动画效果,这里我们用到通过的path绘制实现动画的方案,就是先通过PathMeasure得到path的长度,然后根据动画时间,通过ValueAnimator计算它在某个时刻的坐标,然后重新进行绘制path路径:

private voID startPathAnim(long duration) { ValueAnimator valueAnimator = ValueAnimator.offloat(0,mlineLength); valueAnimator.setDuration(duration); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {  @OverrIDe  public voID onAnimationUpdate(ValueAnimator animation) {   float value = (float) animation.getAnimatedValue();   // 获取当前点坐标封装到mCurrentposition   mPathMeasure.getPosTan(value,mCurrentposition,null);   mlineDrawPath.lineto(mCurrentposition[0],mCurrentposition[1]);   invalIDate();  } }); valueAnimator.start();}

百分比圆形图表实现

其实这个的实现,相比上一个少了很多,大多是集中在onDraw方法里面,关键点是在百分比的数字,怎么横向显示在扇形区域,这里我就主要这个计算规则提出来:

private voID drawText(Canvas canvas,float sweepAngle,float startAngle,ArcVo temp) { float mIDdleAngle; mIDdleAngle = startAngle + sweepAngle / 2; float startX; float startY; float endX; float endY; String drawText = temp.getPercentInCircle() * 100 + "%"; if (mIDdleAngle <= 90) {  //在第四象限  double angle = mIDdleAngle;  angle = Math.toradians(angle);  startY = endY = (float) (Math.sin(angle) * mRaduis + mRaduis);  endX = (float) (mRaduis + Math.cos(angle) * mRaduis);  startX = endX - UiUtils.getTextWIDth(drawText,mTextPaint); } else if (mIDdleAngle <= 180) {  //在第三象限  double angle = 180 - mIDdleAngle;  angle = Math.toradians(angle);  startY = endY = (float) (Math.sin(angle) * mRaduis + mRaduis);  startX = (float) (mRaduis - Math.cos(angle) * mRaduis);  endX = startX + UiUtils.getTextWIDth(drawText,mTextPaint); } else if (mIDdleAngle <= 270) {  //在第二象限  double angle = 270 - mIDdleAngle;  angle = Math.toradians(angle);  startY = endY = (float) (mRaduis - Math.cos(angle) * mRaduis);  startX = (float) (mRaduis - Math.sin(angle) * mRaduis);  endX = startX + UiUtils.getTextWIDth(drawText,mTextPaint); } else {  //在第一象限  double angle = 360 - mIDdleAngle;  angle = Math.toradians(angle);  startY = endY = (float) (mRaduis - Math.sin(angle) * mRaduis);  endX = (float) (mRaduis + Math.cos(angle) * mRaduis);  startX = endX - UiUtils.getTextWIDth(drawText,mTextPaint); } mTextPath.reset(); mTextPath.moveto(startX,startY); mTextPath.lineto(endX,endY); if (mIDdleAngle > 180) {  canvas.drawTextOnPath(drawText,mTextPath,UiUtils.getTextHeight(drawText,mTextPaint),mTextPaint); } else {  canvas.drawTextOnPath(drawText,-UiUtils.getTextHeight(drawText,mTextPaint); }} @OverrIDeprotected voID onDraw(Canvas canvas) { if (!canDraw()) return; float sweepAngle; float startAngle = 0; for (int i = 0,size = mdisArcList.size(); i < size; i++) {  ArcVo temp = mdisArcList.get(i);  marcPaint.setcolor(temp.getScancolor());  sweepAngle = temp.getPercentInCircle() * 360;  canvas.drawArc(mDrawCircleRect,startAngle,sweepAngle,true,marcPaint);  drawText(canvas,temp);  startAngle = startAngle + sweepAngle; }}

使用方式:

如果你觉得你们的项目正好要用到类似的图标,在项目的gradle文件中,增加compile 'wellijohn.org.simplelinechart:linechart:0.0.2'具体的方法,欢迎移步到github上去看,已经封装成库上传至jcenter,上面有具体的使用方法(图表地址),目前暴露的方法不多,可以留言增加

github地址:https://github.com/WelliJohn/LineChart

本地下载:http://xiazai.jb51.net/201712/yuanma/LineChart(jb51.net).rar

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对编程小技巧的支持。

总结

以上是内存溢出为你收集整理的Android实现轻量线性与百分比图表的方法全部内容,希望文章能够帮你解决Android实现轻量线性与百分比图表的方法所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/web/1143424.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-31
下一篇 2022-05-31

发表评论

登录后才能评论

评论列表(0条)

保存