Android 自定义View — 简约的折线图

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

转载请注明出处:http://write.blog.csdn.net/postedit/50434634 接上篇 [Android 圆形百分比(进度条) 自定义view](http://blog.csdn.net/wingichoy/article/details/50334595) 昨天分手了,不开心,来练练自定义view麻痹自己,毕竟菜鸟只能靠不断练习提高。#程序员不应该有女朋友# 我们要实现的是一种只有来看趋势,不需要看具体数值,比较简约的折线图。比如下图这样的: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-21_56efaea1ada45.jpg) 这个时候,一些比较优秀的第三方图表库如:MPChart 就显得比较臃肿了。所以我们需要自定义一个折线图。 老规矩,先来看最终的实现效果: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-21_56efaea223261.jpg) 其实这种做的很简约,大概分三个步骤: 一、画坐标轴 二、画点 三、画线 那么我们开始吧Let's go (Let it go)。 设计一下大概需要的东西。首先把X轴和Y轴的数据存放在两个String[]里。 具体的点的位置用一个Map<Integer,Integer>来存放. **步骤:** 一、新建一个类,取名为SimpleLineChart继承View 重写他的构造方法。这里为了简便,就不添加自定义属性了attr.xml。 ~~~ <span style="font-size:18px;"> </span><span style="font-size:12px;"> public SimpleLineChart(Context context) { this(context, null); } public SimpleLineChart(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SimpleLineChart(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); }</span> ~~~ 二、测量大小。(继续偷懒,只支持EXACTLY,AT_MOST直接丢异常) ~~~ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); if (widthMode == MeasureSpec.EXACTLY) { mWidth = widthSize; }else if(widthMode == MeasureSpec.AT_MOST){ throw new IllegalArgumentException("width must be EXACTLY,you should set like android:width=\"200dp\""); } if (heightMode == MeasureSpec.EXACTLY) { mHeight = heightSize; }else if(widthMeasureSpec == MeasureSpec.AT_MOST){ throw new IllegalArgumentException("height must be EXACTLY,you should set like android:height=\"200dp\""); } setMeasuredDimension(mWidth, mHeight); } ~~~ 三、重点来了,开始draw。首先处理几种异常情况。当X轴或者Y轴为没有文字(也就是没有刻度的时候),抛出异常。 ~~~ if(mXAxis.length==0||mYAxis.length==0){ throw new IllegalArgumentException("X or Y items is null"); } ~~~ 当没有任何点的数据的时候,显示字符串提醒用户没有数据(实际上是往中心drawText)。  ~~~  //画坐标线的轴         Paint axisPaint = new Paint();         axisPaint.setTextSize(mYAxisFontSize);         axisPaint.setColor(Color.parseColor("#3F51B5")); ~~~ ~~~ if (mPointMap == null || mPointMap.size() == 0) { int textLength = (int) axisPaint.measureText(mNoDataMsg); canvas.drawText(mNoDataMsg, mWidth/2 - textLength/2, mHeight/2, axisPaint); } ~~~ 异常情况处理完了,开始上面三个步骤挨个画,首先来画个Y轴,计算每个刻度的间隔。他的值应该是mWidth / Y轴文字个数,然后用循环把每个刻度都画出来。再申请一个数组yPoints,存放每个Y刻度的具体坐标。 ~~~ //画 Y 轴 //存放每个Y轴的坐标 int[] yPoints = new int[mYAxis.length]; //计算Y轴 每个刻度的间距 int yInterval = (int) ((mHeight - mYAxisFontSize - 2) / (mYAxis.length)); //测量Y轴文字的高度 用来画第一个数 Paint.FontMetrics fm = axisPaint.getFontMetrics(); int yItemHeight = (int) Math.ceil(fm.descent - fm.ascent); Log.e("wing", mHeight + ""); for (int i = 0; i < mYAxis.length; i++) { canvas.drawText(mYAxis[i], 0, mYAxisFontSize + i * yInterval, axisPaint); yPoints[i] = (int) (mYAxisFontSize + i * yInterval); } ~~~ 我们运行一下,看到了如下效果: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-21_56efaea235e04.jpg) 需要注意的是,这里的坐标需要微调,大家多试一下。同理开始画X轴: ~~~ //画 X 轴 //x轴的刻度集合 int[] xPoints = new int[mXAxis.length]; Log.e("wing", xPoints.length + ""); //计算Y轴开始的原点坐标 int xItemX = (int) axisPaint.measureText(mYAxis[1]); //X轴偏移量 int xOffset = 50; //计算x轴 刻度间距 int xInterval = (int) ((mWidth - xOffset) / (mXAxis.length)); //获取X轴刻度Y坐标 int xItemY = (int) (mYAxisFontSize + mYAxis.length * yInterval); for (int i = 0; i < mXAxis.length; i++) { canvas.drawText(mXAxis[i], i * xInterval + xItemX + xOffset, xItemY, axisPaint); xPoints[i] = (int) (i * xInterval + xItemX + axisPaint.measureText(mXAxis[i]) / 2 + xOffset + 10); // Log.e("wing", xPoints[i] + ""); } ~~~ 注意这里X轴的y坐标就是画Y轴时候的最下面的文字(最后一个)的坐标,存成了xItemY。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-21_56efaea249448.jpg) 之后我们来画点,这里我采用的方法是画圆。直接drawCircle。从map中取出所有点的对应i,j然后再从两个数组 xPoints[] yPoints[]取出真实的X,Y坐标,最后画出来 ~~~ //画点 Paint pointPaint = new Paint(); pointPaint.setColor(mLineColor); Paint linePaint = new Paint(); linePaint.setColor(mLineColor); linePaint.setAntiAlias(true); //设置线条宽度 linePaint.setStrokeWidth(mStrokeWidth); pointPaint.setStyle(Paint.Style.FILL); for (int i = 0; i < mXAxis.length; i++) { if (mPointMap.get(i) == null) { throw new IllegalArgumentException("PointMap has incomplete data!"); } //画点 canvas.drawCircle(xPoints[i], yPoints[mPointMap.get(i)], mPointRadius, pointPaint); if (i > 0) {//画线 canvas.drawLine(xPoints[i - 1], yPoints[mPointMap.get(i - 1)], xPoints[i], yPoints[mPointMap.get(i)], linePaint); } } ~~~ 上面画完点之后开始画线drawLine,参数是前一个点的坐标,和后一个点的坐标。挨个画出来。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-21_56efaea25d4db.jpg)   这时候我们的最复杂的绘制就完成了。接下来来加入一点功能:参数的设置。 ~~~ /** * 设置map数据 * @param data */ public void setData(HashMap<Integer,Integer> data){ mPointMap = data; invalidate(); } /** * 设置Y轴文字 * @param yItem */ public void setYItem(String[] yItem){ mYAxis = yItem; } /** * 设置X轴文字 * @param xItem */ public void setXItem(String[] xItem){ mXAxis = xItem; } public void setLineColor(int color){ mLineColor = color; invalidate(); } ~~~ 以上代码很简单 我就不多说了。整个View完工,接下来介绍如何使用。 **使用:** ****和普通的View一样,我们直接在XML布局文件中加入SimpleLineChart,注意不要忘记包名。 ~~~ <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin" tools:context="com.wingsofts.simplelinechart.MainActivity"> <com.wingsofts.simplelinechart.SimpleLineChart android:id="@+id/simpleLineChart" android:layout_width="400dp" android:layout_height="200dp" /> </RelativeLayout> ~~~ 然后在Activity中findviewbyid,给他设置X轴的文字 Y轴的文字 还有数据源 ~~~ public class MainActivity extends AppCompatActivity { private SimpleLineChart mSimpleLineChart; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mSimpleLineChart = (SimpleLineChart) findViewById(R.id.simpleLineChart); String[] xItem = {"1","2","3","4","5","6","7"}; String[] yItem = {"10k","20k","30k","40k","50k"}; if(mSimpleLineChart == null) Log.e("wing","null!!!!"); mSimpleLineChart.setXItem(xItem); mSimpleLineChart.setYItem(yItem); HashMap<Integer,Integer> pointMap = new HashMap(); for(int i = 0;i<xItem.length;i++){ pointMap.put(i, (int) (Math.random()*5)); } mSimpleLineChart.setData(pointMap); } } ~~~ 简单的几步,就可以得到预览图的效果了!是不是很好玩!觉得好的话评论一下,star一下。祭奠我死去的爱情。 项目下载地址(求关注 求星星 ):[点击打开链接](https://github.com/githubwing/SimpleLineChart/) 下一篇来一个比较炫 比较复杂的view 自定义仪表盘 :[时尚自定义仪表盘](http://blog.csdn.net/wingichoy/article/details/50468674)
';