Android原生股票圖-分時(shí)圖講解和繪制(一)

Android原生股票圖-分時(shí)圖講解和繪制(一)
Android原生股票圖-分時(shí)圖講解和繪制(二)
Android原生股票圖-K線圖講解和繪制(一)

一、試圖的展示效

效果一

效果二

二、試圖簡(jiǎn)介**

  • 效果圖中分為兩部分:
    1、主視圖,主要展示分時(shí)圖數(shù)據(jù);
    2、子試圖,主要展示CJL圖和MACD圖,點(diǎn)擊子試圖時(shí)兩者進(jìn)行切換。
  • 分時(shí)圖、CJL圖和MACD圖簡(jiǎn)介:
    1、分時(shí)圖
    分時(shí)圖只要是 均線和成交價(jià)的折線,左軸時(shí)成交價(jià)中點(diǎn)為昨日成交量,右軸成交價(jià)的漲跌幅;
    2、CJL圖
    左軸為成交量主要展示柱體的變化,柱體的類(lèi)型見(jiàn)圖:
    CJL 柱體類(lèi)型

    右軸為持倉(cāng)量主要展示折線圖。
    3、MACD圖
    如MACD(12,26,9)主要展示DIFF的值、DEA的值和STICK的值;計(jì)算公式如下:
    DIFF白線:
    DIFF=當(dāng)前周期EMA(12)-當(dāng)前周期EMA(26)
    EMA(12)=當(dāng)前周期EMA(12)×11/13+當(dāng)前周期收盤(pán)價(jià)×2/13
    EMA(26)=當(dāng)前周期EMA(26)×25/27+當(dāng)前周期收盤(pán)價(jià)×2/27
    DEA黃線
    當(dāng)前周期DEA=前一周期DEA×8/10+當(dāng)前周期DIF×2/10。
    STICK(MACD柱)
    STICK=當(dāng)前周期(DIF-DEA)×2
    Y軸刻度生成規(guī)則:
    中軸為0,取圖內(nèi)DIFF、DEA、STICK三項(xiàng)指標(biāo)最高絕對(duì)值時(shí)點(diǎn)值前兩位向上取5的最小倍數(shù),剩下位數(shù)以0填充,后將該值2等分并分別在中軸上下標(biāo)識(shí)刻度。例如當(dāng)前圖內(nèi)最高時(shí)點(diǎn)值為-1623,則前兩位16向上取2的最小倍數(shù)為20,取值為2000,2等分后上下刻度分別為2000、1000、0、-1000、-2000。
    柱體類(lèi)型:
    MACD 柱體類(lèi)型

三、代碼**

  • MACD的計(jì)算代碼 ,主要根據(jù)closePrice生成DIF、DEA和STICK
