自定義折線股票顯示圖

先看UI出的設(shè)計圖


19C91932-CC43-4396-9DCA-171456A00E8B.png

這是修改之后的圖了,在之前的途中 選中的點位上面是有一個顯示的價格的,在下面的點位顯示的是日期;
再看自定義view完成的圖

1618543609(1).jpg

這就基本搞定了 只要把繪制點位上面的顯示文本還有下面的顯示文本去掉就ok了,

在之前的畫的時候 當時以為是滑動的是視圖,都已經(jīng)擼完了, 然后項目主管才告訴我說滑動的不是視圖, 是垂直的線, 當時滿臉問號,不是看圖么?
沒辦法 人家既然說了不對,那得改呀,哎!?。?/p>

擼都擼完了, 改起來就方便多了,不多說,看代碼!
先把動態(tài)需要改的列出來,比如顏色、線的寬度,背景色,文本顏色等等...

<declare-styleable name="StockPriceView">
        <!-- 背景顏色 -->
        <attr name="background_color_dbz" format="color" />
        <!-- x、y軸線顏色 -->
        <attr name="x_y_color_dbz" format="color" />
        <!-- x軸上的文本顏色 -->
        <attr name="x_text_color_dbz" format="color" />
        <!-- x軸上的文本顏色 -->
        <attr name="y_text_color_dbz" format="color" />
        <!-- x、y軸上的文本大小 -->
        <attr name="x_text_size_dbz" format="dimension" />
        <!-- y軸上的文本大小 -->
        <attr name="y_text_size_dbz" format="dimension" />
        <!-- 背景虛線顏色 -->
        <attr name="dotted_line_dbz" format="color" />
        <!-- 折線顏色 -->
        <attr name="line_color_dbz" format="color" />
        <!-- 折線內(nèi)陰影顏色 -->
        <attr name="line_shadow_color_dbz" format="color" />
        <!-- 折線點位顏色顏色 -->
        <attr name="point_color_dbz" format="color" />
        <!-- 折線點位直線顏色 -->
        <attr name="line_point_color_dbz" format="color" />
        <!-- 折線寬度 -->
        <attr name="line_width_dbz" format="dimension" />
        <!-- 折線點位文字顏色 -->
        <attr name="point_text_color_dbz" format="color" />
        <!-- 折線點位文字大小 -->
        <attr name="point_text_size_dbz" format="dimension" />
    </declare-styleable>

