<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          花花綠綠的股票線是怎么畫出來的?想怎么畫就怎么畫!

          共 9749字,需瀏覽 20分鐘

           ·

          2020-10-01 21:45

          ? ? ?

          ? ?正文? ?


          股票??數(shù)字貨幣??都是浮云,沒那智商還是好好擼代碼吧!今天作為一個嫩綠嫩綠的韭菜,就來用技術(shù)征服一下割過自己的股票行情圖。


          股票行情圖中比較復雜的應該當屬于蠟燭線(陰陽線),這塊手勢處理復雜、圖表指標復雜、交互復雜、數(shù)據(jù)處理復雜......總之:復雜!


          所以就從今天開始我從0到1打造出這個復雜的行情圖!費話不多說,上圖!上鏈接:




          github地址:

          github.com/SlamDunk007/StockChart


          /? ?繪制流程? ?/


          整個繪制過程完全自定義View不依賴任何第三方繪制工具,大概分為三個部分:具體的繪制過程、手勢的處理、數(shù)據(jù)的處理。下面就從這三個方面逐個進行講解。


          具體繪制過程


          這里使用的是Android的canvas進行繪制的,android的canvas真的是特別的強大,為了調(diào)高繪制效率,我在這里的繪制進行了修改:提前創(chuàng)建一個Canvas和Bitmap,然后在子線程當中進行繪制:


          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;
          ??????}
          ????}
          ??}


          接下來采用雙緩沖的繪圖機制,先在子線程當中將所有的圖像都繪制到一個Bitmap對象上,然后一次性將內(nèi)存中的Bitmap繪制到屏幕,提高繪制的效率。Android中View的onDraw()方法已經(jīng)實現(xiàn)了這一層緩沖。onDraw()方法中不是繪制一點顯示一點,而是全部繪制完之后一次性顯示到屏幕。


          /**
          ???*?進行具體的繪制
          ???*/

          ??class?DoubleBuffering?implements?Runnable?{

          ????private?final?WeakReference?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);
          ??????????}
          ????????}
          ??????}
          ????}
          ??}


          然后將我們繪制完成的bitmap對象交給View的onDraw()方法的canvas去繪制


          @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);
          ??????}
          ????}
          ??}


          這是整個繪制流程的關(guān)鍵代碼,和平時的自定義繪制沒有什么特殊的區(qū)別,只不過這里采用了雙緩沖的繪圖機制。提前繪制到一個Bitmap上去。


          我做過一個簡單的測試,當繪制的視圖比較復雜的時候,如果提前進行繪制,打開開發(fā)者的呈現(xiàn)模式,可以發(fā)現(xiàn)越復雜的視圖,對GPU的消耗減少的越明顯,這里大家可以寫一個demo簡單測試一下,這里不再贅述。


          蠟燭線、長按十字線和長按彈框的具體繪制


          長按手勢的識別方法可以繼續(xù)參考下面的手勢的處理部分。


          蠟燭線:股票的蠟燭線有高、開、低、收四個參數(shù),分別代表:最高價、開盤價、最低價、收盤價。這里首先計算出最高價當中的最大值和最低價當中的最小值,然后根據(jù)(maxPrice<最高價> - openPrice<開盤價>)/diffPrice<最高價-最低價>,計算出蠟燭線的上影線,下影線,開盤價,收盤價的占比。從而就能計算出在繪制區(qū)域的具體位置。


          //?計算蠟燭線
          ?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;


          長按十字線和彈框:這個是根據(jù)長按的動作然后在右上角的位置,獲取最后一天的高開低收等數(shù)據(jù),最后重新繪制當前屏幕。


          //?繪制長按十字線
          ????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);


          手勢的處理


          代碼當中的ChartTouchHelper是處理手勢的關(guān)鍵類,目前行情圖的手勢有幾種:左右滑動DRAG、慣性滑動FLING、放大縮小Scale、長按LONG_PRESS。


          這里使用了android當中的GestureDetectorCompat結(jié)合onTouch(View v, MotionEvent event)來處理這幾種手勢。


          左右滑動DRAG


          實現(xiàn)OnGestureListener接口,有一個onScroll的方法,在這里將X軸移動的距離當做偏移量,一屏默認顯示的蠟燭線是60個,根據(jù)偏移量可以計算出移動了多少個蠟燭線,然后就能根據(jù)這個去計算下一次繪制的起始點的位置,重新計算滑動后的屏幕的數(shù)據(jù)。最后Invalidate一下,重新進行繪制即可。


          /**
          ???*?@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;
          ????}
          ??}


          慣性滑動FLING


          當手指快速滑動離開的那一瞬間,有一個初始速度。通過SensorManager計算出加速度,根據(jù)公式a=V^2/2S(加速度等于最大速度的平方除以2倍的路程),可以反推出S=V^2/2a,計算出加速度減為0的時候,總共Fling的距離。這里默認是勻減速運動,然后使用手指離開時的速度/加速度=總共耗時duration,最后就可以根據(jù)上面這些數(shù)據(jù)計算出每時間內(nèi)移動的距離,把這個距離當做偏移量去計算我們的數(shù)據(jù)起始位置,重新繪制即可。


          /**
          ???*?@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)?{
          ??????//?根據(jù)加速度計算速度減少到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);
          ????}
          ??}


          放大縮小SCALE


          放大縮小的處理稍微就簡單了一些,這里監(jiān)聽MotionEvent.ACTION_POINTER_DOWN這個手勢,這個手勢處理的就是多指按下的情況,根據(jù)多指的按下位置和縮放之后的位置計算出一個縮放比出來。然后動態(tài)的去更改一屏默認顯示的蠟燭線個數(shù),并且更改繪制的起始位置,刷新即可。


          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;


          根據(jù)移動后的位置計算縮放比


          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);
          ????????????????}
          ??????????????}
          ????????????}
          ??????????}
          ????????}


          長按LONG_PRESS


          長按的處理是簡單的,直接實現(xiàn)接口中的onLongPress方法即可知道當前長按的位置。然后根據(jù)長按動作去處理十字線以及長按的彈框等


          @Override
          ??public?void?onLongPress(MotionEvent?e)?{
          ????mTouchMode?=?LONG_PRESS;
          ????if?(mChartGestureListener?!=?null)?{
          ??????mChartGestureListener.onChartLongPressed(e);
          ????}
          ??}


          數(shù)據(jù)的處理


          使用ChartDataSourceHelper和TechParamsHelper(相關(guān)技術(shù)指標的計算),根據(jù)上面手勢移動的偏移量、縮放比進行數(shù)據(jù)的重組,這塊可以直接參考源碼閱讀即可,沒有什么特別復雜的地方。


          根據(jù)初始位置計算初始化數(shù)據(jù)


          /**
          ???*?初始化行情圖初始數(shù)據(jù)
          ???*/

          ??public?void?initKDrawData(List?klineList,
          ??????KMasterChartView?kLineChartView,
          ??????KSubChartView?volumeView,?KSubChartView?macdView)
          ?
          {

          ????this.mKList?=?klineList;
          ????this.mKLineChartView?=?kLineChartView;
          ????this.mVolumeView?=?volumeView;
          ????this.mMacdView?=?macdView;

          ????mSubChartData?=?new?SubChartData();

          ????//?K線首次當前屏初始位置
          ????startIndex?=?Math.max(0,?klineList.size()?-?K_D_COLUMNS);
          ????//?k線首次當前屏結(jié)束位置
          ????endIndex?=?klineList.size()?-?1;
          ????//?計算技術(shù)指標
          ????mTechParamsHelper.caculateTechParams(klineList,?TechParamType.BOLL);
          ????mTechParamsHelper.caculateTechParams(klineList,?TechParamType.MACD);
          ????initKMoveDrawData(0,?SourceType.INIT);
          ??}


          當橫向滑動、Fling慣性滑動和縮放之后,重新計算初始位置和當前屏幕的蠟燭線等


          /**
          ???*?根據(jù)移動偏移量計算行情圖當前屏數(shù)據(jù)
          ???*
          ???*?@param?distance?手指橫向移動距離
          ???*/

          ??public?void?initKMoveDrawData(float?distance,?SourceType?sourceType)?{

          ????//?重置默認值
          ????resetDefaultValue();

          ????//?計算當前屏幕開始和結(jié)束的位置
          ????countStartEndPos(distance,?sourceType);

          ????//?計算蠟燭線價格最大最小值,成交量最大值
          ????ExtremeValue?extremeValue?=?countMaxMinValue();

          ????//?最大值最小值差值
          ????float?diffPrice?=?maxPrice?-?minPrice;

          ????//?MACD最大最小值
          ????float?diffMacd?=?maxMacd?-?minMacd;

          ????float?diffBoll?=?maxBoll?-?minBoll;

          ????RectF?contentRect?=?mKLineChartView.getViewPortHandler().mContentRect;

          ????//?計算當前屏幕每一個蠟燭線的位置和漲跌情況
          ????for?(int?i?=?startIndex,?k?=?0;?i???????KLineItem?kLineItem?=?mKList.get(i);
          ??????//?開盤價
          ??????float?open?=?kLineItem.open;
          ??????//?最低價
          ??????float?close?=?kLineItem.close;
          ??????//?最高價
          ??????float?high?=?kLineItem.high;
          ??????//?最低價
          ??????float?low?=?kLineItem.low;

          ??????KLineToDrawItem?drawItem?=?new?KLineToDrawItem();

          ??????//?計算蠟燭線
          ??????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;

          ??????//?計算紅漲綠跌,暫時這么計算(其實紅漲綠跌是根據(jù)當前開盤價和前一天的收盤價做對比)
          ??????if?(i?-?1?>=?0)?{
          ????????KLineItem?preItem?=?mKList.get(i?-?1);
          ????????if?(kLineItem.open?>?preItem.close)?{
          ??????????drawItem.isFall?=?false;
          ????????}?else?{
          ??????????drawItem.isFall?=?true;
          ????????}
          ????????if?(preItem.close?!=?0)?{
          ??????????kLineItem.preClose?=?preItem.close;
          ????????}?else?{
          ??????????kLineItem.preClose?=?kLineItem.open;
          ????????}
          ??????}

          ??????//?計算每一個月的第一個交易日
          ??????if?(i?-?1?>=?0?&&?i?+?1?????????int?currentMonth?=?DateUtils.getMonth(kLineItem.day);
          ????????KLineItem?preItem?=?mKList.get(i?-?1);
          ????????int?preMonth?=?DateUtils.getMonth(preItem.day);
          ????????if?(currentMonth?!=?preMonth)?{
          ??????????drawItem.date?=?kLineItem.day.substring(0,?10);
          ????????}
          ??????}

          ??????//?計算成交量
          ??????if?(Math.abs(maxVolume)?>?DataUtils.EPSILON)?{
          ????????RectF?volumeRct?=?mVolumeView.getViewPortHandler().mContentRect;
          ????????float?scaleVolume?=?(maxVolume?-?kLineItem.volume)?/?maxVolume;
          ????????drawItem.volumeRect?=?getRect(volumeRct,?k,?scaleVolume,?1);
          ??????}

          ??????//?計算BOLL
          ??????caculateBollPath(diffBoll,?contentRect,?i,?k,?drawItem);

          ??????//?計算附圖MACD?Path
          ??????caculateMacdPath(diffMacd,?i,?k,?drawItem.isFall);

          ??????drawItem.klineItem?=?kLineItem;
          ??????kLineItems.add(drawItem);
          ????}

          ????List?resultList?=?new?ArrayList<>();
          ????//?數(shù)據(jù)準備完畢
          ????if?(mReadyListener?!=?null)?{
          ??????resultList.addAll(kLineItems);
          ??????mReadyListener.onReady(resultList,?extremeValue,?mSubChartData);
          ????}
          ??}


          ? ??/ ? 總結(jié)? ?/


          目前市面上有很多的自定義圖表,但是能將行情圖以及各項指標完全復用的基本上沒有,比較牛逼的就是MPChart基本上能夠滿足大部分的圖表使用,但是對行情圖來說還是遠遠不夠。所以出于興趣,就模仿火幣和炒股軟件進行了一個自定義蠟燭線,由于不是專業(yè)人士,可能有的金融指標有一些偏差,這里明白繪制技術(shù)即可,不必關(guān)心這些金融細節(jié)。


          規(guī)劃(項目會繼續(xù)完善更新):


          1. 后面會繼續(xù)豐富圖標的各項指標
          2. 數(shù)據(jù)層要進行整理,目前有些地方處理不是特別高效
          3. 實現(xiàn)各種圖表動態(tài)添加、切換等。

          github地址
          github.com/SlamDunk007/StockChart


          源:https://me.csdn.net/kemeng7758

          版權(quán)申明:內(nèi)容來源網(wǎng)絡,版權(quán)歸原創(chuàng)者所有。除非無法確認,我們都會標明作者及出處,如有侵權(quán)煩請告知,我們會立即刪除并表示歉意。謝謝!





          感謝閱讀



          瀏覽 36
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  性福利导航 | 久久夜天天天天 | 操骚逼自拍| 一级AAAAAA毛片免费 | 97国产成人 |