/**
     * 計(jì)算macd
     *
     * @param datas
     */
    public static void calculateMACD(List<Minute> 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 + 今日收盤(pán)價(jià) X 2/13
//                EMA(26) = 前一日EMA(26) X 25/27 + 今日收盤(pán)價(jià) 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;
        }

    }
  • 子試圖點(diǎn)擊區(qū)域的處理,根據(jù)點(diǎn)擊來(lái)卻換CJL和MACD

    //CJL和MACL之間的切換
    @Override
    protected void jumpToCJLAndMACL(float downX, float downY) {
        //點(diǎn)擊子試圖判斷
        if (downX > 0 && downX < mWidth) {
            if (downY > mMainHeight + mVolumeTextHeight && downY < mMainHeight + mVolumeTextHeight + mVolumeHeight) {
                Log.d("--->", "x = " + downX + ";" + "y = " + downY);
                isCJL = !isCJL;
                invalidate();
            }

        }

        //點(diǎn)擊文字判斷
        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);
        }
    }
  • 分時(shí)圖基類(lèi)
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; //據(jù)頂部
    protected int mBottomPadding = 15;//距底部
    //左右padding,允許修改
    protected int mBasePaddingLeft = 50;
    protected int mBasePaddingRight = 50;
    protected float mBaseTextPaddingLeft = 15; //字體據(jù)左側(cè)的距離
    protected float mBaseTextPaddingRight = 15;//字體據(jù)右側(cè)的距離
    protected int mBaseTimePadding = 5; //下方時(shí)間文字

    protected int GridColumns = 5; //列數(shù)
    protected int mGridRows = 6; //主試圖的行數(shù)
    protected int mGridChildRows = 4; //子試圖的行數(shù)
    protected float mPointWidth; //柱子單位量

    protected boolean isLongPress = false; //是否長(zhǎng)按事件
    protected boolean isClosePress = true; //關(guān)閉長(zhǎng)按時(shí)間

    protected boolean mScaleEnable = false; //是否可以縮放
    protected GestureDetectorCompat mDetector;
    protected ScaleGestureDetector mScaleDetector;

    protected boolean isDrawChildView = true; //是否子試圖點(diǎn)擊事件


    protected long mClickTime = 0; //點(diǎn)擊時(shí)間

    protected float mScaleY = 1; //Y軸單位量
    protected float mScaleX = 1; //x軸的單位量
    protected float mOldScale = 1.0f; //用來(lái)判斷當(dāng)前是否縮放
    protected OnScaleGestureListener mOnScaleGestureListener;

    protected long mTotalTime; //總時(shí)間

    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; //點(diǎn)的個(gè)數(shù)
    protected float mValueStart;//成交量起始值/昨收價(jià)
    protected final List<IMinuteLine> mPoints = new ArrayList<>();
    protected final List<IMinuteTime> mTimes = new ArrayList<>();
    protected Date mMainStartTime;
    protected Date mMainEndTime;
    protected Date mStartTime;
    protected Date mEndTime;

    protected int mMACDClickPoint; //MACD選擇點(diǎn)
    private Bitmap mBitmapLogo = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.ic_app_logo);

    private IValueFormatter mVolumeFormatter;
    protected int mIndex = 0; //天數(shù)索引

    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")); //網(wǎng)格線顏色
        mGridPaint.setStrokeWidth(dp2px(1));

        mVolumeFormatter = new BigValueFormatter();

    }


    /**
     * @param data          數(shù)據(jù)源
     * @param startTime     顯示的開(kāi)始時(shí)間
     * @param endTime       顯示的結(jié)束時(shí)間
     * @param times         休息開(kāi)始時(shí)間
     * @param yesClosePrice 昨收價(jià)
     */
    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("開(kāi)始時(shí)間不能大于結(jié)束時(shí)間");

        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); //設(shè)置開(kāi)始的值(昨日成交量)

        if (data != null) {
            mPoints.clear();
            this.mPoints.addAll(data);
            mPointCount = mPoints.size();
            mMACDClickPoint = mPoints.size() - 1;
        }
        notifyChanged();
    }


    /**
     * 點(diǎn)擊, 處理長(zhǎng)安時(shí)間
     *
     * @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:
                //一個(gè)點(diǎn)的時(shí)候滑動(dòng)
                if (event.getPointerCount() == 1) {
                    //長(zhǎng)按之后移動(dòng)
                    if (isLongPress || !isClosePress) {
                        calculateSelectedX(event.getX());
                        invalidate();
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                if (!isClosePress) {
                    isLongPress = false;
                }
                mMACDClickPoint = mPoints.size() - 1;

//                if (isDrawChildView && isClickPress) {
//                    //當(dāng)點(diǎn)擊時(shí)間小于2000ms時(shí),為交互時(shí)間
//                    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); //繪制網(wǎng)格
        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);
        }
    }


    //繪制網(wǎng)格線
    private void drawGird(Canvas canvas) {
        //先畫(huà)出坐標(biāo)軸
        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);
            }
        }

    }


    /*********************************************抽象方法用于實(shí)現(xiàn)繼承類(lèi)**************************************/

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

    /**
     * 設(shè)置開(kāi)始的值 對(duì)稱(chēng)軸線
     */
    protected void setValueStart(float valueStart) {
        this.mValueStart = valueStart;
    }

    /*********************************************可設(shè)置參數(shù)**************************************/

    /**
     * 設(shè)置表格行數(shù)
     */
    public void setGridRows(int gridRows) {
        if (gridRows < 1) {
            gridRows = 1;
        }
        mGridRows = gridRows;
    }

    /**
     * 設(shè)置子試圖表格行數(shù)
     */
    public void setGridChildRows(int gridRows) {
        if (gridRows < 1) {
            gridRows = 1;
        }
        mGridChildRows = gridRows;
    }

    /**
     * 設(shè)置表格列數(shù)
     */
    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高度
        }
    }

    //設(shè)置子試圖高度
    public void setVolumeHeight(int height) {
        mVolumeHeight = dp2px(height);
    }

    //設(shè)置CJL高度
    public void setVolumeTextHeight(int height) {
        mVolumeTextHeight = dp2px(height);
    }

    //設(shè)置主試圖高度
    public void setMainHeight(int height) {
        mMainHeight = dp2px(height);
    }

    /**
     * 獲取最大能有多少個(gè)點(diǎn)
     */
    public long getMaxPointCount(int count) {
        return mTotalTime / ONE_MINUTE;
    }

    /**
     * 添加一個(gè)點(diǎn)
     */
    public void addPoint(IMinuteLine point) {
        mPoints.add(point);
        notifyChanged();
    }

    /**
     * 修改某個(gè)點(diǎn)的值
     *
     * @param position 索引值
     */
    public void changePoint(int position, IMinuteLine point) {
        mPoints.set(position, point);
        notifyChanged();
    }


    /**
     * 根據(jù)索引獲取點(diǎn)
     */
    public IMinuteLine getItem(int position) {
        return mPoints.get(position);
    }

    /**
     * 獲取點(diǎn)的個(gè)數(shù)
     */
    private int getItemSize() {
        return mPoints.size();
    }

    /**
     * 刷新最后一個(gè)點(diǎn)
     */
    public void refreshLastPoint(IMinuteLine point) {
        changePoint(getItemSize() - 1, point);
    }

    //是否可以縮放
    public void setScaleEnable(boolean scaleEnable) {
        this.mScaleEnable = scaleEnable;
    }


    /**
     * 設(shè)置成交量格式化器
     *
     * @param volumeFormatter {@link IValueFormatter} 成交量格式化器
     */
    public void setVolumeFormatter(IValueFormatter volumeFormatter) {
        mVolumeFormatter = volumeFormatter;
    }


    //設(shè)置時(shí)間單位; 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;
    }

    /******************************長(zhǎng)按,點(diǎn)擊手勢(shì)*****************************************/

    // 單擊, 觸摸屏按下時(shí)立刻觸發(fā)
    @Override
    public boolean onDown(MotionEvent e) {
        Log.i("--->", "onDown");
        return false;
    }

    //短按, 觸摸屏按下后片刻后抬起,會(huì)觸發(fā)這個(gè)手勢(shì),如果迅速抬起則不會(huì)
    @Override
    public void onShowPress(MotionEvent e) {
    }

    //抬起, 手指離開(kāi)觸摸屏?xí)r觸發(fā)(長(zhǎng)按、滾動(dòng)、滑動(dòng)時(shí),不會(huì)觸發(fā)這個(gè)手勢(shì))
    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        switch (e.getAction()) {
            case MotionEvent.ACTION_DOWN:
                Log.i("--->", "ACTION_DOWN");
                break;
            case MotionEvent.ACTION_UP: //雙指點(diǎn)擊時(shí)不會(huì)觸發(fā)
                Log.i("--->", "ACTION_UP");
                if (isClosePress) {
                    if (isDrawChildView) {
                        //當(dāng)點(diǎn)擊時(shí)間小于2000ms時(shí),為交互時(shí)間
                        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;
    }

    //滾動(dòng), 觸摸屏按下后移動(dòng)
    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        return false;
    }

    //長(zhǎng)按, 觸摸屏按下后既不抬起也不移動(dòng),過(guò)一段時(shí)間后觸發(fā)
    @Override
    public void onLongPress(MotionEvent e) {
        isLongPress = true;
        isClosePress = false;
        calculateSelectedX(e.getX());
        invalidate();
    }

    //滑動(dòng), 觸摸屏按下后快速移動(dòng)并抬起,會(huì)先觸發(fā)滾動(dòng)手勢(shì),跟著觸發(fā)一個(gè)滑動(dòng)手勢(shì)
    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        return false;
    }


    /******************************縮放手勢(shì)*****************************************/
    @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 觸摸手勢(shì):主要是用來(lái)判斷單指點(diǎn)擊屏幕和雙指點(diǎn)擊屏幕從而來(lái)區(qū)分點(diǎn)擊切換子試圖還是進(jìn)行縮放操作。
