转载请注明出处:http://write.blog.csdn.net/postedit/50434634

    接上篇 Android 圆形百分比(进度条) 自定义view

    昨天分手了,不开心,来练练自定义view麻痹自己,毕竟菜鸟只能靠不断练习提高。#程序员不应该有女朋友#

    我们要实现的是一种只有来看趋势,不需要看具体数值,比较简约的折线图。比如下图这样的:

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

    这个时候,一些比较优秀的第三方图表库如:MPChart 就显得比较臃肿了。所以我们需要自定义一个折线图。

    老规矩,先来看最终的实现效果:

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

    其实这种做的很简约,大概分三个步骤:

    一、画坐标轴

    二、画点

    三、画线

    那么我们开始吧Let's go (Let it go)。

    设计一下大概需要的东西。首先把X轴和Y轴的数据存放在两个String[]里。

    具体的点的位置用一个Map<Integer,Integer>来存放.

    步骤:

    一、新建一个类,取名为SimpleLineChart继承View 重写他的构造方法。这里为了简便,就不添加自定义属性了attr.xml。

    1. <span style="font-size:18px;"> </span><span style="font-size:12px;"> public SimpleLineChart(Context context) {
    2. this(context, null);
    3. }
    4. public SimpleLineChart(Context context, AttributeSet attrs) {
    5. this(context, attrs, 0);
    6. }
    7. public SimpleLineChart(Context context, AttributeSet attrs, int defStyleAttr) {
    8. super(context, attrs, defStyleAttr);
    9. }</span>

    二、测量大小。(继续偷懒,只支持EXACTLY,AT_MOST直接丢异常)

    1. @Override
    2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    3. int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    4. int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    5. int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    6. int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    7. if (widthMode == MeasureSpec.EXACTLY) {
    8. mWidth = widthSize;
    9. }else if(widthMode == MeasureSpec.AT_MOST){
    10. throw new IllegalArgumentException("width must be EXACTLY,you should set like android:width=\"200dp\"");
    11. }
    12. if (heightMode == MeasureSpec.EXACTLY) {
    13. mHeight = heightSize;
    14. }else if(widthMeasureSpec == MeasureSpec.AT_MOST){
    15. throw new IllegalArgumentException("height must be EXACTLY,you should set like android:height=\"200dp\"");
    16. }
    17. setMeasuredDimension(mWidth, mHeight);
    18. }

    三、重点来了,开始draw。首先处理几种异常情况。当X轴或者Y轴为没有文字(也就是没有刻度的时候),抛出异常。

    1. if(mXAxis.length==0||mYAxis.length==0){
    2. throw new IllegalArgumentException("X or Y items is null");
    3. }

    当没有任何点的数据的时候,显示字符串提醒用户没有数据(实际上是往中心drawText)。

    1. //画坐标线的轴
    2. Paint axisPaint = new Paint();
    3. axisPaint.setTextSize(mYAxisFontSize);
    4. axisPaint.setColor(Color.parseColor("#3F51B5"));
    5.  
    1. if (mPointMap == null || mPointMap.size() == 0) {
    2. int textLength = (int) axisPaint.measureText(mNoDataMsg);
    3. canvas.drawText(mNoDataMsg, mWidth/2 - textLength/2, mHeight/2, axisPaint);
    4. }

    异常情况处理完了,开始上面三个步骤挨个画,首先来画个Y轴,计算每个刻度的间隔。他的值应该是mWidth / Y轴文字个数,然后用循环把每个刻度都画出来。再申请一个数组yPoints,存放每个Y刻度的具体坐标。

    1. //画 Y 轴
    2. //存放每个Y轴的坐标
    3. int[] yPoints = new int[mYAxis.length];
    4. //计算Y轴 每个刻度的间距
    5. int yInterval = (int) ((mHeight - mYAxisFontSize - 2) / (mYAxis.length));
    6. //测量Y轴文字的高度 用来画第一个数
    7. Paint.FontMetrics fm = axisPaint.getFontMetrics();
    8. int yItemHeight = (int) Math.ceil(fm.descent - fm.ascent);
    9. Log.e("wing", mHeight + "");
    10. for (int i = 0; i < mYAxis.length; i++) {
    11. canvas.drawText(mYAxis[i], 0, mYAxisFontSize + i * yInterval, axisPaint);
    12. yPoints[i] = (int) (mYAxisFontSize + i * yInterval);
    13. }
    14.  

    我们运行一下,看到了如下效果:

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

    需要注意的是,这里的坐标需要微调,大家多试一下。同理开始画X轴:

    1. //画 X 轴
    2. //x轴的刻度集合
    3. int[] xPoints = new int[mXAxis.length];
    4. Log.e("wing", xPoints.length + "");
    5. //计算Y轴开始的原点坐标
    6. int xItemX = (int) axisPaint.measureText(mYAxis[1]);
    7. //X轴偏移量
    8. int xOffset = 50;
    9. //计算x轴 刻度间距
    10. int xInterval = (int) ((mWidth - xOffset) / (mXAxis.length));
    11. //获取X轴刻度Y坐标
    12. int xItemY = (int) (mYAxisFontSize + mYAxis.length * yInterval);
    13. for (int i = 0; i < mXAxis.length; i++) {
    14. canvas.drawText(mXAxis[i], i * xInterval + xItemX + xOffset, xItemY, axisPaint);
    15. xPoints[i] = (int) (i * xInterval + xItemX + axisPaint.measureText(mXAxis[i]) / 2 + xOffset + 10);
    16. // Log.e("wing", xPoints[i] + "");
    17. }
    18.  

    注意这里X轴的y坐标就是画Y轴时候的最下面的文字(最后一个)的坐标,存成了xItemY。

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

    之后我们来画点,这里我采用的方法是画圆。直接drawCircle。从map中取出所有点的对应i,j然后再从两个数组 xPoints[] yPoints[]取出真实的X,Y坐标,最后画出来

    1. //画点
    2. Paint pointPaint = new Paint();
    3. pointPaint.setColor(mLineColor);
    4. Paint linePaint = new Paint();
    5. linePaint.setColor(mLineColor);
    6. linePaint.setAntiAlias(true);
    7. //设置线条宽度
    8. linePaint.setStrokeWidth(mStrokeWidth);
    9. pointPaint.setStyle(Paint.Style.FILL);
    10. for (int i = 0; i < mXAxis.length; i++) {
    11. if (mPointMap.get(i) == null) {
    12. throw new IllegalArgumentException("PointMap has incomplete data!");
    13. }
    14. //画点
    15. canvas.drawCircle(xPoints[i], yPoints[mPointMap.get(i)], mPointRadius, pointPaint);
    16. if (i > 0) {//画线
    17. canvas.drawLine(xPoints[i - 1], yPoints[mPointMap.get(i - 1)], xPoints[i], yPoints[mPointMap.get(i)], linePaint);
    18. }
    19. }
    20.  

    上面画完点之后开始画线drawLine,参数是前一个点的坐标,和后一个点的坐标。挨个画出来。

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

    这时候我们的最复杂的绘制就完成了。接下来来加入一点功能:参数的设置。

    1. /**
    2. * 设置map数据
    3. * @param data
    4. */
    5. public void setData(HashMap<Integer,Integer> data){
    6. mPointMap = data;
    7. invalidate();
    8. }
    9. /**
    10. * 设置Y轴文字
    11. * @param yItem
    12. */
    13. public void setYItem(String[] yItem){
    14. mYAxis = yItem;
    15. }
    16. /**
    17. * 设置X轴文字
    18. * @param xItem
    19. */
    20. public void setXItem(String[] xItem){
    21. mXAxis = xItem;
    22. }
    23. public void setLineColor(int color){
    24. mLineColor = color;
    25. invalidate();
    26. }

    以上代码很简单 我就不多说了。整个View完工,接下来介绍如何使用。

    使用:

    **和普通的View一样,我们直接在XML布局文件中加入SimpleLineChart,注意不要忘记包名。

    1. <?xml version="1.0" encoding="utf-8"?>
    2. <RelativeLayout
    3. xmlns:android="http://schemas.android.com/apk/res/android"
    4. xmlns:tools="http://schemas.android.com/tools"
    5. android:layout_width="match_parent"
    6. android:layout_height="match_parent"
    7. android:paddingLeft="@dimen/activity_horizontal_margin"
    8. android:paddingRight="@dimen/activity_horizontal_margin"
    9. android:paddingTop="@dimen/activity_vertical_margin"
    10. android:paddingBottom="@dimen/activity_vertical_margin"
    11. tools:context="com.wingsofts.simplelinechart.MainActivity">
    12. <com.wingsofts.simplelinechart.SimpleLineChart
    13. android:id="@+id/simpleLineChart"
    14. android:layout_width="400dp"
    15. android:layout_height="200dp" />
    16. </RelativeLayout>
    17.  

    然后在Activity中findviewbyid,给他设置X轴的文字 Y轴的文字 还有数据源

    1. public class MainActivity extends AppCompatActivity {
    2. private SimpleLineChart mSimpleLineChart;
    3. @Override
    4. protected void onCreate(Bundle savedInstanceState) {
    5. super.onCreate(savedInstanceState);
    6. setContentView(R.layout.activity_main);
    7. mSimpleLineChart = (SimpleLineChart) findViewById(R.id.simpleLineChart);
    8. String[] xItem = {"1","2","3","4","5","6","7"};
    9. String[] yItem = {"10k","20k","30k","40k","50k"};
    10. if(mSimpleLineChart == null)
    11. Log.e("wing","null!!!!");
    12. mSimpleLineChart.setXItem(xItem);
    13. mSimpleLineChart.setYItem(yItem);
    14. HashMap<Integer,Integer> pointMap = new HashMap();
    15. for(int i = 0;i<xItem.length;i++){
    16. pointMap.put(i, (int) (Math.random()*5));
    17. }
    18. mSimpleLineChart.setData(pointMap);
    19. }
    20. }
    21.  

    简单的几步,就可以得到预览图的效果了!是不是很好玩!觉得好的话评论一下,star一下。祭奠我死去的爱情。

    项目下载地址(求关注 求星星 ):点击打开链接

    下一篇来一个比较炫 比较复杂的view 自定义仪表盘 :时尚自定义仪表盘