• Android自定义控件-折线图


    好长时间没有更新博客了,终于可以抽出时间写点东西了,写点什么呢?最近在qq群里边有人问,下边的这个控件怎么画?如下图所示:图可以左右拖动,直到显示完全为止。刚开始看到这个效果图,我也想了一下总共分为以下几个步骤:

    (1)坐标轴的绘画,并绘画坐标轴上的坐标值

    (2)绘画坐标上的点,并将其串联起来

    (3)最后进行封闭图形的填充

    (4)事件的拖动重绘

    1、首先定义自定义属性文件,并确定坐标轴的颜色,宽度,坐标文字的大小,线的颜色等

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <declare-styleable name="LineChart">
        <attr name="xylinecolor" format="color" ></attr>
        <attr name="xylinewidth" format="dimension"></attr>
        <attr name="xytextcolor" format="color"></attr>
        <attr name="xytextsize" format="dimension"></attr>
        <attr name="linecolor" format="color"></attr>
        <attr name="interval" format="dimension"></attr>
        <attr name="bgcolor" format="color"></attr>
        </declare-styleable>
        
    </resources>
    

    2、主布局文件

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:ypm = "http://schemas.android.com/apk/res/com.ypm.linechartdemo"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
       >
    <com.ypm.linechartdemo.LineChart
        android:id="@+id/id_linechart"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        ypm:xylinecolor="@color/xylinecolor"
        ypm:xylinewidth="@dimen/xylinewidth"
       ypm:xytextsize = "@dimen/xytextsize"
       ypm:linecolor="@color/linecolor"
      >
        
    </com.ypm.linechartdemo.LineChart>
    
    </RelativeLayout> 

    3、接下来就是自定义LineChart控件,首先定义一些列的变量值如下:

    /**
    	 * 坐标轴的颜色
    	 */
    	private int xyColor;
    
    	/**
    	 * 坐标轴的宽度
    	 */
    	private int xyWidth;
    
    	/**
    	 * 坐标轴文字的颜色
    	 */
    	private int xyTextColor;
    
    	/**
    	 * 坐标轴文字的大小
    	 */
    	private int xyTextSize;
    
    	/**
    	 * 坐标轴的之间的间距
    	 */
    	private int interval;
    
    	/**
    	 * 折线的颜色
    	 */
    	private int lineColor;
    
    	/**
    	 * 背景颜色
    	 */
    	private int bgColor;
    
    	/**
    	 * 原点坐标最大x
    	 */
    	private int ori_x;
    	
    	/**
    	 * 第一个点的坐标
    	 */
    	private int first_x;
    	
    	/**
    	 * 第一个点的坐标最小x,和最大x坐标
    	 */
    	private int ori_min_x,ori_max_x;
    
    	/**
    	 * 原点坐标y
    	 */
    	private int ori_y;
    
    	/**
    	 * x的刻度值长度 默认值40
    	 */
    	private int xScale = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 80, getResources()
    			.getDisplayMetrics());
    
    	/**
    	 * y的刻度值长度
    	 */
    	private int yScale = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 55, getResources()
    			.getDisplayMetrics());
    
    	/**
    	 * x刻度
    	 */
    	private String[] xLabels;
    
    	/**
    	 * y刻度
    	 */
    	private String[] yLabels;
    
    	/**
    	 * x坐标轴中最远的坐标值
    	 */
    	private int maxX_X, maxX_Y;
    
    	/**
    	 * y坐标轴的最远坐标值
    	 */
    	private int minY_X, minY_Y;
    
    	/**
    	 * x轴最远的坐标轴
    	 */
    	private int x_last_x, x_last_y;
    	/**
    	 * y轴最远的坐标值
    	 */
    	private int y_last_x, y_last_y;
    
    	private double[] dataValues;
    	
    	/**
    	 * 滑动时候,上次手指的x坐标
    	 */
    	private float startX;

    4、读取属性文件上的值

    public LineChart (Context context , AttributeSet attrs , int defStyle)
    	{
    		super(context, attrs, defStyle);
    		TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.LineChart);
    		int count = array.getIndexCount();
    		for (int i = 0; i < count; i++)
    		{
    			int attr = array.getIndex(i);
    			switch (attr)
    			{
    				case R.styleable.LineChart_xylinecolor:
    					xyColor = array.getColor(attr, Color.GRAY);
    
    					break;
    
    				case R.styleable.LineChart_xyline
    					xyWidth = (int) array.getDimension(attr, 5);
    					break;
    
    				case R.styleable.LineChart_xytextcolor:
    
    					xyTextColor = array.getColor(attr, Color.BLACK);
    					break;
    				case R.styleable.LineChart_xytextsize:
    					xyTextSize = (int) array.getDimension(attr, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
    							12, getResources().getDisplayMetrics()));
    					break;
    
    				case R.styleable.LineChart_linecolor:
    
    					lineColor = array.getColor(attr, Color.GRAY);
    					break;
    
    				case R.styleable.LineChart_bgcolor:
    					bgColor = array.getColor(attr, Color.WHITE);
    					break;
    
    				case R.styleable.LineChart_interval:
    					interval = (int) array.getDimension(attr, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
    							100, getResources().getDisplayMetrics()));
    					break;
    				default:
    					break;
    			}
    		}
    		array.recycle();
    	}
    

     5、初始化相应的坐标值,在onMeasure中

      主要进行原点,第一个点坐标,以及x最大值的相关的计算

     1     int width = getWidth();
     2         int height = getHeight();
     3 
     4         ori_x = 40;
     5         ori_y = height - 40;
     6 
     7         maxX_X = width - 50;
     8         minY_Y = 50;
     9         
    10         
    11         ori_min_x = width - 50 -40 - dataValues.length * xScale;
    12         first_x = ori_x;
    13         ori_max_x = first_x;

    6、绘画坐标轴

     1 /**
     2      * 
     3      * 功能描述:绘画坐标轴
     4      * 
     5      * @param canvas
     6      * @版本 1.0
     7      * @创建者 ypm
     8      * @创建时间 2015-8-24 上午10:39:59
     9      * @版权所有 
    10      * @修改者 ypm
    11      * @修改时间 2015-8-24 上午10:39:59 修改描述
    12      */
    13     private void drawXYLine(Canvas canvas)
    14     {
    15         Paint paint = new Paint();
    16         paint.setColor(xyColor);
    17         paint.setAntiAlias(true);
    18         paint.setStrokeWidth(xyWidth);
    19         paint.setTextSize(xyTextSize);
    20         // 绘画x轴
    21         int max = first_x + (xLabels.length-1) * xScale + 50;
    22         if (max > maxX_X)
    23         {
    24             max = getMeasuredWidth();
    25         }
    26 
    27         x_last_x = max;
    28         x_last_y = ori_y;
    29         canvas.drawLine(first_x, ori_y, max, ori_y, paint);
    30         // 绘画y轴
    31         int min = ori_y - (yLabels.length - 1) * yScale - 50;
    32         if (min < minY_Y)
    33         {
    34             min = minY_Y;
    35         }
    36         y_last_x = first_x;
    37         y_last_y = min;
    38         canvas.drawLine(first_x, ori_y, first_x, min, paint);
    39 
    40         // 绘画x轴的刻度
    41         drawXLablePoints(canvas, paint);
    42         // 绘画y轴的刻度
    43         drawYLablePoints(canvas, paint);
    44 
    45     }

    7、绘画折线图

    这里运用到了多边形的绘画,通过Path进行绘画,并使用了多边形的填充,以及xfermode的相关知识,可以查相关的api进行了解

    
    
     1 private void drawDataLine(Canvas canvas)
     2     {
     3         Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
     4         // paint.setStyle(Paint.Style.FILL);
     5         paint.setColor(xyColor);
     6         Path path = new Path();
     7         for (int i = 0; i < dataValues.length; i++)
     8         {
     9             int x = first_x + xScale * i;
    10             if (i == 0)
    11             {
    12                 path.moveTo(x, getYValue(dataValues[i]));
    13             }
    14             else
    15             {
    16                 path.lineTo(x, getYValue(dataValues[i]));
    17             }
    18             canvas.drawCircle(x, getYValue(dataValues[i]), xyWidth, paint);
    19         }
    20         path.lineTo(first_x + xScale * (dataValues.length - 1), ori_y);
    21         path.lineTo(first_x, ori_y);
    22         path.close();
    23         paint.setStrokeWidth(5);
    24         // paint.setColor(Color.parseColor("#D7FFEE"));
    25         paint.setColor(Color.parseColor("#A23400"));
    26         paint.setAlpha(100);
    27         // 画折线
    28         canvas.drawPath(path, paint);
    29         paint.setStyle(Paint.Style.FILL);
    30         paint.setColor(Color.RED);
    31         canvas.clipPath(path);
    32 
    33         // 将折线超出x轴坐标的部分截取掉
    34         paint.setStyle(Paint.Style.FILL);
    35         paint.setColor(bgColor);
    36         paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));
    37         RectF rectF = new RectF(0, 0, x_last_x, ori_y);
    38         canvas.drawRect(rectF, paint);
    39     }
    40 
    41     private float getYValue(double value)
    42     {
    43 
    44         return (float) (ori_y - value / 50 * yScale);
    45     }

    8、事件的拖动,重写onTouchEvent方法,

    这里主要的逻辑就是:

    (1)当手机的宽度小于坐标值的最大值的时候,就禁止拖动

    (2)如果超过手机的宽度的时候,就通过裁剪功能将渲染的图像就行裁剪,拖动的时候,将没有显示的部分进行显示

    主要分为3块:

    第一块:第一个点的坐标+拖动的距离和第一点坐标的最大值进行比较

    第二块:第一个点的坐标+拖动的距离和第一点坐标的最小值进行比较

    第三块:是在第一,二块之间的

     1     @Override
     2     public boolean onTouchEvent(MotionEvent event)
     3     {
     4         if ((dataValues.length * xScale + 50 + ori_x) < maxX_X- ori_x)
     5         {
     6             return false;
     7         }
     8         switch (event.getAction())
     9         {
    10             case MotionEvent.ACTION_DOWN:
    11                 
    12                 startX = event.getX();
    13                 break;
    14             case MotionEvent.ACTION_MOVE:
    15                 float distance = event.getX() - startX;
    16 //                Log.v("tagtag", "startX="+startX+",distance="+distance);
    17                 startX = event.getX();
    18                 if(first_x+distance > ori_max_x)
    19                 {
    20                     Log.v("tagtag", "111");
    21                     first_x = ori_max_x;
    22                 }
    23                 else if(first_x+distance<ori_min_x)
    24                 {
    25                     Log.v("tagtag", "222");
    26                     first_x = ori_min_x;
    27                 }
    28                 else
    29                 {
    30                     Log.v("tagtag", "333");
    31                     first_x = (int)(first_x + distance);
    32                 }
    33                  invalidate();  
    34                 break;
    35         }
    36         return true;
    37     }

    9、最终效果图,如下

     总结:

    自定义控件的编写步骤可以分为以下几个步骤:

    (1)编写attr.xml文件

    (2)在layout布局文件中引用,同时引用命名空间

    (3)在自定义控件中进行读取(构造方法拿到attr.xml文件值)

    (4)覆写onMeasure()方法

    (5)覆写onLayout(),onDraw()方法

    具体用到哪几个方法,具体情况具体分析,关键点还是在算法上边。

    参考文章:

    http://blog.csdn.net/yifei1989/article/details/29891211

    转载请注明来源于

    作者: persist
    出处: http://www.cnblogs.com/persist-confident/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

  • 相关阅读:
    verilog学习(9)实战之存储器&奇偶校验
    求职经验之综合岗位三面
    求职经验之综合岗位二面
    求职经验之综合岗位
    verilog学习(8)实战之PPL与串行/解串器
    verilog学习(7)实战之扫描链
    verilog学习(6)实战4之触发器与锁存器
    verilog学习(5)实战3之计数器与bus
    verilog学习(4)实战1之基础练习
    求职经验之器件与芯片岗
  • 原文地址:https://www.cnblogs.com/persist-confident/p/4756791.html
Copyright © 2020-2023  润新知