**ScaleGestureDetector **主要根據(jù)onScale()、onScaleBegin()和onScaleEnd()這三個(gè)方法來(lái)判斷試圖是放大還是縮小。

    @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();

            }
        }

    }
  • 分時(shí)圖重點(diǎn)實(shí)現(xiàn)試圖展示效果:
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; //持倉(cāng)量
    private float mVolumeMax = 0; //最大成交量
    private float mVolumeMin = 0; //最小成交量
    private float mInterestMax = 0; //最大持倉(cāng)量
    private float mInterestMin = 0; //最小持倉(cāng)量

    private float mMACDValueY = 0;//MACD Y軸

    private long mCount = 0;

    private float mVolumeTimeScaleY; //各時(shí)間點(diǎn)成交量縮放比
    private float mInterestTimeScaleY; //各時(shí)間點(diǎn)持倉(cāng)量縮放比
    private float mMACDLineScaleY; //MACD指標(biāo)線的縮放量
    private float mTextSize = 10;

    private int selectedIndex;
    private boolean isCJL = true; //是否為CJL圖,默認(rèn)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;
        }
    }


    /**
     * 根據(jù)索引獲取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;
    }


    /**
     * 當(dāng)數(shù)據(jù)發(fā)生變化時(shí)調(diào)用
     */
    @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(); //持倉(cāng)量
            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;
        }

        //最大值和開(kāi)始值的單位差值
        float offsetValueMax = (Math.abs(mValueMax - mValueStart)) / (mGridRows / 2);
        float offsetValueMin = (Math.abs(mValueStart - mValueMin)) / (mGridRows / 2);

        //以開(kāi)始的點(diǎn)為中點(diǎn)值   上下間隙多出20%
        float offset = (offsetValueMax > offsetValueMin ? offsetValueMax : offsetValueMin) * 1.2f;
        //坐標(biāo)軸高度以開(kāi)始的點(diǎn)對(duì)稱(chēng)
        mValueMax = mValueStart + offset * (mGridRows / 2);
        mValueMin = mValueStart - offset * (mGridRows / 2);
        //y軸的縮放值
        mScaleY = mMainHeight / (mValueMax - mValueMin);

        //判斷最大值和最小值是否一致
        if (mValueMax == mValueMin) {
            //當(dāng)最大值和最小值都相等的時(shí)候 分別增大最大值和 減小最小值
            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);

        //設(shè)置主狀圖的寬度
        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;
        }

        //繪制平均線和成交價(jià)
        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); //成交價(jià)

