股票??数字货币??都是浮云,没那智商还是好好撸代码吧,啊哈哈哈!今天作为一个嫩绿嫩绿的韭菜,就来用技术征服一下割过自己的股票行情图。 股票行情图中比较复杂的应该当属于蜡烛线(阴阳线),这块手势处理复杂、图表指标复杂、交互复杂、数据处理复杂……总之:复杂! 所以就从今天开始我从0到1打造出这个复杂的行情图!费话不多说,上图!上链接: https://github.com/SlamDunk007/StockChart 整个绘制过程完全自定义View不依赖任何第三方绘制工具,大概分为三个部分:具体的绘制过程、手势的处理、数据的处理。下面就从这三个方面逐个进行讲解。 (1)这里使用的是Android的canvas进行绘制的,android的canvas真的是特别的强大,为了调高绘制效率,我在这里的绘制进行了修改:提前创建一个Canvas和Bitmap: 接下来采用双缓冲的绘图机制,采用先在内存中将所有的图像都绘制到一个Bitmap对象上,然后一次性将内存中的Bitmap绘制到屏幕,提高绘制的效率。Android中View的onDraw()方法已经实现了这一层缓冲。onDraw()方法中不是绘制一点显示一点,而是全部绘制完之后一次性显示到屏幕。 然后将我们绘制完成的bitmap对象交给View的onDraw()方法的canvas去绘制 这是整个绘制流程的关键代码,和平时的自定义绘制没有什么特殊的区别,只不过这里采用了双缓冲的绘图机制。提前绘制到一个Bitmap上去。 我做过一个简单的测试,当绘制的视图比较复杂的时候,如果提前进行绘制,打开开发者的呈现模式,可以发现越复杂的视图,对GPU的消耗减少的越明显,这里大家可以写一个demo简单测试一下,这里不再赘述。 (2)蜡烛线、长按十字线和长按弹框的具体绘制 长按手势的识别方法可以继续参考下面的手势的处理部分。 蜡烛线:股票的蜡烛线有高、开、低、收四个参数,分别代表:最高价、开盘价、最低价、收盘价。这里首先计算出最高价当中的最大值和最低价当中的最小值,然后根据(maxPrice<最高价> – openPrice<开盘价>)/diffPrice<最高价-最低价>,计算出蜡烛线的上影线,下影线,开盘价,收盘价的占比。从而就能计算出在绘制区域的具体位置。 长按十字线和弹框:这个是根据长按的动作然后在右上角的位置,获取最后一天的高开低收等数据,最后重新绘制当前屏幕。 代码当中的ChartTouchHelper是处理手势的关键类,目前行情图的手势有几种:左右滑动DRAG、惯性滑动FLING、放大缩小Scale、长按LONG_PRESS。 这里使用了android当中的GestureDetectorCompat结合onTouch(View v, MotionEvent event)来处理这几种手势。 (1)左右滑动DRAG 实现OnGestureListener接口,有一个onScroll的方法,在这里将X轴移动的距离当做偏移量,一屏默认显示的蜡烛线是60个,根据偏移量可以计算出移动了多少个蜡烛线,然后就能根据这个去计算下一次绘制的起始点的位置,重新计算滑动后的屏幕的数据。最后Invalidate一下,重新进行绘制即可。 (2)惯性滑动FLING 当手指快速滑动离开的那一瞬间,有一个初始速度。通过SensorManager计算出加速度,根据公式a=V^2/2S(加速度等于最大速度的平方除以2倍的路程),可以反推出S=V^2/2a,计算出加速度减为0的时候,总共Fling的距离。这里默认是匀减速运动,然后使用手指离开时的速度/加速度=总共耗时duration,最后就可以根据上面这些数据计算出每时间内移动的距离,把这个距离当做偏移量去计算我们的数据起始位置,重新绘制即可。 (3)放大缩小SCALE 放大缩小的处理稍微就简单了一些,这里监听MotionEvent.ACTION_POINTER_DOWN这个手势,这个手势处理的就是多指按下的情况,根据多指的按下位置和缩放之后的位置计算出一个缩放比出来。然后动态的去更改一屏默认显示的蜡烛线个数,并且更改绘制的起始位置,刷新即可。 根据移动后的位置计算缩放比 (4)长按LONG_PRESS 长按的处理是简单的,直接实现接口中的onLongPress方法即可知道当前长按的位置。然后根据长按动作去处理十字线以及长按的弹框等 使用ChartDataSourceHelper和TechParamsHelper(相关技术指标的计算),根据上面手势移动的偏移量、缩放比进行数据的重组,这块可以直接参考源码阅读即可,没有什么特别复杂的地方。 目前市面上有很多的自定义图表,但是能将行情图以及各项指标完全复用的基本上没有,比较牛逼的就是MPChart基本上能够满足大部分的图表使用,但是对行情图来说还是远远不够。所以出于兴趣,就模仿火币和炒股软件进行了一个自定义蜡烛线,由于不是专业人士,可能有的金融指标有一些偏差,这里明白绘制技术即可,不必关心这些金融细节。 规划(项目会继续完善更新): 1、后面会继续丰富图标的各项指标 2、数据层要进行整理,目前有些地方处理不是特别高效 3、实现各种图表动态添加、切换等。 欢迎大家提出宝贵意见!!! 再次附上源码地址:一、效果图 
 
     
   
