一、BaseMinuteView基类的讲解
首先BaseMinuteView要继承View,实现OnGestureListener和OnScaleGestureListener接口。
1、在onMeasure()和onSizeChanged()我们主要获取MainView和ChildView的Width和Height。在两个方法的具体用法在里就不多说了网上一搜一大推。。。。。。。
@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(); }
2、点击手势
// 单击, 触摸屏按下时立刻触发 @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; }
在这个接口回调方法中主要在onSingleTapUp()方法中进行单指点击调用jumpToCJLAndMACL(float downX, float downY)来切换CJL图和MACD图。
在onLongPress()长按回调方法触发calculateSelectedX(float x)来显示弹框。
3、缩放手势
主要在onScale()方法中通过获取detector.getScaleFactor()缩放因子mOldScale ,然后在onScaleEnd()方法中通过和1进行比较来判断放大和缩小。
@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(); } } }
一、MinuteTimeView分时图实现类的讲解
在这个类中主要实现分时图、CJL图和MACD图的计算和绘制。
1、X轴(时间轴)的绘制
"tradeRanges": [ { "start": 1539867600000, "end": 1539882000000 }, { "start": 1539910800000, "end": 1539915300000 }, { "start": 1539916200000, "end": 1539919800000 }, { "start": 1539927000000, "end": 1539932400000 } ],
在时间数组中主要提供一天各时间段的时间,我们首先计算这个时间段的时间总和,然后用Width除以时间总和得到X轴单位变化率。
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(); }
调用getX(int position)方法获取每个点对应X轴的位置。
/** * 根据索引获取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; }
2、成交价和均线
后台返回成交价和均价时,可能当时的值没有抓取到,就以null的形式传给我们。我处理的方式时当遇到null的字段是置为-1,当绘制遇到-1时就跳点绘制。
如图所示,我们把所有的数据存放到数组中。形成一个链表,去点的操作类似于链表的操作。首先我们lastPoint和curPoint同时指向链表的第一个元素。然后进行遍历,当值指向第二和第三个元素里面的元素时里面的值为空,跳过此点。只到指向第四个元素,此时让lastPoint和curPoint同时指向他。
然后lastPoint指向第五个元素此时里面不为空,就连接lastPoint和curPoint画线,然后把lastPoint移动到第六个元素、curPoint移动到第五个元素,后面画法以此类推。
具体代码如下:
//绘制平均线和成交价 if (mPoints.size() > 0) { mPricePaint.setColor(getResources().getColor(R.color.chart_FFFFFF)); IMinuteLine lastPoint = null; float lastX = 0; for (int i = 0; i < mPoints.size(); i++) { if (mPoints.get(i).getLast() != -1) { lastPoint = mPoints.get(i); lastX = getX(i); break; } } for (int i = 0; i < mPoints.size(); i++) { if (mPoints.get(i).getLast() == -1) { continue; } IMinuteLine curPoint = mPoints.get(i); float curX = getX(i); if ((i - lastX / mScaleX) > 1) { //控制的最大个数时断点 lastPoint = curPoint; lastX = curX; } if (lastPoint.getLast() != -1 && curPoint.getLast() != -1) { canvas.drawLine(lastX + mBaseTimePadding - mScaleX / 2, getY(lastPoint.getLast()), curX + mBaseTimePadding - mScaleX / 2, getY(curPoint.getLast()), mPricePaint); //成交价 } if (lastPoint.getAverage() > mValueMin && lastPoint.getAverage() < mValueMax && lastPoint.getAverage() != -1 && curPoint.getAverage() > mValueMin && curPoint.getAverage() < mValueMax && curPoint.getAverage() != -1) { canvas.drawLine(lastX + mBaseTimePadding - mScaleX / 2, getY(lastPoint.getAverage()), curX + mBaseTimePadding - mScaleX / 2, getY(curPoint.getAverage()), mPriceAveragePaint); //均价线 } if (isDrawChildView) { if (isCJL) { if (i == 0) { try { //CJL成交量(柱状图) if (Double.valueOf(curPoint.getUpdown()) > 0) { mVolumePaint.setColor(getResources().getColor(R.color.color_positive_value)); } else if (Double.valueOf(curPoint.getUpdown()) < 0) { mVolumePaint.setColor(getResources().getColor(R.color.color_negative_value)); } else { mVolumePaint.setColor(getResources().getColor(R.color.chart_text)); } } catch (Exception e) { e.printStackTrace(); } } else { try { //CJL成交量(柱状图) if ((curPoint.getLast() - lastPoint.getLast()) > 0) { mVolumePaint.setColor(getResources().getColor(R.color.color_positive_value)); } else if ((curPoint.getLast() - lastPoint.getLast()) < 0) { mVolumePaint.setColor(getResources().getColor(R.color.color_negative_value)); } else { mVolumePaint.setColor(getResources().getColor(R.color.chart_text)); } } catch (Exception e) { e.printStackTrace(); } } 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; } }
3、CJL和MACD切换时,点击区域的判断
首先我们在onSingleTapUp进行点击手势判断,当点击和手指抬起的时间差小于500ms时认为是有效点击。
@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) { //当点击时间小于500ms时,为交互时间 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; }
然后对点击区域的判断,如果在该点击区域内,就触发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(); } } }
根据isCJL进行切换判断:
if (isDrawChildView) { //GJL if (isCJL) { drawCJL(canvas); } else { drawMACD(canvas); } }
4、内存回收
当Bitmap我们使用过后要及时的进行内存回收以免造成内存泄漏。
//释放内存 public void releaseMemory() { if (mBitmapLogo != null) { if (!mBitmapLogo.isRecycled()) { mBitmapLogo.recycle(); mBitmapLogo = null; } } }
作者:涛涛123759
链接:https://www.jianshu.com/p/96853687c6aa