Android原生股票图-分时图讲解和绘制(一)
16lz
2021-01-23
Android原生股票图-分时图讲解和绘制(一)
Android原生股票图-分时图讲解和绘制(二)
Android原生股票图-K线图讲解和绘制(一)
一、试图的展示效
效果一效果二
二、试图简介**
- 效果图中分为两部分:
1、主视图,主要展示分时图数据;
2、子试图,主要展示CJL图和MACD图,点击子试图时两者进行切换。 - 分时图、CJL图和MACD图简介:
1、分时图
分时图只要是 均线和成交价的折线,左轴时成交价中点为昨日成交量,右轴成交价的涨跌幅;
2、CJL图
左轴为成交量主要展示柱体的变化,柱体的类型见图:
CJL 柱体类型
右轴为持仓量主要展示折线图。
3、MACD图
如MACD(12,26,9)主要展示DIFF的值、DEA的值和STICK的值;计算公式如下:
DIFF白线:
DIFF=当前周期EMA(12)-当前周期EMA(26)
EMA(12)=当前周期EMA(12)×11/13+当前周期收盘价×2/13
EMA(26)=当前周期EMA(26)×25/27+当前周期收盘价×2/27
DEA黄线
当前周期DEA=前一周期DEA×8/10+当前周期DIF×2/10。
STICK(MACD柱)
STICK=当前周期(DIF-DEA)×2
Y轴刻度生成规则:
中轴为0,取图内DIFF、DEA、STICK三项指标最高绝对值时点值前两位向上取5的最小倍数,剩下位数以0填充,后将该值2等分并分别在中轴上下标识刻度。例如当前图内最高时点值为-1623,则前两位16向上取2的最小倍数为20,取值为2000,2等分后上下刻度分别为2000、1000、0、-1000、-2000。
柱体类型:
MACD 柱体类型
三、代码**
- MACD的计算代码 ,主要根据closePrice生成DIF、DEA和STICK
/** * 计算macd * * @param datas */ public static void calculateMACD(List datas) { float ema12 = 0; float ema26 = 0; float dif = 0; float dea = 0; float macd = 0; for (int i = 0; i < datas.size(); i++) { Minute point = datas.get(i); final float closePrice = point.getClose(); if (i == 0) { ema12 = closePrice; ema26 = closePrice; } else {// EMA(12) = 前一日EMA(12) X 11/13 + 今日收盘价 X 2/13// EMA(26) = 前一日EMA(26) X 25/27 + 今日收盘价 X 2/27 ema12 = ema12 * 11f / 13f + closePrice * 2f / 13f; ema26 = ema26 * 25f / 27f + closePrice * 2f / 27f; }// DIF = EMA(12) - EMA(26) 。// 今日DEA = (前一日DEA X 8/10 + 今日DIF X 2/10)// 用(DIF-DEA)*2即为MACD柱状图。 dif = ema12 - ema26; dea = dea * 8f / 10f + dif * 2f / 10f; macd = (dif - dea) * 2f; point.diff = dif; point.dea = dea; point.macd = macd; } }
- 子试图点击区域的处理,根据点击来却换CJL和MACD
//CJL和MACL之间的切换 @Override protected void jumpToCJLAndMACL(float downX, float downY) { //点击子试图判断 if (downX > 0 && downX < mWidth) { if (downY > mMainHeight + mVolumeTextHeight && downY < mMainHeight + mVolumeTextHeight + mVolumeHeight) { Log.d("--->", "x = " + downX + ";" + "y = " + downY); isCJL = !isCJL; invalidate(); } } //点击文字判断 if (downX > 0 && downX < mBaseTextPaddingLeft + mTextPaint.measureText("MACD") + 10) { if (downY > mMainHeight && downY < mMainHeight + mVolumeTextHeight) { Log.d("--->", "x = " + downX + ";" + "y = " + downY); isCJL = !isCJL; invalidate(); } } }
- 给子试图和主视图添加水印
/** * 添加主视图水印 * * @param canvas */ public void drawMainViewLogo(Canvas canvas) { if (mBitmapLogo != null) { int mLeft = getWidth() / 2 - mBitmapLogo.getWidth() / 2; int mTop = mMainHeight / 2 - mBitmapLogo.getHeight() / 2 + mTopPadding; canvas.drawBitmap(mBitmapLogo, mLeft, mTop, null); } } /** * 添加子视图水印 * * @param canvas */ public void drawChildViewLogo(Canvas canvas) { if (mBitmapLogo != null) { int mLeft = getWidth() / 2 - mBitmapLogo.getWidth() / 2; int mTop = mMainHeight + mVolumeTextHeight + (mVolumeHeight / 2) - mBitmapLogo.getHeight() / 2 + mTopPadding; canvas.drawBitmap(mBitmapLogo, mLeft, mTop, null); } }
- 分时图基类
public abstract class BaseMinuteView extends View implements GestureDetector.OnGestureListener, ScaleGestureDetector.OnScaleGestureListener { protected int ONE_MINUTE = 60000; protected int mMainHeight; //主视图 protected int mWidth; //试图宽度 //可修改 protected int mVolumeHeight; //子试图高度 protected int mVolumeTextHeight = 20; //CJL高度 protected int mTopPadding = 1; //据顶部 protected int mBottomPadding = 15;//距底部 //左右padding,允许修改 protected int mBasePaddingLeft = 50; protected int mBasePaddingRight = 50; protected float mBaseTextPaddingLeft = 15; //字体据左侧的距离 protected float mBaseTextPaddingRight = 15;//字体据右侧的距离 protected int mBaseTimePadding = 5; //下方时间文字 protected int GridColumns = 5; //列数 protected int mGridRows = 6; //主试图的行数 protected int mGridChildRows = 4; //子试图的行数 protected float mPointWidth; //柱子单位量 protected boolean isLongPress = false; //是否长按事件 protected boolean isClosePress = true; //关闭长按时间 protected boolean mScaleEnable = false; //是否可以缩放 protected GestureDetectorCompat mDetector; protected ScaleGestureDetector mScaleDetector; protected boolean isDrawChildView = true; //是否子试图点击事件 protected long mClickTime = 0; //点击时间 protected float mScaleY = 1; //Y轴单位量 protected float mScaleX = 1; //x轴的单位量 protected float mOldScale = 1.0f; //用来判断当前是否缩放 protected OnScaleGestureListener mOnScaleGestureListener; protected long mTotalTime; //总时间 protected Paint mGridPaint = new Paint(Paint.ANTI_ALIAS_FLAG); protected Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); protected int mBackgroundColor; protected long mPointCount = 0; //点的个数 protected float mValueStart;//成交量起始值/昨收价 protected final List mPoints = new ArrayList<>(); protected final List mTimes = new ArrayList<>(); protected Date mMainStartTime; protected Date mMainEndTime; protected Date mStartTime; protected Date mEndTime; protected int mMACDClickPoint; //MACD选择点 private Bitmap mBitmapLogo = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.ic_app_logo); private IValueFormatter mVolumeFormatter; protected int mIndex = 0; //天数索引 public BaseMinuteView(Context context) { super(context); init(); } public BaseMinuteView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(); } public BaseMinuteView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } protected void init() { mDetector = new GestureDetectorCompat(getContext(), this); mScaleDetector = new ScaleGestureDetector(getContext(), this); mBackgroundPaint.setDither(true); mBackgroundColor = Color.parseColor("#2A2D4F"); mBackgroundPaint.setColor(mBackgroundColor); mGridPaint.setDither(true); mGridPaint.setColor(Color.parseColor("#10FFFFFF")); //网格线颜色 mGridPaint.setStrokeWidth(dp2px(1)); mVolumeFormatter = new BigValueFormatter(); } /** * @param data 数据源 * @param startTime 显示的开始时间 * @param endTime 显示的结束时间 * @param times 休息开始时间 * @param yesClosePrice 昨收价 */ public void initData(Collection<? extends IMinuteLine> data, Date startTime, Date endTime, Collection<? extends IMinuteTime> times, float yesClosePrice, int index) { this.mIndex = index; if ((data == null && data.isEmpty()) || (times == null && times.isEmpty())) { return; } if (times != null) { mTimes.clear(); mTimes.addAll(times); mTotalTime = 0; } this.mMainStartTime = startTime; this.mMainEndTime = endTime; if (mMainStartTime.getTime() >= mMainEndTime.getTime()) throw new IllegalStateException("开始时间不能大于结束时间"); if (mTimes.size() != 0) { for (int i = 0; i < mTimes.size(); i++) { mStartTime = mTimes.get(i).getStartDate(); mEndTime = mTimes.get(i).getEndDate(); mTotalTime += mEndTime.getTime() - mStartTime.getTime(); } } else { mTotalTime = mMainEndTime.getTime() - mMainStartTime.getTime(); } if (mMainStartTime != mTimes.get(0).getStartDate()){ mMainStartTime = mTimes.get(0).getStartDate(); } setValueStart(yesClosePrice); //设置开始的值(昨日成交量) if (data != null) { mPoints.clear(); this.mPoints.addAll(data); mPointCount = mPoints.size(); mMACDClickPoint = mPoints.size() - 1; } notifyChanged(); } /** * 点击, 处理长安时间 * * @param event * @return */ @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction() & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: mClickTime = System.currentTimeMillis(); break; case MotionEvent.ACTION_MOVE: //一个点的时候滑动 if (event.getPointerCount() == 1) { //长按之后移动 if (isLongPress || !isClosePress) { calculateSelectedX(event.getX()); invalidate(); } } break; case MotionEvent.ACTION_UP: if (!isClosePress) { isLongPress = false; } mMACDClickPoint = mPoints.size() - 1;// if (isDrawChildView && isClickPress) {// //当点击时间小于2000ms时,为交互时间// if (System.currentTimeMillis() - mClickTime < 500) {// float downX = event.getX();// float downY = event.getY();// jumpToCJLAndMACL(downX, downY);// }// } invalidate(); break; case MotionEvent.ACTION_CANCEL: if (!isClosePress) { isLongPress = false; } mMACDClickPoint = mPoints.size() - 1; invalidate(); break; } this.mDetector.onTouchEvent(event); this.mScaleDetector.onTouchEvent(event); return true; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(widthMeasureSpec, heightMeasureSpec); int height = heightSpecSize - mTopPadding - mBottomPadding - mVolumeTextHeight; if (isDrawChildView) { this.mMainHeight = (int) (height * 0.75f); this.mVolumeHeight = (int) (height * 0.25f); } else { this.mMainHeight = (int) (height * 1f); this.mVolumeHeight = (int) (height * 0f); } this.mWidth = getMeasuredWidth(); notifyChanged(); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); int height = h - mTopPadding - mBottomPadding - mVolumeTextHeight; if (isDrawChildView) { this.mMainHeight = (int) (height * 0.75f); this.mVolumeHeight = (int) (height * 0.25f); } else { this.mMainHeight = (int) (height * 1f); this.mVolumeHeight = (int) (height * 0f); } this.mWidth = w; notifyChanged(); } @Override public void onConfigurationChanged(Configuration newConfig) {//横竖屏切换 super.onConfigurationChanged(newConfig); } @Override protected void onDraw(Canvas canvas) { //绘制背景颜色 canvas.drawColor(mBackgroundColor); if (mWidth == 0 || mMainHeight == 0) { return; } drawMainViewLogo(canvas); if (isDrawChildView) { drawChildViewLogo(canvas); } drawGird(canvas); //绘制网格 if (mPoints == null || mPoints.size() == 0) { return; } super.onDraw(canvas); } /** * 添加主视图水印 * * @param canvas */ public void drawMainViewLogo(Canvas canvas) { if (mBitmapLogo != null) { int mLeft = getWidth() / 2 - mBitmapLogo.getWidth() / 2; int mTop = mMainHeight / 2 - mBitmapLogo.getHeight() / 2 + mTopPadding; canvas.drawBitmap(mBitmapLogo, mLeft, mTop, null); } } /** * 添加子视图水印 * * @param canvas */ public void drawChildViewLogo(Canvas canvas) { if (mBitmapLogo != null) { int mLeft = getWidth() / 2 - mBitmapLogo.getWidth() / 2; int mTop = mMainHeight + mVolumeTextHeight + (mVolumeHeight / 2) - mBitmapLogo.getHeight() / 2 + mTopPadding; canvas.drawBitmap(mBitmapLogo, mLeft, mTop, null); } } //绘制网格线 private void drawGird(Canvas canvas) { //先画出坐标轴 canvas.translate(0, mTopPadding); canvas.scale(1, 1); //主视图横向的grid float rowSpace = mMainHeight / mGridRows; for (int i = 1; i <= mGridRows; i++) { canvas.drawLine(0, rowSpace * i, mWidth, rowSpace * i, mGridPaint); } //主试图纵向的grid float columnSpace = (mWidth - mBasePaddingLeft - mBasePaddingRight) / GridColumns; for (int i = 0; i <= GridColumns; i++) { canvas.drawLine(columnSpace * i + mBasePaddingLeft, 0, columnSpace * i + mBasePaddingLeft, mMainHeight, mGridPaint); } //中间轴线// canvas.drawLine(0, rowSpace * mGridRows / 2, mWidth, rowSpace * mGridRows / 2, mGridPaint); if (isDrawChildView) { //子视图横向的grid float rowChildSpace = mVolumeHeight / mGridChildRows; for (int i = 0; i <= mGridChildRows; i++) { canvas.drawLine(0, mMainHeight + rowChildSpace * i + mVolumeTextHeight, mWidth, mMainHeight + rowChildSpace * i + mVolumeTextHeight, mGridPaint); } //子试图纵向的grid float columnChildSpace = (mWidth - mBasePaddingLeft - mBasePaddingRight) / GridColumns; for (int i = 0; i <= GridColumns; i++) { canvas.drawLine(columnChildSpace * i + mBasePaddingLeft, mMainHeight + mVolumeTextHeight, columnChildSpace * i + mBasePaddingLeft, mMainHeight + mVolumeHeight + mVolumeTextHeight, mGridPaint); } } } /*********************************************抽象方法用于实现继承类**************************************/ protected abstract void notifyChanged(); protected abstract void calculateSelectedX(float x); protected abstract void jumpToCJLAndMACL(float downX, float downY); protected int dp2px(float dp) { final float scale = getContext().getResources().getDisplayMetrics().density; return (int) (dp * scale + 0.5f); } protected int sp2px(float spValue) { final float fontScale = getContext().getResources().getDisplayMetrics().scaledDensity; return (int) (spValue * fontScale + 0.5f); } /** * 设置开始的值 对称轴线 */ protected void setValueStart(float valueStart) { this.mValueStart = valueStart; } /*********************************************可设置参数**************************************/ /** * 设置表格行数 */ public void setGridRows(int gridRows) { if (gridRows < 1) { gridRows = 1; } mGridRows = gridRows; } /** * 设置子试图表格行数 */ public void setGridChildRows(int gridRows) { if (gridRows < 1) { gridRows = 1; } mGridChildRows = gridRows; } /** * 设置表格列数 */ public void setGridColumns(int gridColumns) { if (gridColumns < 1) { gridColumns = 1; } GridColumns = gridColumns; } //是否绘制子试图 public void setDrawChildView(boolean b) { isDrawChildView = b; if (!isDrawChildView) { mVolumeHeight = 0; //子试图高度 mVolumeTextHeight = 0; //CJL高度 } } //设置子试图高度 public void setVolumeHeight(int height) { mVolumeHeight = dp2px(height); } //设置CJL高度 public void setVolumeTextHeight(int height) { mVolumeTextHeight = dp2px(height); } //设置主试图高度 public void setMainHeight(int height) { mMainHeight = dp2px(height); } /** * 获取最大能有多少个点 */ public long getMaxPointCount(int count) { return mTotalTime / ONE_MINUTE; } /** * 添加一个点 */ public void addPoint(IMinuteLine point) { mPoints.add(point); notifyChanged(); } /** * 修改某个点的值 * * @param position 索引值 */ public void changePoint(int position, IMinuteLine point) { mPoints.set(position, point); notifyChanged(); } /** * 根据索引获取点 */ public IMinuteLine getItem(int position) { return mPoints.get(position); } /** * 获取点的个数 */ private int getItemSize() { return mPoints.size(); } /** * 刷新最后一个点 */ public void refreshLastPoint(IMinuteLine point) { changePoint(getItemSize() - 1, point); } //是否可以缩放 public void setScaleEnable(boolean scaleEnable) { this.mScaleEnable = scaleEnable; } /** * 设置成交量格式化器 * * @param volumeFormatter {@link IValueFormatter} 成交量格式化器 */ public void setVolumeFormatter(IValueFormatter volumeFormatter) { mVolumeFormatter = volumeFormatter; } //设置时间单位; 1代表一分钟, 2代表两分钟 public void setOpenMinute(int number) { ONE_MINUTE = number * 60000; } public void setLongPress(boolean longPress) { isLongPress = longPress; } public void setClosePress(boolean closePress) { isClosePress = closePress; } /******************************长按,点击手势*****************************************/ // 单击, 触摸屏按下时立刻触发 @Override public boolean onDown(MotionEvent e) { Log.i("--->", "onDown"); return false; } //短按, 触摸屏按下后片刻后抬起,会触发这个手势,如果迅速抬起则不会 @Override public void onShowPress(MotionEvent e) { } //抬起, 手指离开触摸屏时触发(长按、滚动、滑动时,不会触发这个手势) @Override public boolean onSingleTapUp(MotionEvent e) { switch (e.getAction()) { case MotionEvent.ACTION_DOWN: Log.i("--->", "ACTION_DOWN"); break; case MotionEvent.ACTION_UP: //双指点击时不会触发 Log.i("--->", "ACTION_UP"); if (isClosePress) { if (isDrawChildView) { //当点击时间小于2000ms时,为交互时间 if (System.currentTimeMillis() - mClickTime < 500) { float downX = e.getX(); float downY = e.getY(); jumpToCJLAndMACL(downX, downY); } } } else { isClosePress = true; } break; case MotionEvent.ACTION_MOVE: Log.i("--->", "ACTION_MOVE"); break; } return false; } //滚动, 触摸屏按下后移动 @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { return false; } //长按, 触摸屏按下后既不抬起也不移动,过一段时间后触发 @Override public void onLongPress(MotionEvent e) { isLongPress = true; isClosePress = false; calculateSelectedX(e.getX()); invalidate(); } //滑动, 触摸屏按下后快速移动并抬起,会先触发滚动手势,跟着触发一个滑动手势 @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { return false; } /******************************缩放手势*****************************************/ @Override public boolean onScale(ScaleGestureDetector detector) { if (!isScaleEnable()) { return false; } mOldScale = 1.0f; float f = detector.getScaleFactor(); mOldScale *= f;// if (mScaleX < mScaleXMin) {// mScaleX = mScaleXMin;// } else if (mScaleX > mScaleXMax) {// mScaleX = mScaleXMax;// } else {// onScaleChanged(mScaleX, oldScale);// } return true; } @Override public boolean onScaleBegin(ScaleGestureDetector detector) { return true; } @Override public void onScaleEnd(ScaleGestureDetector detector) { if (isClosePress) { if (mOldScale > 1.0f) { //放大 mOnScaleGestureListener.setLoseNumber(); } else { //收缩 mOnScaleGestureListener.setAddNumber(); } } } protected void onScaleChanged(float scale, float oldScale) { invalidate(); } protected boolean isScaleEnable() { return mScaleEnable; } public void setViewScaleGestureListener(OnScaleGestureListener listener) { if (listener != null) { this.mOnScaleGestureListener = listener; } } public interface OnScaleGestureListener { void setAddNumber(); void setLoseNumber(); }}
GestureDetectorCompat 触摸手势:主要是用来判断单指点击屏幕和双指点击屏幕从而来区分点击切换子试图还是进行缩放操作。
**ScaleGestureDetector **主要根据onScale()、onScaleBegin()和onScaleEnd()这三个方法来判断试图是放大还是缩小。
@Override public boolean onScale(ScaleGestureDetector detector) { if (!isScaleEnable()) { return false; } mOldScale = 1.0f; float f = detector.getScaleFactor(); mOldScale *= f; return true; } @Override public boolean onScaleBegin(ScaleGestureDetector detector) { return true; } @Override public void onScaleEnd(ScaleGestureDetector detector) { if (isClosePress) { if (mOldScale > 1.0f) { //放大 mOnScaleGestureListener.setLoseNumber(); } else { //收缩 mOnScaleGestureListener.setAddNumber(); } } }
- 分时图重点实现试图展示效果:
public class MinuteTimeView extends BaseMinuteView { private Paint mAvgPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); //文字 private Paint mTextMACDPaint = new Paint(Paint.ANTI_ALIAS_FLAG); //文字 private Paint mTextLeftPaint = new Paint(Paint.ANTI_ALIAS_FLAG); //左边文字 private Paint mTextReightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);//右边文字 private Paint mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG); //轴线 private Paint mTextBottomPaint = new Paint(Paint.ANTI_ALIAS_FLAG); //下边文字 private Paint mPricePaint = new Paint(Paint.ANTI_ALIAS_FLAG); private Paint mPriceAveragePaint = new Paint(Paint.ANTI_ALIAS_FLAG); private Paint mVolumePaint = new Paint(Paint.ANTI_ALIAS_FLAG); private Paint mSelectorBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private Paint mSelectorTitlePaint = new Paint(Paint.ANTI_ALIAS_FLAG); private Paint mSelectorTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private float mValueMin; //最大值 private float mValueMax; //最小值 private float mVolume = 0; //成交量 private float mInterest = 0; //持仓量 private float mVolumeMax = 0; //最大成交量 private float mVolumeMin = 0; //最小成交量 private float mInterestMax = 0; //最大持仓量 private float mInterestMin = 0; //最小持仓量 private float mMACDValueY = 0;//MACD Y轴 private long mCount = 0; private float mVolumeTimeScaleY; //各时间点成交量缩放比 private float mInterestTimeScaleY; //各时间点持仓量缩放比 private float mMACDLineScaleY; //MACD指标线的缩放量 private float mTextSize = 10; private int selectedIndex; private boolean isCJL = true; //是否为CJL图,默认true public MinuteTimeView(Context context) { super(context); initData(); } public MinuteTimeView(Context context, AttributeSet attrs) { super(context, attrs); initData(); } public MinuteTimeView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initData(); } private void initData() { setDrawChildView(true); mTopPadding = dp2px(mTopPadding); mBottomPadding = dp2px(mBottomPadding); mTextSize = sp2px(mTextSize); mVolumeHeight = dp2px(mVolumeHeight); mVolumeTextHeight = dp2px(mVolumeTextHeight); mTextPaint.setDither(true); mTextPaint.setColor(Color.parseColor("#6774FF")); mTextPaint.setTextSize(sp2px(11)); mTextPaint.setStrokeWidth(dp2px(0.5f)); mTextMACDPaint.setDither(true); mTextMACDPaint.setColor(Color.parseColor("#6774FF")); mTextMACDPaint.setTextSize(sp2px(10)); mTextMACDPaint.setStrokeWidth(dp2px(0.5f)); mTextLeftPaint.setDither(true); mTextLeftPaint.setColor(Color.parseColor("#B1B2B6")); mTextLeftPaint.setTextSize(mTextSize); mTextLeftPaint.setStrokeWidth(dp2px(0.5f)); mTextReightPaint.setDither(true); mTextReightPaint.setColor(Color.parseColor("#B1B2B6")); mTextReightPaint.setTextSize(mTextSize); mTextReightPaint.setStrokeWidth(dp2px(0.5f)); mTextBottomPaint.setDither(true); mTextBottomPaint.setColor(Color.parseColor("#6A798E")); mTextBottomPaint.setTextSize(mTextSize); mTextBottomPaint.setStrokeWidth(dp2px(0.5f)); mLinePaint.setDither(true); mLinePaint.setColor(Color.parseColor("#6774FF")); //轴线 mLinePaint.setTextSize(mTextSize); mLinePaint.setStrokeWidth(dp2px(0.7f)); mAvgPaint.setDither(true); mAvgPaint.setColor(Color.parseColor("#90A901")); mAvgPaint.setStrokeWidth(dp2px(0.5f)); mAvgPaint.setTextSize(mTextSize); mPricePaint.setDither(true); mPricePaint.setColor(Color.parseColor("#FF6600")); mPricePaint.setStrokeWidth(dp2px(0.5f)); mPricePaint.setTextSize(mTextSize); mPriceAveragePaint.setDither(true); mPriceAveragePaint.setColor(Color.parseColor("#EFD521")); mPriceAveragePaint.setStrokeWidth(dp2px(0.5f)); mSelectorBackgroundPaint.setDither(true); mSelectorBackgroundPaint.setColor(Color.parseColor("#4F5490")); mSelectorTitlePaint.setDither(true); mSelectorTitlePaint.setColor(Color.parseColor("#9EB2CD")); mSelectorTitlePaint.setTextSize(sp2px(10)); mSelectorTextPaint.setDither(true); mSelectorTextPaint.setColor(Color.parseColor("#E7EDF5")); mSelectorTextPaint.setTextSize(sp2px(13)); mVolumePaint.setDither(true); mVolumePaint.setColor(ContextCompat.getColor(getContext(), R.color.chart_red)); } @Override protected void calculateSelectedX(float x) {// selectedIndex = (int) (x * 1f / getX(mPoints.size() - 1) * (mPoints.size() - 1) + mPointWidth * 0.2f); selectedIndex = (int) (x * 1f / getX(mPoints.size() - 1) * (mPoints.size() - 1)); if (selectedIndex < 0) { selectedIndex = 0; } if (selectedIndex > mPoints.size() - 1) { selectedIndex = mPoints.size() - 1; } } /** * 根据索引获取x的值 */ private float getX(int position) { mCount = 0; if (mPoints.size() == 0 || mPoints == null || position == -1) { return 0; } Long dateTime = mPoints.get(position).getDate().getTime(); for (int i = 0; i < mTimes.size(); i++) { Long startTime = mTimes.get(i).getStartDate().getTime(); Long endTime = mTimes.get(i).getEndDate().getTime(); if (dateTime >= startTime && dateTime <= endTime) { mCount += (dateTime - startTime) / ONE_MINUTE; break; } else { mCount += (endTime - startTime) / ONE_MINUTE; } } float c = mCount * mScaleX; return mCount * mScaleX; } /** * 当数据发生变化时调用 */ @Override protected void notifyChanged() { mMACDValueY = 0; mValueMax = mValueMin = 0; mVolumeMax = mVolume = mVolumeMin = 0; mInterestMax = mInterest = mInterestMin = 0; if (mPoints.size() <= 0) { return; } if (mPoints.size() > 0) { mValueMax = mPoints.get(0).getLast(); mValueMin = mPoints.get(0).getLast(); mVolumeMax = mPoints.get(0).getVolume();//成交量 mVolume = mPoints.get(0).getVolume(); mVolumeMin = mPoints.get(0).getVolume(); mInterestMax = mPoints.get(0).getInterest(); //持仓量 mInterest = mPoints.get(0).getInterest(); mInterestMin = mPoints.get(0).getInterest(); } for (int i = 0; i < mPoints.size(); i++) { IMinuteLine point = mPoints.get(i); mValueMax = Math.max(mValueMax, point.getLast()); mValueMin = Math.min(mValueMin, point.getLast()); mVolumeMax = Math.max(mVolumeMax, point.getVolume()); mVolumeMin = Math.max(mVolumeMin, point.getVolume()); mInterestMax = Math.max(mInterestMax, point.getInterest()); mInterestMin = Math.min(mInterestMin, point.getInterest()); //MACD Y轴 mMACDValueY = Math.max(mMACDValueY, Math.max(Math.abs(point.getDea()), Math.max(Math.abs(point.getDiff()), Math.abs(point.getMacd())))); Log.i("mMACDValueY :", mMACDValueY + "--; --" + i); } mMACDValueY = StrUtil.getFaveMultipleMinimum(Long.parseLong(StrUtil.getPositiveNumber(mMACDValueY))); Log.i("mMACDValue :", mMACDValueY + ""); if (mValueMin > mValueStart) { mValueStart = (mValueMax + mValueMin) / 2; } //最大值和开始值的单位差值 float offsetValueMax = (Math.abs(mValueMax - mValueStart)) / (mGridRows / 2); float offsetValueMin = (Math.abs(mValueStart - mValueMin)) / (mGridRows / 2); //以开始的点为中点值 上下间隙多出20% float offset = (offsetValueMax > offsetValueMin ? offsetValueMax : offsetValueMin) * 1.2f; //坐标轴高度以开始的点对称 mValueMax = mValueStart + offset * (mGridRows / 2); mValueMin = mValueStart - offset * (mGridRows / 2); //y轴的缩放值 mScaleY = mMainHeight / (mValueMax - mValueMin); //判断最大值和最小值是否一致 if (mValueMax == mValueMin) { //当最大值和最小值都相等的时候 分别增大最大值和 减小最小值 mValueMax += Math.abs(mValueMax * 0.05f); mValueMin -= Math.abs(mValueMax * 0.05f); if (mValueMax == 0) { mValueMax = 1; } } if (mVolumeMax == 0) { mVolumeMax = 1; } //CJL左轴的缩放值 mVolumeMin = 0; if (mVolumeMin < 0) { mVolumeMin = 0; } mVolumeMax = StrUtil.getFaveMultipleMinimum((long) mVolumeMax); mVolumeTimeScaleY = mVolumeHeight / Math.abs(mVolumeMax - mVolumeMin); //CJL右轴的缩放值 float interest = Math.max(Math.abs(mInterestMax - mInterest), Math.abs(mInterest - mInterestMin)); mInterestMax = mInterest + interest; mInterestMin = mInterest - interest; if (mInterestMin < 0) { mInterestMin = 0; } mInterestMax = StrUtil.getZeroMultipleMinimum((long) mInterestMax, 1); mInterestMin = StrUtil.getZeroMultipleMinimum((long) mInterestMin, 2); mInterestTimeScaleY = mVolumeHeight / Math.abs(mInterestMax - mInterestMin); Log.i("---> InterestScaleY ", mInterestTimeScaleY + ""); //MACD右轴的缩放值 mMACDLineScaleY = mVolumeHeight / (2 * mMACDValueY); //x轴的缩放值 mScaleX = (float) (mWidth - mBaseTimePadding * 2) / getMaxPointCount(1); //设置主状图的宽度 mPointWidth = (float) (mWidth - mBaseTimePadding * 2) / getMaxPointCount(1); mVolumePaint.setStrokeWidth(dp2px((float) 0.5)); invalidate(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (mWidth == 0 || mMainHeight == 0 || mPoints == null || mPoints.size() == 0) { return; } //绘制平均线和成交价 if (mPoints.size() > 0) { mPricePaint.setColor(getResources().getColor(R.color.chart_FFFFFF)); IMinuteLine lastPoint = mPoints.get(0); float lastX = getX(0); for (int i = 0; i < mPoints.size(); i++) { IMinuteLine curPoint = mPoints.get(i); float curX = getX(i); canvas.drawLine(lastX + mBaseTimePadding - mScaleX / 2, getY(lastPoint.getLast()), curX + mBaseTimePadding - mScaleX / 2, getY(curPoint.getLast()), mPricePaint); //成交价// canvas.drawLine(lastX + mBaseTimePadding - mScaleX / 2,// getY(lastPoint.getAverage()),// curX + mBaseTimePadding - mScaleX / 2,// getY(curPoint.getAverage()),// mPriceAveragePaint); //均价线 if (isDrawChildView) { if (isCJL) { //CJL成交量(柱状图) if (curPoint.getOpen() < curPoint.getClose()) { mVolumePaint.setColor(getResources().getColor(R.color.color_positive_value)); } else if (curPoint.getOpen() > curPoint.getClose()) { mVolumePaint.setColor(getResources().getColor(R.color.color_negative_value)); } else if (curPoint.getOpen() == curPoint.getClose()) { mVolumePaint.setColor(getResources().getColor(R.color.chart_text)); } canvas.drawLine(curX + mBaseTimePadding - mPointWidth * 0.5f, mMainHeight + mVolumeTextHeight + mVolumeHeight, curX + mBaseTimePadding - mPointWidth * 0.5f, getVolumeCJLY(curPoint.getVolume()), mVolumePaint); } else { //MACD成交量(柱状图) if (curPoint.getMacd() > 0) { mVolumePaint.setColor(getResources().getColor(R.color.color_positive_value)); canvas.drawLine(curX + mBaseTimePadding - mPointWidth * 0.5f, getMACDLineY(0), curX + mBaseTimePadding - mPointWidth * 0.5f, getMACDLineY(curPoint.getMacd()), mVolumePaint); } else if (curPoint.getMacd() < 0) { mVolumePaint.setColor(getResources().getColor(R.color.color_negative_value)); canvas.drawLine(curX + mBaseTimePadding - mPointWidth * 0.5f, getMACDLineY(0), curX + mBaseTimePadding - mPointWidth * 0.5f, getMACDLineY(curPoint.getMacd()), mVolumePaint); } } } //给上一个只赋值 lastPoint = curPoint; lastX = curX; } } drawText(canvas); //绘制文字 //画指示线 if (isLongPress || !isClosePress) { if (selectedIndex >= mPoints.size() || selectedIndex < 0 || mPoints.size() == 0) { return; } IMinuteLine point = mPoints.get(selectedIndex); float x = getX(selectedIndex) + mBaseTimePadding - mPointWidth * 0.5f; //轴线 canvas.drawLine(x, 0, x, mMainHeight + mVolumeHeight + mVolumeTextHeight, mLinePaint);//Y// canvas.drawLine(0, getY(point.getLast()), mWidth, getY(point.getLast()), mLinePaint);//X drawMainSelector(selectedIndex, point, canvas); } } /** * draw选择器 * * @param canvas */ private void drawMainSelector(int selectedIndex, IMinuteLine point, Canvas canvas) { Paint.FontMetrics metrics = mTextLeftPaint.getFontMetrics(); float textHeight = metrics.descent - metrics.ascent; float padding = DensityUtil.dp2px(5); float margin = DensityUtil.dp2px(5); float width = 0; float left = 5; float top = 10; float bottom = 10; List strings = new ArrayList<>(); strings.add(DateUtil.getStringDateByLong(point.getDate().getTime(), 8)); strings.add("价格"); strings.add(point.getLast() + ""); strings.add("均价"); strings.add(point.getAverage() + ""); strings.add("涨跌"); strings.add(point.getUpdown()); strings.add(point.getPercent()); strings.add("持仓量"); strings.add(point.getInterest() + ""); strings.add(point.getChgInterest()); strings.add("成交量"); strings.add(point.getVolume() + ""); strings.add(point.getChgVolume());//成交量变化// strings.add("成交价");// strings.add(point.getLast() + ""); for (int i = 0; i < strings.size(); i++) { width = Math.max(width, mSelectorTextPaint.measureText(strings.get(i))); } width = width + dp2px(6) * 2; float x = getX(selectedIndex); if (x > mWidth / 2) { left = margin; } else { left = mWidth - width - margin; } float height = top + padding * 2 + (textHeight - metrics.bottom - metrics.top) / 2 + (textHeight + padding) * (strings.size() - 1); RectF r = new RectF(left, top, left + width, top + height + bottom); canvas.drawRoundRect(r, padding, padding, mSelectorBackgroundPaint); float y = top + padding * 2 + (textHeight - metrics.bottom - metrics.top) / 2;// if (x > mWidth / 2) {// left = margin + padding;// mSelectorTextPaint.setTextAlign(Paint.Align.LEFT);// mSelectorTitlePaint.setTextAlign(Paint.Align.LEFT);// } else {// left = mWidth - margin - padding;// mSelectorTextPaint.setTextAlign(Paint.Align.RIGHT);// mSelectorTitlePaint.setTextAlign(Paint.Align.RIGHT);// } for (String s : strings) { if (StrUtil.isTimeText(s)) { mSelectorTextPaint.setColor(getResources().getColor(R.color.color_text_positive_paint)); canvas.drawText(s, left + padding, y, mSelectorTextPaint); } else if (StrUtil.isChinaText(s)) { canvas.drawText(s, left + padding, y, mSelectorTitlePaint); } else { if (StrUtil.isPositiveOrNagativeNumberText(s)) { mSelectorTextPaint.setColor(getResources().getColor(R.color.color_negative_value)); canvas.drawText(s, left + padding, y, mSelectorTextPaint); } else if (s.equals("- -")) { mSelectorTextPaint.setColor(getResources().getColor(R.color.color_text_positive_paint)); canvas.drawText(s, left + padding, y, mSelectorTextPaint); } else { mSelectorTextPaint.setColor(getResources().getColor(R.color.color_text_positive_paint)); canvas.drawText(s, left + padding, y, mSelectorTextPaint); } } y += textHeight + padding; } //绘制 MACD, DIFF, DEA, STICK 的指标 if (isDrawChildView) { //GJL if (!isCJL) { drawMACDText(selectedIndex, point, canvas); invalidate(); } } } private void drawText(Canvas canvas) { Paint.FontMetrics fm = mTextLeftPaint.getFontMetrics(); float textHeight = fm.descent - fm.ascent; float baseLine = (textHeight - fm.bottom - fm.top) / 2; float rowValue = (mValueMax - mValueMin) / mGridRows; float rowSpace = mMainHeight / mGridRows; //画左边的值 mTextLeftPaint.setColor(getResources().getColor(R.color.color_positive_value)); canvas.drawText(StrUtil.getOneDecimals(mValueStart + rowValue * 3), mBaseTextPaddingLeft, baseLine, mTextLeftPaint); //绘制最大值 mTextLeftPaint.setColor(getResources().getColor(R.color.color_negative_value)); canvas.drawText(StrUtil.getOneDecimals(mValueStart - rowValue * 3), mBaseTextPaddingLeft, mMainHeight - textHeight + baseLine, mTextLeftPaint); //绘制最小值 for (int i = 0; i < 3; i++) { if (i == 0) { String text = StrUtil.getOneDecimals(mValueStart + rowValue * 1.5); mTextLeftPaint.setColor(getResources().getColor(R.color.color_positive_value)); canvas.drawText(text, mBaseTextPaddingLeft, (float) (rowSpace * 1.5 + baseLine / 2), mTextLeftPaint); } else if (i == 1) { String text = StrUtil.getOneDecimals(mValueStart); mTextLeftPaint.setColor(getResources().getColor(R.color.color_central_paint)); canvas.drawText(text, mBaseTextPaddingLeft, fixTextY(rowSpace * 3), mTextLeftPaint); } else if (i == 2) { String text = StrUtil.getOneDecimals(mValueStart - rowValue * 1.5); mTextLeftPaint.setColor(getResources().getColor(R.color.color_negative_value)); canvas.drawText(text, mBaseTextPaddingLeft, (float) (mMainHeight - textHeight / 2 - rowSpace * 1.5 + baseLine / 2), mTextLeftPaint); } } //画右边的值 mTextReightPaint.setTextAlign(Paint.Align.RIGHT); mTextReightPaint.setColor(getResources().getColor(R.color.color_positive_value)); String text = StrUtil.subAddAndSubMark(StrUtil.floatToString((mValueMax - mValueStart) * 100f / mValueStart, 2) + "%"); canvas.drawText("+" + text, mWidth - mBaseTextPaddingRight, baseLine, mTextReightPaint); mTextReightPaint.setColor(getResources().getColor(R.color.color_negative_value)); text = StrUtil.subAddAndSubMark(StrUtil.floatToString(Math.abs(mValueMin - mValueStart) * 100f / mValueStart, 2) + "%"); canvas.drawText("-" + text, mWidth - mBaseTextPaddingRight, mMainHeight - textHeight + baseLine, mTextReightPaint); for (int i = 0; i < 3; i++) { if (i == 0) { text = StrUtil.subAddAndSubMark(StrUtil.floatToString((rowValue * 1.5f) * 100f / mValueStart, 2) + "%"); mTextReightPaint.setColor(getResources().getColor(R.color.color_positive_value)); canvas.drawText("+" + text, mWidth - mBaseTextPaddingRight, (float) (rowSpace * 1.5 + baseLine / 2), mTextReightPaint); } else if (i == 1) { text = "0"; mTextReightPaint.setColor(getResources().getColor(R.color.color_central_paint)); canvas.drawText(text, mWidth - mBaseTextPaddingRight, fixTextY(rowSpace * 3), mTextReightPaint); } else if (i == 2) { text = StrUtil.subAddAndSubMark(StrUtil.floatToString((rowValue * 1.5f) * 100f / mValueStart, 2) + "%"); mTextReightPaint.setColor(getResources().getColor(R.color.color_negative_value)); canvas.drawText("-" + text, mWidth - mBaseTextPaddingRight, (float) (mMainHeight - textHeight / 2 - rowSpace * 1.5 + baseLine / 2), mTextReightPaint); } } //画时间 float y = mMainHeight + mVolumeHeight + mVolumeTextHeight + baseLine; mTextBottomPaint.setTextAlign(Paint.Align.LEFT); mCount = 0; if (mIndex == 1) { canvas.drawText(DateUtil.getStringDateByLong(mMainStartTime.getTime(), 8), mBaseTimePadding, y, mTextBottomPaint); //起始时间 for (int i = 0; i < mTimes.size() - 1; i++) { mCount += (mTimes.get(i).getEndDate().getTime() - mTimes.get(i).getStartDate().getTime()) / ONE_MINUTE; float x = mScaleX * mCount; String dataStr = DateUtil.getStringDateByLong(mTimes.get(i + 1).getStartDate().getTime(), 8); if (dataStr.equals("04:30")) continue; canvas.drawText(dataStr, mBaseTimePadding + x - mTextBottomPaint.measureText(DateUtil.getStringDateByLong(mMainStartTime.getTime(), 8)), y, mTextBottomPaint); //中间时间 } canvas.drawText(DateUtil.getStringDateByLong(mMainEndTime.getTime(), 8), mWidth - mBaseTimePadding - mTextBottomPaint.measureText(DateUtil.getStringDateByLong(mMainEndTime.getTime(), 8)), y, mTextBottomPaint);//结束时间 } else { //大于1天时 if (mTimes.size() / 4 == mIndex && mTimes.size() != mIndex) { canvas.drawText(DateUtil.getStringDateByLong(mMainStartTime.getTime(), 9), mBaseTimePadding, y, mTextBottomPaint); //起始时间 for (int i = 0; i < mTimes.size() - 1; i++) { mCount += (mTimes.get(i).getEndDate().getTime() - mTimes.get(i).getStartDate().getTime()) / ONE_MINUTE; float x = mScaleX * mCount; String dataStr = DateUtil.getStringDateByLong(mTimes.get(i).getStartDate().getTime(), 8); if (dataStr.equals("09:00")) { //上期所 canvas.drawText(DateUtil.getStringDateByLong(mTimes.get(i).getStartDate().getTime(), 9), mBaseTimePadding + x - mTextBottomPaint.measureText(DateUtil.getStringDateByLong(mMainStartTime.getTime(), 9)) / 3, y, mTextBottomPaint); } } } else if (mTimes.size() / 2 == mIndex && mTimes.size() != mIndex) { canvas.drawText(DateUtil.getStringDateByLong(mMainStartTime.getTime(), 9), mBaseTimePadding, y, mTextBottomPaint); //起始时间 for (int i = 0; i < mTimes.size() - 1; i++) { if (i + 1 == mTimes.size()) return; mCount += (mTimes.get(i).getEndDate().getTime() - mTimes.get(i).getStartDate().getTime()) / ONE_MINUTE; float x = mScaleX * mCount; String dataStr = DateUtil.getStringDateByLong(mTimes.get(i + 1).getStartDate().getTime(), 8); if (dataStr.equals("09:30")) { // //上证指数 (INDEX) canvas.drawText(DateUtil.getStringDateByLong(mTimes.get(i + 1).getStartDate().getTime(), 9), mBaseTimePadding + x - mTextBottomPaint.measureText(DateUtil.getStringDateByLong(mMainStartTime.getTime(), 9)) / 3, y, mTextBottomPaint); } else if (dataStr.equals("06:00")) { //上证指数 (INDEX) canvas.drawText(DateUtil.getStringDateByLong(mTimes.get(i + 1).getStartDate().getTime(), 9), mBaseTimePadding + x - mTextBottomPaint.measureText(DateUtil.getStringDateByLong(mMainStartTime.getTime(), 9)) / 3, y, mTextBottomPaint); } } } else if (mTimes.size() == mIndex) { canvas.drawText(DateUtil.getStringDateByLong(mMainStartTime.getTime(), 9), mBaseTimePadding, y, mTextBottomPaint); //起始时间 for (int i = 0; i < mTimes.size() - 1; i++) { if (i + 1 == mTimes.size()) return; mCount += (mTimes.get(i).getEndDate().getTime() - mTimes.get(i).getStartDate().getTime()) / ONE_MINUTE; float x = mScaleX * mCount; String dataStr = DateUtil.getStringDateByLong(mTimes.get(i + 1).getStartDate().getTime(), 8); if (dataStr.equals("08:00")) { //LME canvas.drawText(DateUtil.getStringDateByLong(mTimes.get(i + 1).getStartDate().getTime(), 9), mBaseTimePadding + x - mTextBottomPaint.measureText(DateUtil.getStringDateByLong(mMainStartTime.getTime(), 9)) / 3, y, mTextBottomPaint); } else if (dataStr.equals("21:30")) { //上证指数 (INDEX) canvas.drawText(DateUtil.getStringDateByLong(mTimes.get(i + 1).getStartDate().getTime(), 9), mBaseTimePadding + x - mTextBottomPaint.measureText(DateUtil.getStringDateByLong(mMainStartTime.getTime(), 9)) / 3, y, mTextBottomPaint); } else if (dataStr.equals("09:30")) { //外汇 (INDEX) canvas.drawText(DateUtil.getStringDateByLong(mTimes.get(i + 1).getStartDate().getTime(), 9), mBaseTimePadding + x - mTextBottomPaint.measureText(DateUtil.getStringDateByLong(mMainStartTime.getTime(), 9)) / 3, y, mTextBottomPaint); } } } } if (isDrawChildView) { //GJL if (isCJL) { drawCJL(canvas); } else { drawMACD(canvas); } } } //MACD , 存在问题 private void drawMACD(Canvas canvas) { //上面文字 mTextPaint.setColor(getResources().getColor(R.color.color_child_text)); Paint.FontMetrics fm = mTextPaint.getFontMetrics(); float textHeight = fm.descent - fm.ascent; float baseLine = (textHeight - fm.bottom - fm.top) / 2; mTextPaint.setTextAlign(Paint.Align.LEFT); canvas.drawText("MACD", mBaseTextPaddingLeft, mMainHeight + mVolumeTextHeight / 2 + textHeight - baseLine, mTextPaint); //存在问题?? //绘制 MACD, DIFF, DEA, STICK 的指标 drawMACDText(0, mPoints.get(mMACDClickPoint), canvas); //左边文字 float rowChildSpace = mVolumeHeight / mGridChildRows; mTextPaint.setColor(getResources().getColor(R.color.color_candle_paint)); canvas.drawText(StrUtil.getPositiveNumber(mMACDValueY), mBaseTextPaddingLeft, mMainHeight + baseLine + mVolumeTextHeight, mTextPaint); //绘制最大值 canvas.drawText("0", mBaseTextPaddingLeft, mMainHeight + mVolumeTextHeight + fixTextY(rowChildSpace * 2), mTextPaint); //中间值 canvas.drawText(StrUtil.getPositiveNumber(-mMACDValueY), mBaseTextPaddingLeft, mMainHeight - textHeight + baseLine + mVolumeTextHeight + mVolumeHeight, mTextPaint); //绘制最小值 //DIFF线 if (mPoints.size() > 0) { mPricePaint.setColor(getResources().getColor(R.color.chart_FFFFFF)); mPricePaint.setStyle(Paint.Style.STROKE); IMinuteLine lastPoint = mPoints.get(0); float lastX = getX(0); for (int i = 0; i < mPoints.size(); i++) { IMinuteLine curPoint = mPoints.get(i); float curX = getX(i);// Log.i("diff --> :" , curPoint.getDiff() + ""); canvas.drawLine(lastX + mBaseTimePadding - mScaleX / 2, getMACDLineY(lastPoint.getDiff()), curX + mBaseTimePadding - mScaleX / 2, getMACDLineY(curPoint.getDiff()), mPricePaint); //成交价 //给上一个只赋值 lastPoint = curPoint; lastX = curX; } } //DEA线 if (mPoints.size() > 0) { mPricePaint.setColor(getResources().getColor(R.color.color_cjl_line_paint)); IMinuteLine lastPoint = mPoints.get(0); float lastX = getX(0); for (int i = 0; i < mPoints.size(); i++) { IMinuteLine curPoint = mPoints.get(i); float curX = getX(i);// Log.i("dea --> :" , curPoint.getDea() + ""); canvas.drawLine(lastX + mBaseTimePadding - mScaleX / 2, getMACDLineY(lastPoint.getDea()), curX + mBaseTimePadding - mScaleX / 2, getMACDLineY(curPoint.getDea()), mPricePaint); //成交价 //给上一个只赋值 lastPoint = curPoint; lastX = curX; } } } //绘制 MACD, DIFF, DEA, STICK 的指标 private void drawMACDText(int selectedIndex, IMinuteLine point, Canvas canvas) { mMACDClickPoint = selectedIndex; Paint.FontMetrics fm = mTextMACDPaint.getFontMetrics(); float textHeight = fm.descent - fm.ascent; float baseLine = (textHeight - fm.bottom - fm.top) / 2; float x = dp2px(6) + mTextPaint.measureText("MACD ") + mBaseTextPaddingLeft; float countX = (mWidth - x) / 4; mTextMACDPaint.setTextAlign(Paint.Align.LEFT); mTextMACDPaint.setColor(getResources().getColor(R.color.color_macd_text)); canvas.drawText("MACD(12,26,9)", x, mMainHeight + mVolumeTextHeight / 2 + textHeight - baseLine, mTextMACDPaint); //MACD mTextMACDPaint.setColor(getResources().getColor(R.color.color_diff_text)); canvas.drawText("DIFF:" + StrUtil.floatToString(point.getDiff(), 2), x + countX, mMainHeight + mVolumeTextHeight / 2 + textHeight - baseLine, mTextMACDPaint); //DIFF mTextMACDPaint.setColor(getResources().getColor(R.color.color_dea_text)); canvas.drawText("DEA:" + StrUtil.floatToString(point.getDea(), 2), x + countX * 2, mMainHeight + mVolumeTextHeight / 2 + textHeight - baseLine, mTextMACDPaint); //DEA mTextMACDPaint.setColor(getResources().getColor(R.color.c6774FF)); canvas.drawText("STICK:" + StrUtil.floatToString(point.getMacd(), 2), x + countX * 3, mMainHeight + mVolumeTextHeight / 2 + textHeight - baseLine, mTextMACDPaint); //STICK } //GJL private void drawCJL(Canvas canvas) { //上面文字 mTextPaint.setColor(getResources().getColor(R.color.color_child_text)); Paint.FontMetrics fm = mTextPaint.getFontMetrics(); float textHeight = fm.descent - fm.ascent; float baseLine = (textHeight - fm.bottom - fm.top) / 2; mTextPaint.setTextAlign(Paint.Align.LEFT); canvas.drawText("CJL", mBaseTextPaddingLeft, mMainHeight + mVolumeTextHeight / 2 + textHeight - baseLine, mTextPaint);//存在问题 ??? float rowChildSpace = mVolumeHeight / mGridChildRows; if (mVolumeMax != mVolumeMin) { //左边文字 mTextPaint.setColor(getResources().getColor(R.color.color_candle_paint)); canvas.drawText(StrUtil.getPositiveNumber(mVolumeMax) + "", mBaseTextPaddingLeft, mMainHeight + baseLine + mVolumeTextHeight, mTextPaint); //绘制最大值 String text = StrUtil.getPositiveNumber((mVolumeMax - mVolumeMin) / 2); canvas.drawText(text, mBaseTextPaddingLeft, mMainHeight + mVolumeTextHeight + fixTextY(rowChildSpace * 2), mTextPaint); //中间值 canvas.drawText(StrUtil.getFaveMultipleMinimum((long) mVolumeMin) + "", mBaseTextPaddingLeft, mMainHeight - textHeight + baseLine + mVolumeTextHeight + mVolumeHeight, mTextPaint); //绘制最小值 } //GJL线 if (mPoints.size() > 0) { mPricePaint.setColor(getResources().getColor(R.color.color_cjl_line_paint)); IMinuteLine lastPoint = mPoints.get(0); float lastX = getX(0); for (int i = 0; i < mPoints.size(); i++) { IMinuteLine curPoint = mPoints.get(i); float curX = getX(i); canvas.drawLine(lastX + mBaseTimePadding - mScaleX / 2, getInterestCJLY(lastPoint.getInterest()), curX + mBaseTimePadding - mScaleX / 2, getInterestCJLY(curPoint.getInterest()), mPricePaint); //成交价 //给上一个只赋值 lastPoint = curPoint; lastX = curX; } } if (mInterestMax != mInterestMin) { //右边文字 mTextPaint.setTextAlign(Paint.Align.RIGHT); mTextPaint.setColor(getResources().getColor(R.color.color_candle_paint)); canvas.drawText(StrUtil.getPositiveNumber((long) mInterestMax) + "", mWidth - mBaseTextPaddingRight, mMainHeight + baseLine + mVolumeTextHeight, mTextPaint); //绘制最大值 String text1 = StrUtil.getPositiveNumber(mInterest); canvas.drawText(text1, mWidth - mBaseTextPaddingRight, mMainHeight + mVolumeTextHeight + fixTextY(rowChildSpace * 2), mTextPaint); //中间值 canvas.drawText(StrUtil.getPositiveNumber((long) mInterestMin) + "", mWidth - mBaseTextPaddingRight, mMainHeight - textHeight + baseLine + mVolumeTextHeight + mVolumeHeight, mTextPaint); //绘制最小值 } } //CJL和MACL之间的切换 @Override protected void jumpToCJLAndMACL(float downX, float downY) { //点击子试图判断 if (downX > 0 && downX < mWidth) { if (downY > mMainHeight + mVolumeTextHeight && downY < mMainHeight + mVolumeTextHeight + mVolumeHeight) { Log.d("--->", "x = " + downX + ";" + "y = " + downY); isCJL = !isCJL; invalidate(); } } //点击文字判断 if (downX > 0 && downX < mBaseTextPaddingLeft + mTextPaint.measureText("MACD") + 10) { if (downY > mMainHeight && downY < mMainHeight + mVolumeTextHeight) { Log.d("--->", "x = " + downX + ";" + "y = " + downY); isCJL = !isCJL; invalidate(); } } } /** * 修正y值 */ private float getY(float value) { return (mValueMax - value) * mScaleY; } //CJL柱形 private float getVolumeCJLY(float value) { return mMainHeight + mVolumeTextHeight + mVolumeHeight - (value - mVolumeMin) * mVolumeTimeScaleY; } //CJL线 private float getInterestCJLY(float value) { return mMainHeight + mVolumeTextHeight + mVolumeHeight - (value - mInterestMin) * mInterestTimeScaleY; } //MACD指标线 private float getMACDLineY(float value) { if (value == 0) { return mMainHeight + mVolumeTextHeight + mVolumeHeight / 2; } else if (value > 0) { return mMainHeight + mVolumeTextHeight + mVolumeHeight / 2 - value * mMACDLineScaleY; } else if (value < 0) { return mMainHeight + mVolumeTextHeight + mVolumeHeight / 2 - value * mMACDLineScaleY; } else { return 0; } } /** * 解决text居中的问题 */ public float fixTextY(float y) { Paint.FontMetrics fontMetrics = mTextLeftPaint.getFontMetrics(); return (y + (fontMetrics.descent - fontMetrics.ascent) / 2 - fontMetrics.descent); }}
具体详解我放在Android原生股票图-分时图讲解和绘制(二)中。
更多相关文章
- Android——弹出窗口中实现时间选择,文本输入,遇到Popupwindow不穿
- Android中 完美实现 计时 倒计时 时间间隔处理等功能实现 by Cou
- android 仿课程表,时间星期展示选择列表,简单易懂
- android中获得系统的时间
- android 时间1
- android 自定义toast停留时间
- android 图片缩放抗锯齿