Android版的股票行情K線圖開(kāi)發(fā)
現(xiàn)在在手上的是一個(gè)證券資訊類型的app,其中有涉及到股票行情界面,行情中有K線圖等,看到網(wǎng)上很多人在求這方面的資料,所以我特地寫(xiě)了一個(gè)demo在此處給大家分享一下。
下面是做出來(lái)的效果圖:

背景圖是利用canvas先畫(huà)出一個(gè)矩形,然后再畫(huà)幾根虛線,均線圖是通過(guò)path來(lái)繪制的,總之圖的繪制是很簡(jiǎn)單的,我就不在這里作介紹了,大家可以去github下載源碼看看。涉及到均線、最高價(jià)、最低價(jià)、收盤(pán)價(jià)、開(kāi)盤(pán)價(jià)的概念大家可以百度一下。
我再這里要介紹的是計(jì)算問(wèn)題:
大家可以看到分時(shí)圖、日K、月K的左邊的成交價(jià)格都是不一樣的,而我們的k線都是通過(guò)這個(gè)價(jià)格來(lái)繪制的,也就是說(shuō)價(jià)格是時(shí)刻變動(dòng),那么我們的k線繪制也是變動(dòng)的。假設(shè)我們要計(jì)算分時(shí)圖中價(jià)格為25.69的那一分鐘應(yīng)該如何畫(huà),畫(huà)在屏幕中的哪一個(gè)位置,那么這個(gè)應(yīng)該怎么畫(huà)呢,價(jià)格是變動(dòng)的,畫(huà)的位置也是變動(dòng)的,但是有一點(diǎn)我們屏幕的大小是不變的。所以我們可以通過(guò)背景圖的高度來(lái)計(jì)算某個(gè)價(jià)格的線圖應(yīng)該從哪個(gè)地方開(kāi)始畫(huà)。我們可以計(jì)算出一個(gè)像素點(diǎn)對(duì)應(yīng)多少個(gè)價(jià)格,分析圖如下:

