Android簡(jiǎn)易折線(xiàn)圖的實(shí)現(xiàn)方案
萬(wàn)事開(kāi)頭難违柏,不說(shuō)內(nèi)容如何,關(guān)鍵在于我終于邁出這一步了香椎!
對(duì)于折線(xiàn)圖來(lái)說(shuō)漱竖,github已經(jīng)有太多優(yōu)秀的開(kāi)源框架,如MPAndroidCharts畜伐,使用起來(lái)很方便馍惹,簡(jiǎn)單的調(diào)用其API方法即可。然在懂得運(yùn)用的情況下我們更要理解折線(xiàn)圖表實(shí)現(xiàn)的原理玛界。
最近寫(xiě)了一個(gè)簡(jiǎn)單的折線(xiàn)圖万矾,在此分享一下,接下來(lái)會(huì)逐漸完善與優(yōu)化慎框,主要就是模仿一些APP已經(jīng)實(shí)現(xiàn)的簡(jiǎn)易功能良狈。說(shuō)了那么多的廢話(huà),開(kāi)始行動(dòng)吧笨枯!
說(shuō)到折線(xiàn)圖薪丁,我相信大家都不陌生,在很多地方都遇到過(guò)如Excel.還有股票走勢(shì)等等馅精,所以說(shuō)大家也一定知道折現(xiàn)圖長(zhǎng)什么樣子严嗜。那么接下來(lái)我們會(huì)才想,它是怎么實(shí)現(xiàn)的呢洲敢?漫玄?
構(gòu)思:
折現(xiàn)圖的形式:
簡(jiǎn)而言之,一條X軸压彭,一條Y軸睦优,X軸與Y軸組成區(qū)域內(nèi)的一些點(diǎn),線(xiàn)哮塞,以及這些點(diǎn)、線(xiàn)或坐標(biāo)軸的文字描述凳谦。
猜想:應(yīng)該怎么實(shí)現(xiàn)呢忆畅?既然知道了折線(xiàn)圖的表現(xiàn)形式,由兩條坐標(biāo)軸,若干點(diǎn)與線(xiàn)組成家凯,那應(yīng)該就會(huì)想到缓醋,怎么畫(huà)坐標(biāo)軸?怎么畫(huà)點(diǎn)绊诲?怎么畫(huà)線(xiàn)送粱?怎么把它們給鏈接起來(lái)?
實(shí)現(xiàn)步驟
- 畫(huà)坐標(biāo)軸
- 畫(huà)點(diǎn)
- 畫(huà)線(xiàn)
Action:
假如現(xiàn)在有一個(gè)2維數(shù)組掂之,
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}};
我們將通過(guò)拆線(xiàn)圖的形式把每個(gè)點(diǎn)繪制出來(lái)抗俄。
執(zhí)行自定義View的3個(gè)常用方法
OnMeassure OnLayout OnDraw ,在這主要主是OnDraw.
- 第一步世舰,平移坐標(biāo)原點(diǎn):
//平移坐標(biāo)原點(diǎn)
canvas.translate(50,height-50);
- 第二步动雹,畫(huà)X軸及X軸上顯示的文本描述:
注意
具體怎么畫(huà)有很多種方法,這里采用的是每次畫(huà)一小段跟压,一段連接著下一段胰蝠,最終形成一條完整的線(xiàn)段,
也可以直接先畫(huà)一條直線(xiàn)(寬度有限震蒋,也可以叫線(xiàn)段茸塞,怎么叫無(wú)所謂了,實(shí)現(xiàn)才是最關(guān)鍵的)查剖。
當(dāng)然在畫(huà)線(xiàn)的時(shí)候會(huì)畫(huà)一個(gè)點(diǎn)钾虐,及關(guān)于這個(gè)點(diǎn)的描述,具體也可偏移一些以方便顯示文本梗搅。
private void drawLineXAxis(Canvas canvas) {
int startX = 0;
int startY = 0;
int spaceing = (width-100)/points.length;
//每次畫(huà)一小段 也可以直接畫(huà)一條線(xiàn)禾唁,然后再線(xiàn)上繪制具體的各個(gè)點(diǎn),其原理一樣无切,都是等分每次變化一個(gè)spaceing
for (int i = 0;(startX+spaceing*i)<width-50;i++){
//繪制X軸上的線(xiàn)段了荡短,0-1 1-2 2-3.。哆键。這個(gè)地方應(yīng)該搞個(gè)中間變量來(lái)表示 要不重復(fù)畫(huà)了0-1 0-2 0-3 代碼問(wèn)題
canvas.drawLine(startX,startY,startX+spaceing*i ,startY,linePaint);
//繪制X軸各坐標(biāo)點(diǎn) 小圓
canvas.drawCircle(startX+spaceing*i, startY, 5, linePaint);
//繪制X軸各坐標(biāo)點(diǎn)的描述掘托,在這偷懶了直接就用0 1 2 3...表示了,實(shí)際運(yùn)用可靈活變化 取真正有意義應(yīng)該顯示的內(nèi)容
canvas.drawText(i+0+"",startX+spaceing*i,startY+30,textPaint);
}
}
- 第三步籍嘹,畫(huà)Y軸及Y軸上對(duì)應(yīng)顯示的文本描述:
注意
Y軸相對(duì)X軸來(lái)說(shuō)相對(duì)稍微復(fù)雜一點(diǎn)闪盔,因?yàn)閅軸上的坐標(biāo)點(diǎn)刻度不是像X軸一樣有什么顯示什么,而是顯示的一個(gè)區(qū)間辱士,
這個(gè)區(qū)間可以直接用數(shù)據(jù)源中的最大值最小值表示泪掀,也可以通過(guò)動(dòng)態(tài)設(shè)定。在這里也偷懶了(人為的分成了6份颂碘,
因?yàn)榭梢灾庇^(guān)的知道數(shù)據(jù)的大小异赫,真實(shí)應(yīng)用時(shí)應(yīng)該根據(jù)前面所說(shuō),用數(shù)據(jù)源中最大值最小值表示,還是其他設(shè)定)塔拳。
這一點(diǎn)我們搞明白了鼠证,Y軸也就簡(jiǎn)單了。同理X軸靠抑,要多少個(gè)坐標(biāo)點(diǎn) 就分成多少份量九,這里也是height/數(shù)據(jù)長(zhǎng)度代表每一區(qū)間的高度
,繪制Y坐標(biāo)文本時(shí)應(yīng)該是動(dòng)態(tài)按比例計(jì)算颂碧,此處直接寫(xiě)6不合理荠列,
舉個(gè)例子按照最大最小分6份:
最小值+(最大值-最小值)/6i //i表示第幾段了 或者這么說(shuō) 起始值 + 段值段數(shù) 來(lái)表示Y坐標(biāo)軸點(diǎn)的描述
private void drawLineYAxis(Canvas canvas) {
int startX = 0;
int startY = 0;
int spaceing = (height-100)/points.length;
//每次畫(huà)一小段
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);
//畫(huà)Y軸關(guān)鍵點(diǎn)就在于Y軸上的坐標(biāo)點(diǎn)怎么計(jì)算與顯示
canvas.drawText(6*i+"",startX-30,startY-spaceing*i,textPaint);
}
}
- 第四步,畫(huà)點(diǎn)與點(diǎn)與點(diǎn)連接成的線(xiàn)
注意 最關(guān)鍵一最后一步了稚伍。
根據(jù)數(shù)據(jù)源弯予,計(jì)算出每個(gè)點(diǎn)應(yīng)該在坐標(biāo)軸上的哪個(gè)地方,然后繪制點(diǎn)再連接成線(xiàn)个曙。
在這里每個(gè)點(diǎn)的橫坐標(biāo)簡(jiǎn)單锈嫩,同理這也有2各思路,但實(shí)質(zhì)是一樣的垦搬。
橫坐標(biāo)是多少 就乘以sapceingWith(X軸分成了多少份)呼寸,這樣就能計(jì)算出橫坐標(biāo)點(diǎn)的位置
縱坐標(biāo)的話(huà)大體相同,因?yàn)榭v坐標(biāo)值表示與X不一樣猴贰,應(yīng)做一些改進(jìn)对雪。
spaceingHeightUnit = height/(最大值-最小值)或者h(yuǎn)eight/(終值-起始值)
,這樣可以得到每單位的值占有多少像素
然后spaceHeightUnit*Y值(數(shù)據(jù)源中每條數(shù)據(jù)的值)
另外就是按比例了 和每單位值多少一樣米绕。
代碼中直接寫(xiě)了60也是直接寫(xiě)了偷懶原因瑟捣。可做改進(jìn)
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步栅干,畫(huà)X軸迈套、Y軸、點(diǎn)及點(diǎn)點(diǎn)之間連接形成的線(xiàn)碱鳞。
這是最終形成的效果圖桑李。
當(dāng)然這只是自己之前第一次寫(xiě)的,測(cè)試demo,但最終做成的是這樣窿给,
數(shù)據(jù)全部動(dòng)態(tài)了贵白,代碼也優(yōu)化成更靈活的了,估計(jì)后期還可能加上各種事件或者其他效果崩泡,慢慢改進(jìn)吧.
一直閱讀別人寫(xiě)的博客禁荒,閱讀起來(lái)感覺(jué)很簡(jiǎn)單,但是自己執(zhí)行時(shí)才發(fā)現(xiàn)角撞,代碼寫(xiě)起來(lái)容易呛伴,寫(xiě)這個(gè)難啊寥掐。
自己頭一次 寫(xiě)東西,搞了好久才勉強(qiáng)弄出來(lái)了磷蜀,雖說(shuō)仍有各種問(wèn)題,但我終于邁出了這一步百炬!
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;// 半透明畫(huà)筆
private Paint linePaint;// 線(xiàn)條畫(huà)筆
private Paint textPaint;// 橫坐標(biāo)畫(huà)筆
private Paint pointPaint;//點(diǎn)的坐標(biāo)
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() {
//畫(huà)筆 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);
//文字畫(huà)筆
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));
//文字畫(huà)筆
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);
//平移坐標(biāo)原點(diǎn)
canvas.translate(50,height-50);
//有多少條數(shù)據(jù)褐隆,把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;
//每次畫(huà)一小段
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;
//每次畫(huà)一小段
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"/>
接下來(lái) 不知道怎么寫(xiě) 了 中斷了剖踊,也算第一篇文章不管好壞完成了吧庶弃。