初始化

    public StockPriceView(Context context) {
        this(context, null);
    }

    public StockPriceView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public StockPriceView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView(context, attrs, defStyleAttr);
        initPaint();
    }

    private void initView(Context context, AttributeSet attrs, int defStyleAttr) {
        final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.StockPriceView, defStyleAttr, 0);
        mBackgroundColor = typedArray.getColor(R.styleable.StockPriceView_background_color_dbz, Color.WHITE);
        mLinePointColor = typedArray.getColor(R.styleable.StockPriceView_line_point_color_dbz, mLinePointColor);
        mLineColor = typedArray.getColor(R.styleable.StockPriceView_line_color_dbz, mLineColor);
        mPointColor = typedArray.getColor(R.styleable.StockPriceView_point_color_dbz, mPointColor);
        mPointTextColor = typedArray.getColor(R.styleable.StockPriceView_point_text_color_dbz, mPointTextColor);
        mXYColor = typedArray.getColor(R.styleable.StockPriceView_x_y_color_dbz, mXYColor);
        mXTextColor = typedArray.getColor(R.styleable.StockPriceView_x_text_color_dbz, mXTextColor);
        mYTextColor = typedArray.getColor(R.styleable.StockPriceView_y_text_color_dbz, mYTextColor);
        mLineShadowColor = typedArray.getColor(R.styleable.StockPriceView_line_shadow_color_dbz, mLineShadowColor);
        mPointTextSize = (int) typedArray.getDimension(R.styleable.StockPriceView_point_text_size_dbz, mPointTextSize);
        mLineWidth = (int) typedArray.getDimension(R.styleable.StockPriceView_line_width_dbz, mLineWidth);
        mXTextSize = (int) typedArray.getDimension(R.styleable.StockPriceView_x_text_size_dbz, mXTextSize);
        mYTextSize = (int) typedArray.getDimension(R.styleable.StockPriceView_y_text_size_dbz, mYTextSize);
        typedArray.recycle();
    }

    private void initPaint() {
        mXPaint = new Paint();
        mXPaint.setAntiAlias(true);
        mXPaint.setColor(mXYColor);
        mXPaint.setStrokeWidth(dpToPx(1));

        mXTextPaint = new Paint();
        mXTextPaint.setAntiAlias(true);
        mXTextPaint.setColor(mXTextColor);
        mXTextPaint.setTextSize(mXTextSize);

        mYTextPaint = new Paint();
        mYTextPaint.setAntiAlias(true);
        mYTextPaint.setColor(mYTextColor);
        mYTextPaint.setTextSize(mYTextSize);

        mDottedPaint = new Paint();
        mDottedPaint.setAntiAlias(true);
        mDottedPaint.setColor(mXYColor);
        mDottedPaint.setStyle(Paint.Style.STROKE);
        mDottedPaint.setStrokeWidth(dpToPx(1));
        // DashPathEffect () 數(shù)組 第一個是線的寬度 第二個數(shù)據(jù)是虛線間隔,
        mDottedPaint.setPathEffect(new DashPathEffect(new float[]{dpToPx(4), dpToPx(2)}, 0));

        mLinePaint = new Paint();
        mLinePaint.setAntiAlias(true);
        mLinePaint.setColor(mLineShadowColor);
        mLinePaint.setStrokeWidth(dpToPx(1));
        mLinePaint.setStyle(Paint.Style.FILL);

        mPointPaint = new Paint();
        mPointPaint.setAntiAlias(true);
        mPointPaint.setColor(mLineColor);
        mPointPaint.setStrokeWidth(mLineWidth);
        mPointPaint.setStyle(Paint.Style.STROKE);

        mLinePointPaint = new Paint();
        mLinePointPaint.setAntiAlias(true);
        mLinePointPaint.setColor(mLinePointColor);
        mLinePointPaint.setStrokeWidth(dpToPx(1));
    }
   /**
     * 計算文本間距,和第一個點的位置  取數(shù)據(jù)最大值
     */
    private void calculation(){
        String text = mYValue.size() == 0 ? "00-00" : mYValue.get(mYValue.size() - 1).value;
        // 測量Y軸數(shù)據(jù)的文本寬度
        yRect = getTextBounds(text, mYTextPaint);
        // 計算X軸的左邊距
        mYLeftInterval = yRect.width() + mXTextLeftInterval * 2;
        // 每個點位的間距 mXValue.size() - 2 減去2 是因為兩邊的點各占一個  不用間距
        mInterval = (mWidth - mYLeftInterval - mXRightInterval) / (mXValue.size() - 2);
        // 第一個X軸點的位置
        mXFirstPoint = mYLeftInterval;
        // 遍歷數(shù)據(jù)最大值 如果為0那么默認為1
        for (int i = 0; i < mXValue.size(); i++) {
            max = Math.max(max, Float.parseFloat(mXValue.get(i).value));
        }
        if (max == 0) {
            max = 1;
        }
    }