價(jià)格和像素形成個(gè)一個(gè)比例計(jì)算是:double heightScale = (endY - startY)/(highPrice - lowPrice);
所以價(jià)格25.69應(yīng)該是畫(huà)在mStartY = (float) (startY+ (highPrice - 25.69) * heightScale);
這個(gè)明白了之后其他的原理都是一樣的,我就不介紹了,下面是部分代碼:
@Override
protected void drawKChatBackGround() {
Rect dirty = new Rect(left, kChartTop, right, KChartbottom);
// 畫(huà)背景圖的矩形
mCanvas.drawRect(dirty, LineGrayPaint);
PathEffect effects = new DashPathEffect(new float[] { 5, 5, 5, 5 }, 1);
LineGrayPaint.setPathEffect(effects);
Path path = new Path();
int y = kChartTop + 15;
// 畫(huà)上面的虛線
path.moveTo(left, y );
path.lineTo(right, y );
String text = getPriceText(highPrice);
int textHeight = (int) (textGrayPaint.descent() - textGrayPaint.ascent());
mCanvas.drawText(text,left - textGrayPaint.measureText(text) - 5,y + textHeight/2 ,textGrayPaint);
double max = highPrice - lowPrice;
if (max > 10){
// 分成四等分
// 畫(huà)中間的三根虛線
int n = 4;
double sper = (highPrice - lowPrice) / 4;// 每一等分代表的價(jià)格
for(int i=1;i<n;i++){
y = i*((KChartbottom - kChartTop)/n) + kChartTop;
path.moveTo(left, y);
path.lineTo(right,y);
text = getPriceText(highPrice - i*sper);
mCanvas.drawText(text,left - textGrayPaint.measureText(text) - 5,y + textHeight/2,textGrayPaint);
}
}else{
// 分成兩等分
// 畫(huà)中間的虛線
y = (KChartbottom - kChartTop)/2 + kChartTop;
path.moveTo(left, y);
path.lineTo(right, y);
text = getPriceText(highPrice - (highPrice - lowPrice) / 2);
mCanvas.drawText(text,left - textGrayPaint.measureText(text) - 5,y + textHeight/2,textGrayPaint);
}
// 畫(huà)下面的虛線
y = KChartbottom - 15;
path.moveTo(left, y);
path.lineTo(right, y);
text = getPriceText(lowPrice);
mCanvas.drawText(text,left - textGrayPaint.measureText(text) - 5,y + textHeight/2,textGrayPaint);
// // 畫(huà)等分的虛線和下面的日期
for (int i = num - 1; i > 0; i--) {
int x = left + perWidth * i;
path.moveTo(x, kChartTop);
path.lineTo(x, KChartbottom);
perXPoint[i - 1] = x;
}
mCanvas.drawPath(path, LineGrayPaint);
}
@Override
protected void drawMAChart() {
// 畫(huà)均線
Path path5 = new Path();
Path path10 = new Path();
Path path20 = new Path();
double heightScale = (KChartbottom - kChartTop)/(highPrice - lowPrice);
int maStart = left;
float maStartY;
path5.moveTo(maStart, (float) (kChartTop + (highPrice - infos.get(0).getMaValue5()) * heightScale));
path10.moveTo(maStart, (float) (kChartTop + (highPrice - infos.get(0).getMaValue10()) * heightScale));
path20.moveTo(maStart, (float) (kChartTop + (highPrice - infos.get(0).getMaValue20()) * heightScale));
for(SingleStockInfo info:infos){
maStart += per * perHalf;// 每一天實(shí)際所占的數(shù)據(jù)是4/6,左右邊距各1/6
maStartY = (float) (kChartTop + (highPrice - info.getMaValue5()) * heightScale);
path5.lineTo(maStart, maStartY);
maStartY = (float) (kChartTop + (highPrice - info.getMaValue10()) * heightScale);
path10.lineTo(maStart, maStartY);
maStartY = (float) (kChartTop + (highPrice - info.getMaValue20()) * heightScale);
path20.lineTo(maStart, maStartY);
maStart += per * perHalf;
}
Paint paint = new Paint();
paint.setColor(Color.BLUE);
paint.setAntiAlias(true);
paint.setStrokeWidth(2);
paint.setStyle(Style.STROKE);
mCanvas.drawPath(path5, paint);
paint.setColor(Color.MAGENTA);
mCanvas.drawPath(path10, paint);
paint.setColor(Color.GREEN);
mCanvas.drawPath(path20, paint);
}
/**
* 下面的柱形圖
*/
@Override
protected void drawPillarsChart(int flag) {
LineGrayPaint.setPathEffect(null);
Rect dirty = new Rect(left, pillarsChartTop, right, pillarsChartbottom);
// 畫(huà)背景圖的矩形
mCanvas.drawRect(dirty, LineGrayPaint);
int y = pillarsChartTop + (pillarsChartbottom - pillarsChartTop)/2;
mCanvas.drawLine(left,y,right, y, LineGrayPaint);
// 中間的值
String totalCount = getPriceText(maxCount/2/10000);
float maginLeft = left - textGrayPaint.measureText(totalCount)- 5;
mCanvas.drawText(totalCount, maginLeft, y,textGrayPaint);
// 上面的值
totalCount = getPriceText(maxCount/10000);
maginLeft = left - textGrayPaint.measureText(totalCount)- 5;
mCanvas.drawText(totalCount, maginLeft, pillarsChartTop,textGrayPaint);
// 下面的值
totalCount = "萬(wàn)手";
maginLeft = left - textGrayPaint.measureText(totalCount) - 5;
mCanvas.drawText(totalCount, maginLeft, pillarsChartbottom,textGrayPaint);
int pStart = left;
float pStartY;
double heightScale = (pillarsChartbottom - pillarsChartTop)/maxCount;
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.FILL);
if (flag == StockService.FLAG){
for(MinuteInfo info:minuteInfos){
pStart += per * per16;// 每一天實(shí)際所占的數(shù)據(jù)是4/6,加上1/6
pStartY = (float) (pillarsChartTop + (maxCount - info.getVolume()) * heightScale);
dirty = new Rect(pStart, (int) pStartY, (int) (pStart + per * per46), pillarsChartbottom-2);
paint.setColor(info.getColor());
// 畫(huà)背景圖的矩形
mCanvas.drawRect(dirty, paint);
pStart += per * per56;// 右邊的間距 5/6
}
}else{
for(SingleStockInfo info:infos){
pStart += per * per16;// 每一天實(shí)際所占的數(shù)據(jù)是4/6,加上1/6
pStartY = (float) (pillarsChartTop + (maxCount - info.getTotalCount()) * heightScale);
dirty = new Rect(pStart, (int) pStartY, (int) (pStart + per * per46), pillarsChartbottom-2);
paint.setColor(info.getColor());
// 畫(huà)背景圖的矩形
mCanvas.drawRect(dirty, paint);
pStart += per * per56;// 右邊的間距 5/6
}
}
}
/**
* 分時(shí)圖
*/
@Override
public void drawHoursChart(){
double heightScale = (KChartbottom - kChartTop)/(highPrice - lowPrice);
int cLeft = left;
int cTop = 0;
Path path = new Path();
path.moveTo(cLeft, KChartbottom-2);
int position = 0;
int perPointX = perXPoint[position];// 記錄第一條垂直虛線的x坐標(biāo)
for(MinuteInfo info:minuteInfos){
cLeft += per * per16;
cTop = (int) (kChartTop + (highPrice - info.getNow()) * heightScale);
path.lineTo(cLeft + per * per26, cTop);
if (cLeft >= perPointX){
// 恰好畫(huà)到第一條垂直虛線的地方,需要畫(huà)下面的時(shí)間
String text = KChartUtil.getMinute(info.getMinute());
float textWidth = textGrayPaint.measureText(text);
int textHeight = (int) (textGrayPaint.descent()- textGrayPaint.ascent());
mCanvas.drawText(text, perPointX - textWidth/2, KChartbottom + textHeight, textGrayPaint);
if (!(position == perXPoint.length-1)){
Log.e(TAG, perPointX+"----------"+info.getMinute()+"---"+text);
perPointX = perXPoint[++position];
}
}
cLeft += per * per56;// 右邊的間距 5/6
}
path.lineTo(cLeft, KChartbottom-2);
Paint LinePaint = new Paint();
LinePaint.setColor(Color.BLUE);
LinePaint.setAntiAlias(true);
LinePaint.setStrokeWidth(1);
LinePaint.setStyle(Style.STROKE);
// LinePaint.setStyle(Style.STROKE);
mCanvas.drawPath(path, LinePaint);
LinePaint.setAlpha(50);
LinePaint.setStyle(Style.FILL);
mCanvas.drawPath(path, LinePaint);
}
新年伊始,中國(guó)股市走出世界罕見(jiàn),前無(wú)古人后無(wú)來(lái)者的極端行情,股市有風(fēng)險(xiǎn),投資需謹(jǐn)慎。
這句話是題外話了,重點(diǎn)還是希望對(duì)大家學(xué)習(xí)Android程序設(shè)計(jì)有所幫助。
- Android獲取應(yīng)用程序名稱(ApplicationName)示例
- Android 避免APP啟動(dòng)閃黑屏的解決辦法(Theme和Style)
- ANDROID 完美退出APP的實(shí)例代碼
- 怎么發(fā)布打包并發(fā)布自己的Android應(yīng)用(APP)
- 通過(guò)Html網(wǎng)頁(yè)調(diào)用本地安卓(android)app程序代碼
- android實(shí)現(xiàn)通知欄下載更新app示例
- 一看就懂的Android APP開(kāi)發(fā)入門(mén)教程
- Android開(kāi)發(fā)中避免應(yīng)用無(wú)響應(yīng)的方法(Application Not Responding、ANR)
- Android 應(yīng)用APP加入聊天功能
相關(guān)文章
Android開(kāi)發(fā)筆記之:用Enum(枚舉類型)取代整數(shù)集的應(yīng)用詳解
本篇文章是對(duì)Android中用Enum(枚舉類型)取代整數(shù)集的應(yīng)用進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05
Android下拉刷新控件PullToRefresh實(shí)例解析
這篇文章主要為大家詳細(xì)解析了Android下拉刷新控件PullToRefresh實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-09-09
Android高仿京東垂直循環(huán)滾動(dòng)新聞欄
通過(guò)自定義的LinearLayout,并且textView能夠循環(huán)垂直滾動(dòng),而且條目可以點(diǎn)擊,顯示區(qū)域最多顯示2個(gè)條目,并且還有交替的屬性垂直移動(dòng)的動(dòng)畫(huà)效果,通過(guò)線程來(lái)控制滾動(dòng)的實(shí)現(xiàn)2016-03-03
Android實(shí)現(xiàn)知乎選項(xiàng)卡動(dòng)態(tài)隱藏效果實(shí)例
選項(xiàng)卡相信對(duì)大家來(lái)說(shuō)應(yīng)該不陌生,最近發(fā)現(xiàn)知乎選項(xiàng)卡的動(dòng)態(tài)隱藏效果不錯(cuò),下面這篇文章主要給大家介紹了關(guān)于Android實(shí)現(xiàn)知乎選項(xiàng)卡動(dòng)態(tài)隱藏效果的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2018-02-02
android textview設(shè)置字體的行距和字間距
這篇文章主要介紹了android textview設(shè)置字體的行距和字間距的方法,非常簡(jiǎn)單實(shí)用,有需要的小伙伴可以參考下2016-05-05
Android實(shí)現(xiàn)便于批量操作可多選的圖片ListView實(shí)例
這篇文章主要介紹了Android實(shí)現(xiàn)便于批量操作可多選的圖片ListView功能實(shí)現(xiàn)方法,涉及ListView針對(duì)多圖操作的相關(guān)技巧,需要的朋友可以參考下2016-08-08
Android自定義view實(shí)現(xiàn)多色進(jìn)度條GradientProgressView的繪制
我們常使用shape實(shí)現(xiàn)漸變色,但是shape的極限卻只有三色,如果有超過(guò)三種顏色的View的要求,那么我們就不得不去自定義View來(lái)實(shí)現(xiàn)這個(gè)需求,所以下面我們就來(lái)看看如何自定義view實(shí)現(xiàn)多色進(jìn)度條的繪制吧2023-08-08
Android中Glide實(shí)現(xiàn)超簡(jiǎn)單的圖片下載功能
本篇文章主要介紹了Android中Glide實(shí)現(xiàn)超簡(jiǎn)單的圖片下載功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-03-03

