先上图:
这里需要理解下,深度图左右两边,绿色代表买部分,红色代表卖部分,买部分从中间到最左边,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);
}
}