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_xylinewidth:
					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

时间: 2024-10-12 22:44:15

Android自定义控件-折线图的相关文章

android绘画折线图一

最近需要实现用android来画折线图,所以百度了一下,发现确实很多,也很乱,现在整理两种方法(第二种方法在[android绘画折线图二]中实现),仅供大家参考,一起学习研究. 第一种使用ChartFactory.getLineChartIntent()方法获取,这种方法首先需要一个achartengine-1.0.0.jar(项目中有),然后建立一个简单的android项目 项目中所需要导入的包有: import java.util.ArrayList; import java.util.Ha

Android画折线图、柱状图、饼图(使用achartengine.jar)

自从用了画折线的jar包之后,就不想再用canvas画布去画了,编程就是要站在巨人的肩膀上. 所需要的jar包achartenginejar 折线代码布局文件就不上传了很简单 另一种线的渲染器 扇形图代码 柱状图代码 属性总结部分代码 新测试代码下载地址 所需要的jar包:achartengine.jar 下载地址:http://download.csdn.net/detail/zhengyikuangge/9460642 折线代码(布局文件就不上传了,很简单): package com.exa

android 自定义折线图

看图: 比较简陋,主要是通过canvas画上去的: package com.example.democurvegraph.view; import java.util.ArrayList; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.util.Attribute

android自定义折线图

BrokenLine控件: import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.Date; import java.util.GregorianCalendar; import java.util.List; import android.content.Context; im

Android自定义控件 -Canvas绘制折线图(实现动态报表效果)

有时候我们在项目中会遇到使用折线图等图形,Android的开源项目中为我们提供了很多插件,但是很多时候我们需要根据具体项目自定义这些图表,这一篇文章我们一起来看看如何在Android中使用Canvas绘制折线图.先看看绘制的效果: 代码: public class MyView extends View { //坐标轴原点的位置 private int xPoint=60; private int yPoint=260; //刻度长度 private int xScale=8;  //8个单位构

Android之自定义控件实现天气温度折线图和饼状图

以前写了个天气的APP,最近把他更新了一个版本,就抽取其中的天气温度折现图这个功能写了这篇博客,来与大家分享,希望对你有所帮助. 效果如图: 代码: MainActivity.Java /**** * 饼状图和天气折线图 */ public class MainActivity extends AppCompatActivity { private WeatnerChartView chart1; private WeatnerChartView chart2; private PinChart

Android开发学习之路-自定义控件(天气趋势折线图)

之前写了个天气APP,带4天预报和5天历史信息.所以想着要不要加一个折线图来显示一下天气变化趋势,难得有空,就写了一下,这里做些记录,脑袋不好使容易忘事. 先放一下效果: 控件内容比较简单,就是一个普通的折线图,上下分别带有数字,点击的时候显示当天温度的差值. 创建一个类继承自View,并添加两个构造方法: public class TrendGraph extends View { public TrendGraph(Context context) { // 在java代码中创建调用 sup

Android自定义控件系列四:绘制实用型的柱形图和折线图

概述: 前几天突然需要做两种图表--柱形图.折线图,于是第一反应是先看看网上有没有现成的,结果有是有,但都不是想要的,而且大多数不是用纯android代码完成,不过HTML5似乎完成这类工作要容易得多,单是非我们所擅长. **知识点:**android自定义view.图形图像.Fragment.MVC模式. Demo 界面是模仿红圈营销搭建的 折线图:  代码,注释很详细,直接看代码就行了: 001.public class LineChartView extends View { 002. 0

Android - 折线图

使用Android的canvas,画折线图:代码为: package spt.view; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.DashPathEffect; import android.graphics.Paint;