//                canvas.drawLine(lastX + mBaseTimePadding - mScaleX / 2,
//                        getY(lastPoint.getAverage()),
//                        curX + mBaseTimePadding - mScaleX / 2,
//                        getY(curPoint.getAverage()),
//                        mPriceAveragePaint); //均價(jià)線

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

                        }

                    }
                }

                //給上一個(gè)只賦值
                lastPoint = curPoint;
                lastX = curX;
            }
        }


        drawText(canvas); //繪制文字

        //畫(huà)指示線
        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<String> strings = new ArrayList<>();
        strings.add(DateUtil.getStringDateByLong(point.getDate().getTime(), 8));
        strings.add("價(jià)格");
        strings.add(point.getLast() + "");
        strings.add("均價(jià)");
        strings.add(point.getAverage() + "");

        strings.add("漲跌");
        strings.add(point.getUpdown());
        strings.add(point.getPercent());

        strings.add("持倉(cāng)量");
        strings.add(point.getInterest() + "");
        strings.add(point.getChgInterest());

        strings.add("成交量");
        strings.add(point.getVolume() + "");
        strings.add(point.getChgVolume());//成交量變化

//        strings.add("成交價(jià)");
//        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 的指標(biāo)
        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;
        //畫(huà)左邊的值
        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);

            }
        }

        //畫(huà)右邊的值
        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);
            }
        }


        //畫(huà)時(shí)間
        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); //起始時(shí)間

            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); //中間時(shí)間

            }
            canvas.drawText(DateUtil.getStringDateByLong(mMainEndTime.getTime(), 8),
                    mWidth - mBaseTimePadding - mTextBottomPaint.measureText(DateUtil.getStringDateByLong(mMainEndTime.getTime(), 8)),
                    y, mTextBottomPaint);//結(jié)束時(shí)間

        } else { //大于1天時(shí)
            if (mTimes.size() / 4 == mIndex && mTimes.size() != mIndex) {
                canvas.drawText(DateUtil.getStringDateByLong(mMainStartTime.getTime(), 9), mBaseTimePadding, y, mTextBottomPaint); //起始時(shí)間
                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); //起始時(shí)間
                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")) { // //上證指數(shù) (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")) {  //上證指數(shù) (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); //起始時(shí)間
                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")) {  //上證指數(shù) (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 , 存在問(wèn)題
    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); //存在問(wèn)題??

        //繪制 MACD, DIFF, DEA, STICK 的指標(biāo)
        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); //成交價(jià)

                //給上一個(gè)只賦值
                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); //成交價(jià)

                //給上一個(gè)只賦值
                lastPoint = curPoint;
                lastX = curX;
            }
        }

    }

    //繪制 MACD, DIFF, DEA, STICK 的指標(biāo)
    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);//存在問(wèn)題 ???

        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); //成交價(jià)

                //給上一個(gè)只賦值
                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) {
        //點(diǎn)擊子試圖判斷
        if (downX > 0 && downX < mWidth) {
            if (downY > mMainHeight + mVolumeTextHeight && downY < mMainHeight + mVolumeTextHeight + mVolumeHeight) {
                Log.d("--->", "x = " + downX + ";" + "y = " + downY);
                isCJL = !isCJL;
                invalidate();
            }

        }

        //點(diǎn)擊文字判斷
        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指標(biāo)線
    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居中的問(wèn)題
     */
    public float fixTextY(float y) {
        Paint.FontMetrics fontMetrics = mTextLeftPaint.getFontMetrics();
        return (y + (fontMetrics.descent - fontMetrics.ascent) / 2 - fontMetrics.descent);
    }
}

具體詳解我放在Android原生股票圖-分時(shí)圖講解和繪制(二)中。
源碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容