Android简易折线图的实现方案
万事开头难,不说内容如何,关键在于我终于迈出这一步了!
对于折线图来说,github已经有太多优秀的开源框架,如MPAndroidCharts,使用起来很方便,简单的调用其API方法即可。然在懂得运用的情况下我们更要理解折线图表实现的原理。
最近写了一个简单的折线图,在此分享一下,接下来会逐渐完善与优化,主要就是模仿一些APP已经实现的简易功能。说了那么多的废话,开始行动吧!
说到折线图,我相信大家都不陌生,在很多地方都遇到过如Excel.还有股票走势等等,所以说大家也一定知道折现图长什么样子。那么接下来我们会才想,它是怎么实现的呢??
构思:
折现图的形式:
简而言之,一条X轴,一条Y轴,X轴与Y轴组成区域内的一些点,线,以及这些点、线或坐标轴的文字描述。
猜想:应该怎么实现呢?既然知道了折线图的表现形式,由两条坐标轴,若干点与线组成,那应该就会想到,怎么画坐标轴?怎么画点?怎么画线?怎么把它们给链接起来?
实现步骤
- 画坐标轴
- 画点
- 画线
Action:
假如现在有一个2维数组,
protected float[][] points = new float[][]{{1,10}, {2,47}, {3,11}, {4,38}, {5,9},{6,52}, {7,14}, {8,37}, {9,29}, {10,31}};
我们将通过拆线图的形式把每个点绘制出来。
执行自定义View的3个常用方法
OnMeassure OnLayout OnDraw ,在这主要主是OnDraw.
- 第一步,平移坐标原点:
//平移坐标原点
canvas.translate(50,height-50);
- 第二步,画X轴及X轴上显示的文本描述:
注意
具体怎么画有很多种方法,这里采用的是每次画一小段,一段连接着下一段,最终形成一条完整的线段,
也可以直接先画一条直线(宽度有限,也可以叫线段,怎么叫无所谓了,实现才是最关键的)。
当然在画线的时候会画一个点,及关于这个点的描述,具体也可偏移一些以方便显示文本。
private void drawLineXAxis(Canvas canvas) {
int startX = 0;
int startY = 0;
int spaceing = (width-100)/points.length;
//每次画一小段 也可以直接画一条线,然后再线上绘制具体的各个点,其原理一样,都是等分每次变化一个spaceing
for (int i = 0;(startX+spaceing*i)<width-50;i++){
//绘制X轴上的线段了,0-1 1-2 2-3.。。这个地方应该搞个中间变量来表示 要不重复画了0-1 0-2 0-3 代码问题
canvas.drawLine(startX,startY,startX+spaceing*i ,startY,linePaint);
//绘制X轴各坐标点 小圆
canvas.drawCircle(startX+spaceing*i, startY, 5, linePaint);
//绘制X轴各坐标点的描述,在这偷懒了直接就用0 1 2 3...表示了,实际运用可灵活变化 取真正有意义应该显示的内容
canvas.drawText(i+0+"",startX+spaceing*i,startY+30,textPaint);
}
}
- 第三步,画Y轴及Y轴上对应显示的文本描述:
注意
Y轴相对X轴来说相对稍微复杂一点,因为Y轴上的坐标点刻度不是像X轴一样有什么显示什么,而是显示的一个区间,
这个区间可以直接用数据源中的最大值最小值表示,也可以通过动态设定。在这里也偷懒了(人为的分成了6份,
因为可以直观的知道数据的大小,真实应用时应该根据前面所说,用数据源中最大值最小值表示,还是其他设定)。
这一点我们搞明白了,Y轴也就简单了。同理X轴,要多少个坐标点 就分成多少份,这里也是height/数据长度代表每一区间的高度
,绘制Y坐标文本时应该是动态按比例计算,此处直接写6不合理,
举个例子按照最大最小分6份:
最小值+(最大值-最小值)/6i //i表示第几段了 或者这么说 起始值 + 段值段数 来表示Y坐标轴点的描述
private void drawLineYAxis(Canvas canvas) {
int startX = 0;
int startY = 0;
int spaceing = (height-100)/points.length;
//每次画一小段
for (int i = 0;(startY+spaceing*i)<height-50;i++){
canvas.drawLine(startX,startY,startX ,startY-spaceing*i,linePaint);
canvas.drawCircle(startX, startY-spaceing*i, 5, linePaint);
//画Y轴关键点就在于Y轴上的坐标点怎么计算与显示
canvas.drawText(6*i+"",startX-30,startY-spaceing*i,textPaint);
}
}
- 第四步,画点与点与点连接成的线
注意 最关键一最后一步了。
根据数据源,计算出每个点应该在坐标轴上的哪个地方,然后绘制点再连接成线。
在这里每个点的横坐标简单,同理这也有2各思路,但实质是一样的。
横坐标是多少 就乘以sapceingWith(X轴分成了多少份),这样就能计算出横坐标点的位置
纵坐标的话大体相同,因为纵坐标值表示与X不一样,应做一些改进。
spaceingHeightUnit = height/(最大值-最小值)或者height/(终值-起始值)
,这样可以得到每单位的值占有多少像素
然后spaceHeightUnit*Y值(数据源中每条数据的值)
另外就是按比例了 和每单位值多少一样。
代码中直接写了60也是直接写了偷懒原因。可做改进
float pointX = 0;
float pointY = 0;
private void drawLinePoints(Canvas canvas) {
float pointXTemp = 0;
float pointYTemp = 0;
for(int i =0;i<points.length;i++){
float temp = points[i][0]%points.length;
if(temp==0){
pointX = 0+(points[i][0])*((width-100)/points.length);
}else{
pointX = 0+(points[i][0]%points.length)*((width-100)/points.length);
}
// pointY = pointY - points[i][1]*((height-100)/points.length);
pointY = 0-(points[i][1]/60)*((height-100));
// canvas.drawPoint(pointX,pointY,pointPaint);
//pointY = 0-points[i][1]*6;
canvas.drawCircle(pointX, pointY, 5, pointPaint);
canvas.drawText(i+1+"",pointX-10,pointY-10,pointPaint);
canvas.drawText("("+((int)points[i][0]+","+(int)points[i][1])+")",pointX-20,pointY-20,textPaint);
if( i!=0) {
canvas.drawLine(pointXTemp, pointYTemp, pointX, pointY, linePaint);
}
pointXTemp = pointX;
pointYTemp = pointY;
}
}
主要就是以上4步,画X轴、Y轴、点及点点之间连接形成的线。
这是最终形成的效果图。
当然这只是自己之前第一次写的,测试demo,但最终做成的是这样,
数据全部动态了,代码也优化成更灵活的了,估计后期还可能加上各种事件或者其他效果,慢慢改进吧.
一直阅读别人写的博客,阅读起来感觉很简单,但是自己执行时才发现,代码写起来容易,写这个难啊。
自己头一次 写东西,搞了好久才勉强弄出来了,虽说仍有各种问题,但我终于迈出了这一步!
package com.example.demochart.chart;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import com.example.demochart.util.Tool;
public class SelfLineChartView extends View {
private int width;// 宽
private int height;// 高
private Paint alphaLinePaint;// 半透明画笔
private Paint linePaint;// 线条画笔
private Paint textPaint;// 横坐标画笔
private Paint pointPaint;//点的坐标
private Paint textEmptyPaint;// 空提示
private Context context;
public SelfLineChartView(Context context) {
super(context);
}
public SelfLineChartView(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
init();
}
private void init() {
//画笔 X轴
linePaint = new Paint();
linePaint.setStyle(Paint.Style.FILL);
linePaint.setColor(context.getResources().getColor(
android.R.color.holo_red_dark));
linePaint.setAntiAlias(true);
linePaint.setStrokeWidth((float) 2.0);
//文字画笔
textPaint = new Paint();
textPaint.setTextAlign(Paint.Align.CENTER);
textPaint.setStyle(Paint.Style.FILL);
textPaint.setColor(context.getResources().getColor(
android.R.color.holo_red_dark));
textPaint.setAntiAlias(true);
textPaint.setTextSize(Tool.dip2px(context, 10));
//文字画笔
pointPaint = new Paint();
pointPaint.setTextAlign(Paint.Align.CENTER);
pointPaint.setStyle(Paint.Style.FILL);
pointPaint.setColor(context.getResources().getColor(
android.R.color.holo_blue_light));
pointPaint.setAntiAlias(true);
pointPaint.setTextSize(Tool.dip2px(context, 20));
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
width = MeasureSpec.getSize(widthMeasureSpec);
height = MeasureSpec.getSize(heightMeasureSpec);
int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
int heightSpec = MeasureSpec.makeMeasureSpec(height,MeasureSpec.EXACTLY);
setMeasuredDimension(widthSpec, heightSpec);
}
protected float[][] points = new float[][]{{1,10}, {2,47}, {3,11}, {4,38}, {5,9},{6,52}, {7,14}, {8,37}, {9,29}, {10,31}};
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//平移坐标原点
canvas.translate(50,height-50);
//有多少条数据,把X轴分成多少份,
drawLineXAxis(canvas);
drawLineYAxis(canvas);
drawLinePoints(canvas);
}
float pointX = 0;
float pointY = 0;
private void drawLinePoints(Canvas canvas) {
float pointXTemp = 0;
float pointYTemp = 0;
for(int i =0;i<points.length;i++){
float temp = points[i][0]%points.length;
if(temp==0){
pointX = 0+(points[i][0])*((width-100)/points.length);
}else{
pointX = 0+(points[i][0]%points.length)*((width-100)/points.length);
}
// pointY = pointY - points[i][1]*((height-100)/points.length);
pointY = 0-(points[i][1]/60)*((height-100));
// canvas.drawPoint(pointX,pointY,pointPaint);
//pointY = 0-points[i][1]*6;
canvas.drawCircle(pointX, pointY, 5, pointPaint);
canvas.drawText(i+1+"",pointX-10,pointY-10,pointPaint);
canvas.drawText("("+((int)points[i][0]+","+(int)points[i][1])+")",pointX-20,pointY-20,textPaint);
if( i!=0) {
canvas.drawLine(pointXTemp, pointYTemp, pointX, pointY, linePaint);
}
pointXTemp = pointX;
pointYTemp = pointY;
}
}
private void drawLineYAxis(Canvas canvas) {
int startX = 0;
int startY = 0;
int spaceing = (height-100)/points.length;
//每次画一小段
for (int i = 0;(startY+spaceing*i)<height-50;i++){
canvas.drawLine(startX,startY,startX ,startY-spaceing*i,linePaint);
canvas.drawCircle(startX, startY-spaceing*i, 5, linePaint);
canvas.drawText(6*i+"",startX-30,startY-spaceing*i,textPaint);
}
}
private void drawLineXAxis(Canvas canvas) {
int startX = 0;
int startY = 0;
int spaceing = (width-100)/points.length;
//每次画一小段
for (int i = 0;(startX+spaceing*i)<width-50;i++){
canvas.drawLine(startX,startY,startX+spaceing*i ,startY,linePaint);
canvas.drawCircle(startX+spaceing*i, startY, 5, linePaint);
canvas.drawText(i+0+"",startX+spaceing*i,startY+30,textPaint);
}
}
}
<com.example.demochart.chart.SelfLineChartView
android:layout_width="match_parent"
android:layout_height="300dp"
android:id="@+id/selfLineChartView"/>
接下来 不知道怎么写 了 中断了,也算第一篇文章不管好坏完成了吧。