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;

import android.graphics.Path;

import android.graphics.PathEffect;

import android.util.Log;

import android.view.View;



/**

 * 为了扩展不同分辨率手机的兼容性,百分比和一些重要变量设置为final,其他可变变量设置为成员变量.

 * 

 * @author Administrator

 * 

 */

@SuppressLint("DrawAllocation")

public class BrokenLineView extends View {

	// 计算相对比例时,均使用边距margin为依照(值与比例结果成反比).

	// 画轴线时,箭头所占比例.

	private static final int ARROW_PECENT = 10;



	// x,y轴坐标点文字的比例.

	private static final int X_TEXT_PECENT = 10;

	private static final int Y_TEXT_PECENT = 10;



	// 标题文字的比例.

	private static final int TITLE_TEXT_PECENT = 5;



	// 默认坐标轴值.

	private static final String[] X_LABLE = { "a", "b", "c", "d", "e", "f", "g" };

	private static final String[] Y_LABLE = { "0", "50", "100", "150", "200",

			"250", "300" };



	// 数据点圆的半径.

	private static final int dataRadius = 10;



	// x,y轴坐标点数字的位置偏离轴线的距离.

	private int xTextDistanceAxis = 20;

	private int yTextDistanceAxis = 30;



	// 数据点数值文字相对于数据点的高度值.

	private int dataTextAboveCircle = 25;



	// 边距(也不能为final,因为可能用户可能根据不同条件设置不同间距.

	private int margin = 100;



	// X,Y轴的单位长度

	private int xScale = 20;

	private int yScale = 20;



	// 标题的高度.

	private String title;

	// 标题距离最顶行线的y距离.

	private int titleHeight = 20;

	// 原点坐标

	private int x0Point;

	private int y0Point;



	// X,Y轴上面的显示文字

	private String[] xLabel = null;

	private String[] yLabel = null;

	// 曲线数据

	private int[] data = null;



	public BrokenLineView(Context context, String title, String[] xLabel,

			String[] yLabel, int[] data) {

		super(context);

		this.title = title;

		// 若传递空值,则使用默认的值.

		this.xLabel = (xLabel == null ? X_LABLE : xLabel);

		this.yLabel = (yLabel == null ? Y_LABLE : yLabel);

		if (data == null)

			throw new RuntimeException("data cannot null:");

		this.data = data;

	}



	public BrokenLineView(Context context) {

		this(context, null, X_LABLE, Y_LABLE, null);

	}



	// 设置坐标原点位置和轴线上的单位长度.

	public void init() {

		x0Point = margin; // x0.

		y0Point = getHeight() - margin; // y0.

		xScale = (getWidth() - 2 * margin) / (this.xLabel.length - 1);

		yScale = (getHeight() - 2 * margin) / (this.yLabel.length - 1);

	}



	public int getMargin() {

		return margin;

	}



	public void setMargin(int margin) {

		if (margin < 0)

			throw new RuntimeException("间距不能为负数:" + margin);

		this.margin = margin;

	}



	@Override

	protected void onDraw(Canvas canvas) {

		canvas.drawColor(Color.BLACK); // 背景色.

		Paint p = new Paint();

		p.setStyle(Paint.Style.STROKE); // 设置轴线的的外框的样式“空心”(STROKE).

		p.setAntiAlias(true); // 抗锯齿

		p.setColor(Color.WHITE);

		p.setStrokeWidth(2); // 设置轴线的的外框的宽度.

		init();

		drawYAxis(canvas, p);

		drawXAxis(canvas, p);

		drawHorizontalLine(canvas);

		drawData(canvas);

	}



	// x向线

	private void drawHorizontalLine(Canvas canvas) {

		Paint paint = new Paint();

		paint.setStyle(Paint.Style.STROKE);

		paint.setColor(Color.GRAY);

		Path path = new Path();

		// 先画长度为1的实线,然后长度为10的空白,再画长度为1实线,再画长度为10的空白;最后一个是是偏移量,可不理会.

		PathEffect effects = new DashPathEffect(new float[] { 1, 10, 1, 10 }, 1);

		paint.setPathEffect(effects);

		for (int i = 1; (y0Point - i * yScale) >= margin; i++) {

			int startX = x0Point;

			int startY = y0Point - i * yScale;

			int stopX = x0Point + (xLabel.length - 1) * xScale;

			path.moveTo(startX, startY);

			path.lineTo(stopX, startY);

			paint.setColor(Color.DKGRAY);

			canvas.drawPath(path, paint);

		}

	}