開始繪制 在繪制折線圖的時候有一點我也不明白,就是明明已經(jīng)分好間距了,為什么在右邊還會出來一點,如果有知道的麻煩告訴我一下,(多出來的一點我已經(jīng)在繪制X軸和虛線的時候強制多繪制了4dp)

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        calculation();
        canvas.drawColor(mBackgroundColor);
        // 繪制X軸時間文本
        drawBottomText(canvas);
        // 繪制X軸
        drawXLine(canvas);
        // 繪制Y軸文本
        drawYText(canvas);
        // 繪制背景虛線
        drawBackDottedLine(canvas);
        // 繪制陰影折線
        drawLine(canvas);
        // 繪制折線點和提示文本
        drawLinePoint(canvas);
    }

    /**
     * 繪制X軸時間文本
     */
    private void drawBottomText(Canvas canvas) {
        Rect endText = getTextBounds(mXEndText, mXTextPaint);
        canvas.drawText(mXStartText, mXTextLeftInterval, mHeight - mXBottomInterval, mXTextPaint);
        canvas.drawText(mXEndText, mWidth - mXTextLeftInterval - endText.width(), mHeight - mXBottomInterval, mXTextPaint);
    }

    /**
     * 繪制X軸
     */
    private void drawXLine(Canvas canvas) {
        Rect startText = getTextBounds(mXStartText, mXTextPaint);
        // 距離底邊的距離 = 底邊距離 * 2 + X軸文本高度
        int xBottom = mHeight - getPaddingTop() - getPaddingBottom() - mXBottomInterval * 2 - startText.height();
        // mXRightInterval + dpToPx(4) (正常是不用加上的, 但不知道為什么就是少那么一丟丟)
        canvas.drawLine(mYLeftInterval, xBottom, mWidth - mXRightInterval + dpToPx(4), xBottom, mXPaint);
    }

    /**
     * 繪制Y軸文本
     */
    private void drawYText(Canvas canvas) {
        Rect startText = getTextBounds(mXStartText, mXTextPaint);
        for (int i = 0; i < mYValue.size(); i++) {
            // 繪制區(qū)域 = 總高度 - 下邊距 - 上邊距
            // 下邊距 = 文本高度 + 文本距離上下的間距
            // 繪制區(qū)域 / (繪制數(shù)據(jù)的數(shù)量 - 1) (減一是計算繪制之間的間距, 如果不減一, 那么在開始第一個繪制時會多出一段間距)
            float y = (float) (mHeight - getPaddingBottom() - getPaddingTop() - mXBottomInterval * 2 - mYTopInterval - startText.height()) / (mYValue.size() - 1);
            // X軸 文本水平居中X軸    Y軸 從最大值到最小值,反著繪制, Y點從上到下 但要加 上邊距
            // Y軸文本  距離左邊邊距, Y軸文本的寬度 / 2  居中繪制
            canvas.drawText(mYValue.get(mYValue.size() - (i + 1)).value, mXTextLeftInterval, y * i + mYTopInterval, mYTextPaint);
        }
    }

    /**
     * 繪制背景虛線
     */
    private void drawBackDottedLine(Canvas canvas) {
        Rect startText = getTextBounds(mXStartText, mXTextPaint);
        for (int i = 0; i < mYValue.size(); i++) {
            float y = (float) (mHeight - getPaddingBottom() - getPaddingTop() - mXBottomInterval * 2 - mYTopInterval - startText.height()) / (mYValue.size() - 1);
            // x軸距離左邊 = Y軸文本寬度 + Y軸文本左右邊距
            // mXRightInterval + dpToPx(4) (正常是不用加上的, 但不知道為什么就是少那么一丟丟)
            canvas.drawLine(mXTextLeftInterval * 2 + yRect.width(), y * i + mYTopInterval, mWidth - mXRightInterval + dpToPx(4), y * i + mYTopInterval, mDottedPaint);
        }
    }

    /**
     * 繪制陰影折線
     **/
    private void drawLine(Canvas canvas) {
        if (mXValue.size() <= 0) return;
        Rect startText = getTextBounds(mXStartText, mXTextPaint);
        Path path = new Path();
        // 繪制區(qū)域 = 總高度 - 下邊距 - 上邊距
        float totalHeight = mHeight - getPaddingBottom() - getPaddingTop() - mXBottomInterval * 2 - startText.height() - mYTopInterval;
        // 起點x、y軸開始繪制
        float x = mXFirstPoint;
        float y = (mHeight - getPaddingBottom() - mXBottomInterval * 2 - startText.height()) - Float.parseFloat(mXValue.get(0).value) * totalHeight / max;
        // 繪制x、y軸左下角起點
        path.moveTo(mYLeftInterval, mHeight - getPaddingBottom() - getPaddingTop() - mXBottomInterval * 2 - startText.height());
        // 繪制第一個點的位置
        path.lineTo(x, y);
        // 因為繪制了第一個點,所以i起始是1
        for (int i = 1; i < mXValue.size(); i++) {
            // x點繪制的位置, mInterval是兩點的間距 * 點的數(shù)量 + x軸距離左邊距
            x = mXFirstPoint + mInterval * i;
            // y軸上到下是數(shù)字變大 所以需要反著繪制  繪制區(qū)域 - 百分比高度(百分比高度 = 數(shù)值 * 總高度 / 數(shù)據(jù)最大值), 相反過來就是從下到上的比例高度
            y = (mHeight - getPaddingBottom() - mXBottomInterval * 2 - startText.height()) - Float.parseFloat(mXValue.get(i).value) * totalHeight / max;
            path.lineTo(x, y);
        }
        // 如果不繪制點位到下方的路徑, 那么 不會實現(xiàn)全部陰影效果, x 就是 繪制最后一個點的位置, y 就是底邊的位置
        path.lineTo(x, mHeight - mXBottomInterval * 2 - startText.height());
        canvas.drawPath(path, mLinePaint);

        // 繪制折線
        Path linePath = new Path();
        // 起點x、y軸開始繪制
        float x1;
        float y1 = (mHeight - getPaddingBottom() - mXBottomInterval * 2 - startText.height()) - Float.parseFloat(mXValue.get(0).value) * totalHeight / max;
        // 繪制第一個點的位置
        linePath.moveTo(mXFirstPoint, y1);
        // 因為繪制了第一個點,所以i起始是1
        for (int i = 1; i < mXValue.size(); i++) {
            // x點繪制的位置, mInterval是兩點的間距 * 點的數(shù)量 + x軸距離左邊距
            x1 = mXFirstPoint + mInterval * i;
            // y軸上到下是數(shù)字變大 所以需要反著繪制  繪制區(qū)域 - 百分比高度(百分比高度 = 數(shù)值 * 總高度 / 數(shù)據(jù)最大值), 相反過來就是從下到上的比例高度
            y1 = (mHeight - getPaddingBottom() - mXBottomInterval * 2 - startText.height()) - Float.parseFloat(mXValue.get(i).value) * totalHeight / max;
            linePath.lineTo(x1, y1);
        }
        // 繪制折線
        mPointPaint.setColor(mLineColor);
        mPointPaint.setStyle(Paint.Style.STROKE);
        canvas.drawPath(linePath, mPointPaint);
    }

    /**
     * 繪制折線和提示文本
     */
    private void drawLinePoint(Canvas canvas) {
        Rect startText = getTextBounds(mXStartText, mXTextPaint);
        // 繪制區(qū)域 = 總高度 - 下邊距 - 上邊距
        float totalHeight = mHeight - getPaddingBottom() - getPaddingTop() - mXBottomInterval * 2 - startText.height() - mYTopInterval;
        for (int i = 0; i < mXValue.size(); i++) {
            // x點繪制的位置, mInterval是兩點的間距 * 點的數(shù)量 + x軸距離左邊距
            float x = mInterval * i + mXFirstPoint;
            // y軸上到下是數(shù)字變大 所以需要反著繪制  繪制區(qū)域 - 百分比高度(百分比高度 = 數(shù)值 * 總高度 / 數(shù)據(jù)最大值), 相反過來就是從下到上的比例高度
            float y = (mHeight - getPaddingBottom() - getPaddingTop() - mXBottomInterval * 2 - startText.height()) - Float.parseFloat(mXValue.get(i).value) * totalHeight / max;
            if (mCurrentSelectPoint == i + 1) {
                // 繪制垂直直線、點、文本
                drawVerticalLinePointText(canvas, x, startText, mXValue.get(i).num);
                // 繪制選中的點
                drawCurrentSelectPoint(canvas, x, y);
                // 繪制選中提示點
                drawCurrentTextBox(canvas, x, y - dpToPx(10), mXValue.get(i).value);
            }
        }
    }

    /**
     * 繪制垂直直線、點、文本
     */
    private void drawVerticalLinePointText(Canvas canvas, float x, Rect startText, String text) {
        int y = mHeight - getPaddingBottom() - mXBottomInterval * 2 - startText.height();
        mLinePointPaint.setStyle(Paint.Style.STROKE);
        mLinePointPaint.setColor(mLinePointColor);
        canvas.drawLine(x, y, x, mYTopInterval, mLinePointPaint);
        mLinePointPaint.setStyle(Paint.Style.FILL);
        canvas.drawCircle(x, y, dpToPx(4), mLinePointPaint);
        mLinePointPaint.setColor(mPointColor);
        canvas.drawCircle(x, y, dpToPx(2), mLinePointPaint);
        Rect rect = getTextBounds(text, mXTextPaint);
        canvas.drawText(text, x - (float) rect.width() / 2, y - dpToPx(6) - (float) rect.height() / 2, mXTextPaint);
    }

    /**
     * 繪制選中的點
     */
    private void drawCurrentSelectPoint(Canvas canvas, float x, float y) {
        mPointPaint.setStyle(Paint.Style.FILL);
        canvas.drawCircle(x, y, dpToPx(4), mPointPaint);
        mPointPaint.setColor(mPointColor);
        mPointPaint.setStyle(Paint.Style.FILL);
        canvas.drawCircle(x, y, dpToPx(2), mPointPaint);
    }

    /**
     * 繪制選中提示框
     */
    private void drawCurrentTextBox(Canvas canvas, float x, float y, String text) {
        Rect rect = getTextBounds(text, mXTextPaint);
        // y點計算  以下兩種方法均可
        // x減去文本的寬度  y - 三角的高度 - 文本高度 / 2
        canvas.drawText(text, x - (float) rect.width() / 2, y - dpToPx(6) - (float) rect.height() / 2, mXTextPaint);
    }

    /**
     * dp轉(zhuǎn)化成為px
     *
     * @param dp 單位
     */
    private int dpToPx(int dp) {
        float density = getContext().getResources().getDisplayMetrics().density;
        return (int) (dp * density + 0.5f * (dp >= 0 ? 1 : -1));
    }

    /**
     * 獲取丈量文本的矩形
     *
     * @param text  文本
     * @param paint 畫筆
     */
    private Rect getTextBounds(String text, Paint paint) {
        Rect rect = new Rect();
        paint.getTextBounds(text, 0, text.length(), rect);
        return rect;
    }