二、绘制流程
1、具体绘制过程
 
private void initCanvas() {     repeatNum = 0;     if (mRealCanvas == null) {       mRealCanvas = new Canvas();        Bitmap curBitmap =           createBitmap(mViewPortHandler.getChartWidth(), mViewPortHandler.getChartHeight(),               Bitmap.Config.ARGB_8888);       Bitmap alterBitmap = curBitmap.copy(Bitmap.Config.ARGB_8888, true);       if (curBitmap != null && alterBitmap != null) {         mRealCanvas.setBitmap(curBitmap);         mCurBitmap = curBitmap;         mAlterBitmap = alterBitmap;       }     }   } 
/**    * 进行具体的绘制    */   class DoubleBuffering implements Runnable {      private final WeakReference<BaseChartView> mChartView;      public DoubleBuffering(BaseChartView view) {       mChartView = new WeakReference<>(view);     }      @Override     public synchronized void run() {       if (mChartView != null) {         BaseChartView baseChartView = mChartView.get();         if (baseChartView != null && baseChartView.mRealCanvas != null) {           baseChartView.drawFrame(baseChartView.mRealCanvas);                    Bitmap bitmap = baseChartView.mCurBitmap;           if (bitmap != null && baseChartView.mHandler != null) {             baseChartView.mHandler.sendEmptyMessage(baseChartView.REFRESH);           }         }       }     }   } 
@Override   protected void onDraw(Canvas canvas) {     super.onDraw(canvas);     if (mRealBitmap != null) {       canvas.drawBitmap(mRealBitmap, 0, 0, mPaint);     }     if (hasDrawed) {       hasDrawed = false;       if (!mHandler.hasMessages(START_PAINT)) {         Message message = new Message();         message.what = START_PAINT;         message.obj = mDoubleBuffering;         mHandler.sendMessageDelayed(message, 25);       }     }   } 
 // 计算蜡烛线  float scaleY_open = (maxPrice - open) / diffPrice;  float scaleY_low = (maxPrice - close) / diffPrice;  RectF candleRect = getRect(contentRect, k, scaleY_open, scaleY_low);  drawItem.rect = candleRect;  // 计算上影线,下影线  float scale_HL_T = (maxPrice - high) / diffPrice;  float scale_HL_B = (maxPrice - low) / diffPrice;  RectF shadowRect = getLine(contentRect, k, scale_HL_T, scale_HL_B);  drawItem.shadowRect = shadowRect; 