	/**

	 * 画y轴线

	 * 

	 * @param canvas

	 * @param p

	 */

	private void drawYAxis(Canvas canvas, Paint p) {

		// y轴.

		canvas.drawLine(x0Point, y0Point, margin, margin, p);

		// y轴箭头的左部分.

		canvas.drawLine(x0Point, margin, x0Point - x0Point / ARROW_PECENT,

				margin + margin / ARROW_PECENT, p);

		// y轴箭头的右部分.

		canvas.drawLine(x0Point, margin, x0Point + x0Point / ARROW_PECENT,

				margin + margin / ARROW_PECENT, p);

	}



	/**

	 * 画x轴线.

	 * 

	 * @param canvas

	 * @param p

	 */

	private void drawXAxis(Canvas canvas, Paint p) {

		// x轴.

		canvas.drawLine(x0Point, y0Point, getWidth() - margin, y0Point, p);

		// x轴箭头的上部分.

		canvas.drawLine(getWidth() - margin, y0Point, getWidth() - margin

				- margin / ARROW_PECENT, y0Point - margin / ARROW_PECENT, p);

		// x轴箭头的下部分.

		canvas.drawLine(getWidth() - margin, y0Point, getWidth() - margin

				- margin / ARROW_PECENT, y0Point + margin / ARROW_PECENT, p);

	}



	// 画数据

	private void drawData(Canvas canvas) {

		Paint p = new Paint();

		p.setAntiAlias(true); // 抗锯齿.

		p.setColor(Color.RED);

		p.setTextSize(margin / X_TEXT_PECENT);

		for (int x = 0; x < data.length; x++) {

			int startX = x0Point + x * xScale;

			// 轴坐标点文字的显示.

			canvas.drawText(xLabel[x], startX, y0Point + xTextDistanceAxis, p);

			// 数据点的圆.

			canvas.drawCircle(startX, calY(data[x]), dataRadius, p);

			// 在数据点上标数据值.

			canvas.drawText(data[x] + "", startX, calY(data[x]) + dataRadius

					- dataTextAboveCircle, p);

			// 在数据点圆间画变化线.

			// 画线时,最后数据点不执行操作.

			if (x != data.length - 1)

				canvas.drawLine(startX, calY(data[x]), startX + xScale,

						calY(data[x + 1]), p);

		} // for x.



		// 画y轴坐标点文字.

		Paint py = new Paint();

		py.setAntiAlias(true); // 抗锯齿.

		py.setColor(Color.RED);

		py.setTextSize(margin / Y_TEXT_PECENT);

		for (int y = 0; y < yLabel.length; y++) {

			int startY = y0Point - y * yScale;

			// y轴坐标点文字的显示.

			canvas.drawText(yLabel[y], x0Point - yTextDistanceAxis, startY, py);

		} // for y.



		// 画标题.

		Paint pTitle = new Paint();

		pTitle.setAntiAlias(true); // 抗锯齿.

		pTitle.setColor(Color.RED);

		pTitle.setTextSize(margin / TITLE_TEXT_PECENT);

		// 通过Paint.measureText计算标题长度的像素,进而将标题水平居中.

		canvas.drawText(title, (getWidth() - pTitle.measureText(title)) / 2,

				margin - titleHeight, pTitle);

	}



	/**

	 * 计算数据值在坐标系中y的位置.

	 * 

	 * @param y

	 * @return

	 */

	private int calY(int y) {

		int y0 = 0;

		int y1 = 0;

		try {

			y0 = Integer.parseInt(yLabel[0]);

			y1 = Integer.parseInt(yLabel[1]);

			return y0Point - ((y - y0) * yScale / (y1 - y0));

		} catch (NumberFormatException e) {

			Log.d("sysout", "y轴label必须是数字:" + e.getMessage());

			return -1;

		}

	}



}

 然后在Activity中调用即可:

@Override

	protected void onCreate(Bundle savedInstanceState) {

		super.onCreate(savedInstanceState);

		

		//test:

		int[] data = { 0, 50, 51, 78, 200, 121, 31 };

		final String title = "最近一周AQI变化图";

		setContentView(new BrokenLineView(this, title, null, null, data));

		initView();

	}

 

你可能感兴趣的:(android)