看滑動部分,因為是在繪制區(qū)域滑動手勢,所以按下的時候要判斷是否在X軸垂直線上, Y軸要判斷是否在繪制區(qū)域,繪制區(qū)域以外不能滑動

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        this.getParent().requestDisallowInterceptTouchEvent(true);//當該view獲得點擊事件,就請求父控件不攔截事件
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_MOVE:
                clickAction(event);
                break;
            case MotionEvent.ACTION_UP:
                clickAction(event);
                this.getParent().requestDisallowInterceptTouchEvent(false);
                break;
            case MotionEvent.ACTION_CANCEL:
                this.getParent().requestDisallowInterceptTouchEvent(false);
                break;
        }
        return true;
    }

    /**
     * 點擊X軸坐標或者折線節(jié)點
     *
     * @param event 事件
     */
    private void clickAction(MotionEvent event) {
        Rect startText = getTextBounds(mXStartText, mXTextPaint);
        int dp3 = dpToPx(3);
        float eventX = event.getX();
        float eventY = event.getY();
        // 繪制區(qū)域 = 總高度 - 下邊距 - 上邊距
        float bottomInterval = mHeight - getPaddingBottom() - getPaddingTop() - mXBottomInterval * 2 - startText.height() - mYTopInterval;
        for (int i = 0; i < mXValue.size(); i++) {
            // x點繪制的位置, mInterval是兩點的間距 * 點的數(shù)量 + x軸距離左邊距
            float x = mInterval * i + mXFirstPoint;
            // y軸上到下是數(shù)字變大 所以需要反著繪制  繪制區(qū)域 - 百分比高度(百分比高度 = 數(shù)值 * 總高度 / 數(shù)據(jù)最大值), 相反過來就是從下到上的比例高度
            if (eventX >= x - dp3 && eventX <= x + dp3 && eventY >= mYTopInterval && eventY <= bottomInterval) {
                mCurrentSelectPoint = i + 1;
                invalidate();
                if (onSelectedActionClick != null) {
                    onSelectedActionClick.onActionClick(i, mXValue.get(i).num, mXValue.get(i).value);
                }
                return;
            }
        }
    }

基本都有注釋,只要細看相信都能看懂
git:https://github.com/xiaobinAndroid421726260/Android_CustomAllCollection.git
里面有多種折線統(tǒng)計圖找到需要的,直接拿去用,不用客氣, 有bug 麻煩告訴一下
發(fā)郵件:421726260@qq.com

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

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

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