// 绘制长按十字线     if (mFocusPoint != null && onLongPress) {       if (contentRect.contains(mFocusPoint.x, mFocusPoint.y)) {         canvas.drawLine(contentRect.left, mFocusPoint.y, contentRect.right, mFocusPoint.y,             PaintUtils.FOCUS_LINE_PAINT);       }       canvas.drawLine(mFocusPoint.x, contentRect.top, mFocusPoint.x, contentRect.bottom,           PaintUtils.FOCUS_LINE_PAINT);       KLineToDrawItem item = mToDrawList.get(mFocusIndex);       drawBollDes(canvas, contentRect, item);     }      // 长按显示的弹框     showLongPressDialog(canvas, contentRect);2、手势的处理
 
/**    * @param e1 down的时候event    * @param e2 move的时候event    * @param distanceX x轴移动距离:两个move之间差值    * @param distanceY y轴移动距离    */   @Override   public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {      if (mChartGestureListener != null) {       scrollX -= distanceX;       // 当X轴移动距离大于18px认为是移动       if (Math.abs(scrollX) > mXMoveDist) {         mChartGestureListener.onChartTranslate(e2, scrollX);         scrollX = 0;       }     }     if (Math.abs(distanceX) > Math.abs(distanceY)) {       return true;     } else {       return false;     }   } 
/**    * @param e1 手指按下的位置    * @param e2 手指抬起的位置    * @param velocityX 手指抬起时的x轴的加速度  px/s    */   @Override   public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {     mLastGesture = ChartGesture.FLING;     fling(velocityX, e2.getX() - e1.getX());     return true;   }    private void fling(float velocity, float offset) {     stopFling();     if (Math.abs(mDeceleration) > DataUtils.EPSILON) {       // 根据加速度计算速度减少到0时的时间       int duration = (int) (1000 * velocity / mDeceleration);       // 手指抬起时,缓冲的距离       int totalDistance = (int) ((velocity * velocity) / (mDeceleration + mDeceleration));       int startX = (int) offset, flingX;       if (velocity < 0) {         flingX = startX - totalDistance;       } else {         flingX = startX + totalDistance;       }       mFlingRunnable = new FlingRunnable(startX, flingX, duration, mHandler, mChartGestureListener);       mHandler.post(mFlingRunnable);     }   } 
case MotionEvent.ACTION_POINTER_DOWN:         if (event.getPointerCount() >= 2) {           saveTouchStart(event);           // 两个手指之间在X轴的距离           mSavedXDist = getXDist(event);           // 两个手指之间的距离           mSavedDist = spacing(event);           // 两个手指之间距离大于10才认为是缩放           if (mSavedDist > 10f) {             mTouchMode = X_ZOOM;           }           // 计算两个手指之间的中点位置           midPoint(mTouchPointCenter, event);         }         break; 
case MotionEvent.ACTION_MOVE:         if (mTouchMode == DRAG) {           mLastGesture = ChartGesture.DRAG;         } else if (mTouchMode == X_ZOOM) {           if (event.getPointerCount() >= 2) {              // 手指移动的距离             float totalDist = spacing(event);              if (totalDist > mMinScalePointerDistance) {               if (mTouchMode == X_ZOOM) {                 mLastGesture = ChartGesture.X_ZOOM;                 float xDist = getXDist(event);                 float scaleX = xDist / mSavedXDist;                 if (mChartGestureListener != null) {                   mChartGestureListener.onChartScale(event, scaleX, 1);                 }               }             }           }         } 
@Override   public void onLongPress(MotionEvent e) {     mTouchMode = LONG_PRESS;     if (mChartGestureListener != null) {       mChartGestureListener.onChartLongPressed(e);     }   }3、数据的处理
三、总结
本网页所有视频内容由 imoviebox边看边下-网页视频下载, iurlBox网页地址收藏管理器 下载并得到。
ImovieBox网页视频下载器 下载地址: ImovieBox网页视频下载器-最新版本下载
本文章由: imapbox邮箱云存储,邮箱网盘,ImageBox 图片批量下载器,网页图片批量下载专家,网页图片批量下载器,获取到文章图片,imoviebox网页视频批量下载器,下载视频内容,为您提供.
阅读和此文章类似的: 全球云计算
 官方软件产品操作指南 (170)
官方软件产品操作指南 (170)