k線相關-深度圖篇
阿新 • • 發佈:2018-11-27
先上圖:
這裡需要理解下,深度圖左右兩邊,綠色代表買部分,紅色代表賣部分,買部分從中間到最左邊,price依次遞減,賣的價格從中間到最右邊價格依次遞減,縱座標這裡給的是累計的交易量;
理解了要繪製的深度圖,我們就可以開始繪製了。
style部分主要主要涉及買賣部分的line ,背景,字型大小,字型顏色等,以及深度圖單點以及長按等屬性;
直接上程式碼:
<declare-styleable name="DepthView"> <!--買--> <attr name="dvBuyLineColor" format="color"/> <attr name="dvBuyBGColor" format="color"/> <attr name="dvBuyLineStrokeWidth" format="integer"/> <!--賣 --> <attr name="dvSellLineColor" format="color"/> <attr name="dvSellBGColor" format="color"/> <attr name="dvSellLineStrokeWidth" format="integer"/> <!--橫座標 --> <attr name="dvAbscissaColor" format="color"/> <attr name="dvAbscissaTextSize" format="integer"/> <!--縱座標 --> <attr name="dvOrdinateColor" format="color"/> <attr name="dvOrdinateTextSize" format="integer"/> <attr name="dvOrdinateNum" format="integer"/> <attr name="dvInfoBgColor" format="color"/> <attr name="dvInfoTextSize" format="integer"/> <attr name="dvInfoTextColor" format="color"/> <attr name="dvInfoLineCol" format="color"/> <!--遊標線寬度、交界圓點半徑、價格文字說明、數量文字說明--> <attr name="dvInfoLineWidth" format="float" /> <attr name="dvInfoPointRadius" format="integer"/> <attr name="dvInfoPriceTitle" format="string"/> <attr name="dvInfoVolumeTitle" format="string"/> </declare-styleable>
資料來源部分 :
bean十分簡單,主要就price和volume兩個值,另外為了方便繪製,新增x,y兩個座標值;
public class DepthBean implements Comparable<DepthBean> { private double price;//委託價 private double volume;//委託量 private int tradeType; private String coinName; private float xValue; private float yValue; public DepthBean(double price, double volume, int tradeType, String coinName) { this.price = price; this.volume = volume; this.tradeType = tradeType; this.coinName = coinName; } public String getCoinName() { return coinName; } public void setCoinName(String coinName) { this.coinName = coinName; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public double getVolume() { return volume; } public void setVolume(double volume) { this.volume = volume; } public int getTradeType() { return tradeType; } public void setTradeType(int tradeType) { this.tradeType = tradeType; } public float getxValue() { return xValue; } public void setxValue(float xValue) { this.xValue = xValue; } public float getyValue() { return yValue; } public void setyValue(float yValue) { this.yValue = yValue; } @Override public int compareTo(@NonNull DepthBean o) { double diff=this.price-o.price; if (diff>0){ return 1; }else if (diff<0){ return -1; }else{ return 0; } } @Override public String toString() { return "DepthBean{price="+price+",volume="+volume+",coinName="+coinName+",tradeType="+tradeType+"}"; } }
onlayout
@Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); leftStart=getPaddingLeft()+10; rightEnd=getMeasuredWidth()-getPaddingRight()-1; topStart=getPaddingTop()+20; bottomEnd=getMeasuredHeight() - getPaddingBottom() - 1; double maxBuyVolume; double minBuyVolume; double maxSellVolume; double minSellVolume; if (!buyList.isEmpty()){ maxBuyVolume=buyList.get(0).getVolume(); minBuyVolume=buyList.get(buyList.size()-1).getVolume(); }else{ maxBuyVolume=minBuyVolume=0; } if (!sellList.isEmpty()){ maxSellVolume=sellList.get(sellList.size()-1).getVolume(); minSellVolume=sellList.get(0).getVolume(); }else{ maxSellVolume=minSellVolume=0; } //獲得最大最小的交易量的值 maxVolume=Math.max(maxBuyVolume,maxSellVolume); minVolume=Math.min(minBuyVolume,minSellVolume); //buylist 不空,取買最低價 ,否則取賣最高價 if (!buyList.isEmpty()){ leftPriceStr=setPrecision(buyList.get(0).getPrice(),priceScale); }else if (!sellList.isEmpty()){ leftPriceStr=setPrecision(sellList.get(0).getPrice(),priceScale); }else{ } //selllist 不空 取賣最低價, 否則取買最高價 if (!sellList.isEmpty()){ rightPriceStr=setPrecision(sellList.get(sellList.size()-1).getPrice(),priceScale); }else if (!buyList.isEmpty()){ rightPriceStr=setPrecision(buyList.get(buyList.size()-1).getPrice(),priceScale); } strokePaint.getTextBounds(leftPriceStr,0,leftPriceStr.length(),textRect); //深度圖除文字外的高度, depthImgHeight = bottomEnd - topStart - textRect.height() - 18; avgHeightPerVolume= depthImgHeight / (maxVolume - minVolume); //x軸每一刻度的值 avgWidthPerSize= (rightEnd - leftStart) / (buyList.size() + sellList.size()); avgVolumeSpace = maxVolume / ordinateNum; avgOrdinateSpace = depthImgHeight / ordinateNum; //計算x,y座標 for (int i=0;i<buyList.size();i++){ buyList.get(i).setxValue(leftStart+(float) avgWidthPerSize*i); buyList.get(i).setyValue(topStart + (float) ((maxVolume - buyList.get(i).getVolume()) * avgHeightPerVolume)); } for (int i=0;i<sellList.size();i++){ sellList.get(i).setxValue(leftStart+(float) avgWidthPerSize*(i+buyList.size())); sellList.get(i).setyValue(topStart + (float) ((maxVolume - sellList.get(i).getVolume()) * avgHeightPerVolume)); } }
座標軸+橫縱座標
onlayout中已經計算了所需要的一些資料,這裡直接用
//座標軸,橫縱座標的值
public void drawCoordinate(Canvas canvas){
//橫座標
strokePaint.setStrokeWidth(1);
strokePaint.setColor(abscissaColor);
strokePaint.setTextSize(20);
strokePaint.getTextBounds(rightPriceStr,0,rightPriceStr.length(),textRect);
canvas.drawText(leftPriceStr,leftStart+2,bottomEnd-5,strokePaint);
canvas.drawText(rightPriceStr,rightEnd-textRect.width(),bottomEnd-5,strokePaint);
double centerPrice=0;
if (!buyList.isEmpty()&&!buyList.isEmpty()){
centerPrice= (buyList.get(buyList.size()-1).getPrice()+sellList.get(0).getPrice())/2;
}else if (!buyList.isEmpty()){
centerPrice=buyList.get(buyList.size()-1).getPrice();
}else if (!sellList.isEmpty()){
centerPrice=sellList.get(buyList.size()-1).getPrice();
}
canvas.drawText(setPrecision(centerPrice,priceScale),getMeasuredWidth()/2-30,bottomEnd-5,strokePaint);
//縱座標
strokePaint.setStrokeWidth(0);
strokePaint.setColor(ordinateColor);
strokePaint.setTextSize(20);
strokePaint.getTextBounds(maxVolume+"",0,(maxVolume+"").length(),textRect);
for (int i=0;i<ordinateNum;i++){
String text=setPrecision(maxVolume-avgVolumeSpace*(i),volumeScale);
canvas.drawText(text,leftStart+2,(float) (topStart+textRect.height()+i*avgOrdinateSpace),strokePaint);
}
}
繪製買賣線以及背景
買部分,根據buyList獲取每個點x,y軸資料,y軸相同,
//繪製邊線以及背景
private void drawLineAndBg(Canvas canvas){
//左側買
if (buyList!=null){
linePath.reset();
bgPath.reset();
for (int i=0;i<buyList.size();i++){
if (i == 0) {
linePath.moveTo(buyList.get(i).getxValue(),buyList.get(i).getyValue());
bgPath.moveTo(buyList.get(i).getxValue(),buyList.get(i).getyValue());
}else{
linePath.lineTo(buyList.get(i).getxValue(),buyList.get(i).getyValue());
bgPath.lineTo(buyList.get(i).getxValue(),buyList.get(i).getyValue());
}
}
if (!buyList.isEmpty() && topStart + (float) ((maxVolume - buyList.get(buyList.size()-1).getVolume()) * avgHeightPerVolume) < topStart + depthImgHeight) {
bgPath.lineTo(leftStart + (float) avgWidthPerSize * (buyList.size()-1), (float) (topStart + depthImgHeight));
}
bgPath.lineTo(leftStart, (float) (topStart + depthImgHeight));
bgPath.close();
//整個買部分的範圍
fillPaint.setColor(buyBgColor);
canvas.drawPath(bgPath, fillPaint);
//邊線
strokePaint.setColor(buyLineColor);
strokePaint.setTextSize(20);
strokePaint.setStrokeWidth(4);
canvas.drawPath(linePath,strokePaint);
}
//右側賣
if (sellList!=null){
linePath.reset();
bgPath.reset();
for (int i=0;i<sellList.size();i++){
if (i == 0) {
linePath.moveTo(sellList.get(i).getxValue(),sellList.get(i).getyValue());
bgPath.moveTo(sellList.get(i).getxValue(),sellList.get(i).getyValue());
}else{
linePath.lineTo(sellList.get(i).getxValue(),sellList.get(i).getyValue());
bgPath.lineTo(sellList.get(i).getxValue(),sellList.get(i).getyValue());
}
}
bgPath.lineTo(sellList.get(sellList.size()-1).getxValue(), (float) (topStart + depthImgHeight));
if (!sellList.isEmpty() && topStart + (float) ((maxVolume - sellList.get(0).getVolume()) * avgHeightPerVolume)< (float) (topStart + depthImgHeight)) {
bgPath.lineTo(sellList.get(0).getxValue(), (float) (topStart + depthImgHeight));
}
bgPath.close();
fillPaint.setColor(sellBgColor);
canvas.drawPath(bgPath, fillPaint);
strokePaint.setColor(sellLineColor);
strokePaint.setTextSize(20);
strokePaint.setStrokeWidth(4);
canvas.drawPath(linePath,strokePaint);
}
}
詳情彈框
詳情繪製比較簡單,主要有兩部分判斷,第一部分,根據使用者點選的位置,獲取對應的位置的bean,獲得對應的price,x,y等資料;
private float clickDownX;
private float clickDownY;
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
clickDownX=event.getX();
clickDownY=event.getY();
break;
case MotionEvent.ACTION_UP:
float diffX=Math.abs(event.getX()-clickDownX);
float diffY=Math.abs(event.getY()-clickDownY);
if (diffX<moveLimitDistance&&diffY<moveLimitDistance){
isShowinfos=true;
getClickBean(clickDownX);
if (clickBean!=null){
invalidate();
}
}
break;
}
return true;
}
private DepthBean clickBean;
//獲取點選位置的bean
public void getClickBean(float xValue){
clickBean=null;
if (sellList.isEmpty()){
for (int i=0;i<buyList.size();i++){
if (i+1<buyList.size()&&xValue>=(leftStart + (float) avgWidthPerSize * i)&&xValue<(leftStart + (float) avgWidthPerSize * (i+1))){
clickBean=buyList.get(i);
break;
}else if (i==buyList.size()-1&&xValue>= (leftStart + (float) avgWidthPerSize * i)){
clickBean=buyList.get(i);
break;
}
}
}else if (xValue<(leftStart + (float) avgWidthPerSize * buyList.size())){
for (int i=0;i<buyList.size();i++){
if (i+1<buyList.size()&&xValue>=(leftStart + (float) avgWidthPerSize * i)&&xValue<(leftStart + (float) avgWidthPerSize * (i+1))){
clickBean=buyList.get(i);
break;
}else if (i==buyList.size()-1&&xValue>= (leftStart + (float) avgWidthPerSize * i)&&xValue<(leftStart + (float) avgWidthPerSize * (i+1))){
clickBean=buyList.get(i);
break;
}
}
}else{
for (int i=0;i<sellList.size();i++){
if (i+1<sellList.size()&&xValue>=sellList.get(i).getxValue()&&xValue<sellList.get(i+1).getxValue()){
clickBean=sellList.get(i);
break;
}else if (i==sellList.size()-1&&xValue>= sellList.get(i).getxValue()){
clickBean=sellList.get(i);
break;
}
}
}
}
繪製詳情彈框部分,主要繪製基準線,背景框,以及對應的price以及volume等資料;
基準線通過上面onTouchEvent獲取的clickBean,從而獲取x軸座標即可繪製。
背景框根據繪製文案的最大寬度+padding與clickbean的x比較確定在基準左右。
//繪製詳情資訊
public void drawDetailsInfo(Canvas canvas){
if (isShowinfos&&clickBean!=null){
//準線
strokePaint.setStrokeWidth(1);
strokePaint.setTextSize(30);
strokePaint.setColor(sellLineColor);
canvas.drawLine(clickDownX,topStart,clickDownX+2,bottomEnd,strokePaint);
String priceStr="價格: "+setPrecision(clickBean.getPrice(),priceScale);
String volume ="累計交易量:"+setPrecision(clickBean.getVolume(),volumeScale);
strokePaint.setStrokeWidth(1);
strokePaint.setTextSize(30);
strokePaint.setColor(infoTextCol);
strokePaint.getTextBounds(priceStr,0,priceStr.length(),textRect);
float priceStrWidth=textRect.width();
float priceStrHeight=textRect.height();
strokePaint.getTextBounds(volume,0,volume.length(),textRect);
float volumeStrWidth=textRect.width();
float volumeStrHeight=textRect.height();
float maxWidth=Math.max(priceStrWidth,volumeStrWidth);
float maxHeight=Math.max(priceStrHeight,volumeStrHeight);
float bgLeft,bgRight,bgBottom,bgTop;
//根據x座標判斷,繪製的線上的左邊還是右邊
if (clickBean.getxValue()<maxWidth+topStart+60){
bgLeft=clickBean.getxValue();
bgRight=clickBean.getxValue()+maxWidth+60;
}else{
bgLeft=clickBean.getxValue()-maxWidth-60;
bgRight=clickBean.getxValue();
}
//根據y座標判斷
if (topStart+depthImgHeight-clickBean.getyValue()<maxHeight+60){
bgTop=clickBean.getyValue()-maxHeight-60;
bgBottom=clickBean.getyValue();
}else{
bgBottom=clickBean.getyValue()+maxHeight+60;
bgTop=clickBean.getyValue();
}
fillPaint.setColor(infoBgCol);
canvas.drawRect(bgLeft,bgTop,bgRight,bgBottom,fillPaint);
canvas.drawText(priceStr,bgLeft+20,bgTop+40,strokePaint);
canvas.drawText(volume,bgLeft+20,bgTop+45+priceStrHeight,strokePaint);
}
}