1. 程式人生 > >android:自定義view--區線圖

android:自定義view--區線圖

老規矩,先上圖,整個自定義view分為標題模組,xy軸標註資料模組和資料模組,

其實資料模組是有一個淺灰色的背景,但是動態圖裡面看不出來,真機上可以看出;

和前面幾個圖表相比增加了貝塞爾區線,虛線,手勢點選顯示標註線和當前日資料;

後面還有左右按鈕切換上週下週資料,這個不是主要功能,就不加到部落格中了,真實專案使用直接將資料準備好,

左右切換的時候切換資料來源重新整理view即可;

老樣子,直接上程式碼   

/**
 *  Created by zheng on 2018/3/28 0027.
 *  VFX我的模組 貝塞爾區線資料圖
 *  前六個繪製方法沒寫多少註釋,都是前面用到過的方法
 *  繪製主要資料 和 繪製點選事件的顯示資料 註釋都寫的很詳細
 */
public class VFXBesselChart extends View {

    //主畫筆
    private Paint mPaint;
    //標題矩形
    private Rect titleRect;
    //資料區域矩形
    private Rect dataRect;

    String titleName = "浮動收益";
    String[] yStrs = {"$10000.00", "$7500.00", "$5000.00", "$2500.00", "$0.00"};
    String[] xStrs = {"3/20", "3/21", "3/22", "3/23", "3/24", "3/25", "3/26"};
    Integer[] dataArray = {500, 5000, 1399, 9999, 7499, 999, 10};

    //定時器
    private CountDownTimer timer;
    //手勢點選記錄週期
    private int weekPosition = -1;
    /**
     * 曲線上資料點
     */
    private List<Point> pointList = new ArrayList<>();

    //   左上角標題文字顏色  x軸底部文字顏色  x軸刻度顏色
    private int textTitleColor, textXColor, scaleXColor;
    //    y軸左邊文字顏色  貝塞爾資料線顏色  手勢點選後資料標註線顏色
    private int textYColor, lineBesselColor, lineClickColor;
    //              虛線顏色       y軸左邊文字距離     x軸下邊文字距離底部的高度
    private int lineImaginaryColor, yScaleDataWidth, xScaleDataHeight;
    //    x軸刻度線寬度  貝塞爾資料線寬度
    private int scaleXWidth, lineBesselWidth;
    //            虛線寬度  手勢點選後資料標註線寬度   資料區域距離頂部距離
    private float lineImaginaryWidth, lineClickWidth, dataMarginTop;
    //資料區域距離右邊的距離
    private int dataMarginRightWidth;
    //資料區域內部padding距離
    private int dataPaddingLeft;
    private int dataPaddingRight;
    private int dataPaddingTop;
    private int dataPaddingBottom;

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

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

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

    private void initPaint() {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setStrokeWidth(2);
    }

    private void initXmlAttrs(Context context, int attrs) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.VFXBesselChart);
        if (typedArray == null) return;
        textTitleColor = typedArray.getColor(R.styleable.VFXBesselChart_text_title_color, getResources().getColor(R.color.black));
        textXColor = typedArray.getColor(R.styleable.VFXBesselChart_text_x_color, getResources().getColor(R.color.black));
        scaleXColor = typedArray.getColor(R.styleable.VFXBesselChart_scale_x_color, getResources().getColor(R.color.gray_d5));
        textYColor = typedArray.getColor(R.styleable.VFXBesselChart_text_y_color, getResources().getColor(R.color.black));
        lineBesselColor = typedArray.getColor(R.styleable.VFXBesselChart_line_bessel_color, getResources().getColor(R.color.gold));
        lineClickColor = typedArray.getColor(R.styleable.VFXBesselChart_line_click_color, getResources().getColor(R.color.black_an));
        lineImaginaryColor = typedArray.getColor(R.styleable.VFXBesselChart_line_imaginary_color, getResources().getColor(R.color.gray_d5));
        scaleXWidth = typedArray.getInteger(R.styleable.VFXBesselChart_scale_x_width, 2);
        lineImaginaryWidth = typedArray.getDimension(R.styleable.VFXBesselChart_line_imaginary_width, 2);
        lineBesselWidth = typedArray.getInteger(R.styleable.VFXBesselChart_line_bessel_widht, 3);
        lineClickWidth = typedArray.getDimension(R.styleable.VFXBesselChart_line_click_widht, 2);
        dataMarginTop = typedArray.getDimension(R.styleable.VFXBesselChart_data_margin_top, 120);
        yScaleDataWidth = typedArray.getInteger(R.styleable.VFXBesselChart_width_y_scale_data, 150);
        xScaleDataHeight = typedArray.getInteger(R.styleable.VFXBesselChart_height_x_scale_data, 60);
        dataMarginRightWidth = typedArray.getInteger(R.styleable.VFXBesselChart_width_data_margin_right, 50);
        dataPaddingLeft = typedArray.getInteger(R.styleable.VFXBesselChart_padding_data_left, 10);
        dataPaddingRight = typedArray.getInteger(R.styleable.VFXBesselChart_padding_data_right, 10);
        dataPaddingTop = typedArray.getInteger(R.styleable.VFXBesselChart_padding_data_top, 10);
        dataPaddingBottom = typedArray.getInteger(R.styleable.VFXBesselChart_padding_data_bottom, 20);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //繪製左上角標題  浮動收益
        drawTitle(canvas);
        //資料區域  (灰色矩形)
        drawDataRect(canvas);
        //y軸左邊資料 ($10000.00)
        drawYStrs(canvas);
        //x軸下面的資料  (3.20)
        drawXStrs(canvas);
        //繪製五條虛線
        drawImaginary(canvas);
        //繪製x軸的刻度
        drawXScales(canvas);

        //繪製主要資料
        drawMainDatas(canvas);
        //繪製點選事件的顯示資料
        drawClickData(canvas);

    }

    //繪製左上角標題  浮動收益
    private void drawTitle(Canvas canvas) {
        mPaint.setTextAlign(Paint.Align.LEFT);
        mPaint.setColor(textTitleColor);
        mPaint.setTextSize(38);
        titleRect = new Rect();
        mPaint.getTextBounds(titleName, 0, titleName.length(), titleRect);
        canvas.drawText(
                titleName,
                50,
                titleRect.height() + 40,
                mPaint
        );
    }

    //資料區域  (灰色矩形)
    private void drawDataRect(Canvas canvas) {
        mPaint.setColor(Color.parseColor("#f8f8f8"));
        dataRect = new Rect(
                yScaleDataWidth, (int) dataMarginTop,
                getWidth() - dataMarginRightWidth, getHeight() - xScaleDataHeight
        );
        canvas.drawRect(
                dataRect,
                mPaint
        );
    }

    //y軸左邊資料 ($10000.00)
    private void drawYStrs(Canvas canvas) {
        mPaint.setTextSize(25);
        mPaint.setColor(textYColor);
        mPaint.setTextAlign(Paint.Align.RIGHT);
        for (int i = 0; i < yStrs.length; i++) {
            canvas.drawText(
                    yStrs[i],
                    yScaleDataWidth - 5,
                    (dataRect.height() - 30) / 4 * i + 20 + dataMarginTop,
                    mPaint
            );
        }
    }

    //x軸下面的資料  (3.20)
    private void drawXStrs(Canvas canvas) {

        mPaint.setTextSize(25);
        mPaint.setColor(textXColor);

        for (int i = 0; i < 7; i++) {
            if (i == 0) {
                mPaint.setTextAlign(Paint.Align.LEFT);
            } else if (i == 6) {
                mPaint.setTextAlign(Paint.Align.RIGHT);
            } else {
                mPaint.setTextAlign(Paint.Align.CENTER);
            }
            Rect xDataRect = new Rect();
            mPaint.getTextBounds(xStrs[i], 0, xStrs[i].length(), xDataRect);
            canvas.drawText(
                    xStrs[i],
                    yScaleDataWidth + 10 + ((dataRect.width() - 20) / 6) * i,
                    dataMarginTop + dataRect.height() + (int) (xDataRect.height() * 1.5),
                    mPaint
            );
        }

    }

    //繪製五條虛線
    private void drawImaginary(Canvas canvas) {

        Paint imaginaryPaint = new Paint();
        imaginaryPaint.setStyle(Paint.Style.FILL);
        imaginaryPaint.setColor(lineImaginaryColor);
        imaginaryPaint.setStrokeWidth(lineImaginaryWidth);
        //繪製長度為4的實線後再繪製長度為4的空白區域,0位間隔
        DashPathEffect effect = new DashPathEffect(new float[]{4, 4}, 0);
        imaginaryPaint.setPathEffect(effect);
        imaginaryPaint.setAntiAlias(true);

        int allImaginaryHeight = dataRect.height() - dataPaddingTop - dataPaddingBottom;

        for (int i = 0; i < 5; i++) {
            //兩點一線
            canvas.drawLine(
                    yScaleDataWidth + dataPaddingLeft, dataMarginTop + dataPaddingTop + allImaginaryHeight / 4 * i,
                    getWidth() - dataMarginRightWidth - dataPaddingRight, dataMarginTop + dataPaddingTop + allImaginaryHeight / 4 * i,
                    imaginaryPaint
            );
        }

    }

    //繪製x軸的刻度
    private void drawXScales(Canvas canvas) {
        Paint xScalesPaint = new Paint();
        xScalesPaint.setAntiAlias(true);
        xScalesPaint.setStyle(Paint.Style.STROKE);
        xScalesPaint.setColor(scaleXColor);
        xScalesPaint.setStrokeWidth(scaleXWidth);
        int allXScalesWidth = getWidth() - yScaleDataWidth - dataPaddingLeft - dataPaddingRight - dataMarginRightWidth;
        for (int i = 0; i < 7; i++) {
            canvas.drawLine(
                    yScaleDataWidth + dataPaddingLeft + allXScalesWidth / 6 * i, getHeight() - xScaleDataHeight,
                    yScaleDataWidth + dataPaddingLeft + allXScalesWidth / 6 * i, getHeight() - xScaleDataHeight - dataPaddingBottom + 10,
                    xScalesPaint
            );
        }
    }

    //繪製主要資料
    private void drawMainDatas(Canvas canvas) {

        Paint dataPaint = new Paint();
        dataPaint.setAntiAlias(true);
        dataPaint.setStyle(Paint.Style.STROKE);
        dataPaint.setColor(lineBesselColor);
        dataPaint.setStrokeWidth(lineBesselWidth);

        Path path = new Path();
        path.reset();
        //計算整個資料區域的寬高
        int allDataHeight = dataRect.height() - dataPaddingTop - dataPaddingBottom;
        int allDataWidth = dataRect.width() - dataPaddingLeft - dataPaddingRight;

        /**
         * 計算資料的XY座標
         */
        for (int i = 0; i < dataArray.length; i++) {
            Point point = new Point();
            point.set(
                    yScaleDataWidth + dataPaddingLeft + allDataWidth / 6 * i,
                    (int) (getHeight() - xScaleDataHeight - dataPaddingBottom - (allDataHeight / 10000.0 * dataArray[i]))
            );
            pointList.add(point);
        }
        //資料資料點繪製貝塞爾區線
        drawScrollLine(canvas, dataPaint);
    }

    //繪製點選事件的顯示資料
    private void drawClickData(Canvas canvas) {

        //如果為-1代表沒有點選
        if (weekPosition == -1) return;

        //這裡重新重建畫筆
        Paint weekPaint = new Paint();
        weekPaint.setAntiAlias(true);
        weekPaint.setStrokeWidth(lineClickWidth);
        weekPaint.setTextSize(20);
        weekPaint.setColor(lineClickColor);

        //繪製豎線(兩點一線)
        canvas.drawLine(
                //y軸標註資料寬度 + 資料模組左padding值 + 六天總寬度 / 6 * 第幾天
                yScaleDataWidth + dataPaddingLeft + (dataRect.width() - dataPaddingLeft - dataPaddingRight) / 6 * weekPosition,
                //資料模組上padding值 + 資料模組距離view頂部距離
                dataPaddingTop + dataMarginTop,
                yScaleDataWidth + dataPaddingLeft + (dataRect.width() - dataPaddingLeft - dataPaddingRight) / 6 * weekPosition,
                //整個view高度 - x軸下方標註資料高度 - 資料模組下padding值
                getHeight() - xScaleDataHeight - dataPaddingBottom,
                weekPaint
        );

        //繪製矩形資料
        Point weekPoint = pointList.get(weekPosition);      //獲取資料點
        String weekDataStr = "$" + dataArray[weekPosition];

        ToastUtils.showToast(getContext(), weekDataStr);

        Rect weekRect = new Rect();
        weekPaint.getTextBounds(weekDataStr, 0, weekDataStr.length(), weekRect);

        weekPaint.setColor(Color.parseColor("#aac69d5e"));
        weekPaint.setTextAlign(Paint.Align.CENTER);

        //如果是前五天(週一到週五)顯示在資料線右邊 否則 顯示在資料線左邊
        if (weekPosition < 5) {
            /**
             * 精細計算了一下寬度,高度同理,寬度根據文字寬度+預留padding值
             * 寬度:當前資料點x座標 + 文字寬度 + 預留padding值(30)
             */
            Rect weekBgRect = new Rect(
                    weekPoint.x + 5, weekPoint.y - 35,
                    weekPoint.x + weekRect.width() + 30, weekPoint.y - 5
            );
            canvas.drawRect(weekBgRect, weekPaint);
            weekPaint.setColor(Color.WHITE);
            canvas.drawText(
                    weekDataStr,
                    weekPoint.x + 5 + weekBgRect.width() / 2,
                    weekPoint.y - 12,
                    weekPaint
            );
        } else {
            Rect weekBgRect = new Rect(
                    weekPoint.x - weekRect.width() - 30, weekPoint.y - 35,
                    weekPoint.x - 5, weekPoint.y - 5
            );
            canvas.drawRect(weekBgRect, weekPaint);
            weekPaint.setColor(Color.WHITE);
            canvas.drawText(
                    weekDataStr,
                    weekPoint.x - 5 - weekBgRect.width() / 2,
                    weekPoint.y - 12,
                    weekPaint
            );
        }

    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_UP:
                //兩天之間的寬度
                int weekScale = pointList.get(1).x - pointList.get(0).x;
                for (int i = 0; i < pointList.size(); i++) {
                    //for迴圈遍歷計算手勢點選座標離哪天最近
                    if (Math.abs(event.getX() - pointList.get(i).x) < weekScale / 2) {
                        //標註點選
                        weekPosition = i;
                        //倒計時4秒隱藏(倒計時之前先取消上次倒計時)
                        if (timer != null)
                            timer.cancel();
                        //開啟倒計時
                        startCountDownTime(4);
                        invalidate();
                        break;
                    }
                }
                break;
        }
        return true;
    }

    //多個數據點繪製貝塞爾區線
    private void drawScrollLine(Canvas canvas, Paint dataPaint) {
        Point startp = new Point();
        Point endp = new Point();
        for (int i = 0; i < pointList.size() - 1; i++) {
            startp = pointList.get(i);
            endp = pointList.get(i + 1);
            int wt = (startp.x + endp.x) / 2;
            Point p3 = new Point();
            Point p4 = new Point();
            p3.y = startp.y;
            p3.x = wt;
            p4.y = endp.y;
            p4.x = wt;
            Path path = new Path();
            path.moveTo(startp.x, startp.y);
            path.cubicTo(p3.x, p3.y, p4.x, p4.y, endp.x, endp.y);
            canvas.drawPath(path, dataPaint);
        }
    }

    private void startCountDownTime(long time) {

        timer = new CountDownTimer(time * 1000, 1000) {
            @Override
            public void onTick(long millisUntilFinished) {
                //每隔countDownInterval秒會回撥一次onTick()方法
            }

            @Override
            public void onFinish() {
                //倒計時結束
                weekPosition = -1;
                invalidate();
            }
        };
        timer.start();// 開始計時
        //timer.cancel(); // 取消
    }

}

設定自定義屬性需要在styles.xml中宣告:

<!--我的模組貝塞爾區線資料圖-->
    <declare-styleable name="VFXBesselChart">
        <!--左上角標題文字顏色-->
        <attr name="text_title_color" format="color" />
        <!--x軸底部文字顏色-->
        <attr name="text_x_color" format="color" />
        <!--x軸刻度顏色-->
        <attr name="scale_x_color" format="color" />
        <!--y軸左邊文字顏色-->
        <attr name="text_y_color" format="color" />
        <!--貝塞爾資料線顏色-->
        <attr name="line_bessel_color" format="color" />
        <!--手勢點選後資料標註線顏色-->
        <attr name="line_click_color" format="color" />
        <!--虛線顏色-->
        <attr name="line_imaginary_color" format="color" />
        <!--x軸刻度線寬度-->
        <attr name="scale_x_width" format="integer" />
        <!--虛線寬度-->
        <attr name="line_imaginary_width" format="dimension" />
        <!--貝塞爾資料線寬度-->
        <attr name="line_bessel_widht" format="integer" />
        <!--手勢點選後資料標註線寬度-->
        <attr name="line_click_widht" format="dimension" />
        <!--資料區域距離頂部距離-->
        <attr name="data_margin_top" format="dimension" />
        <!--y軸左邊文字距離-->
        <attr name="width_y_scale_data" format="integer"/>
        <!--x軸下邊文字距離底部的高度-->
        <attr name="height_x_scale_data" format="integer"/>
        <!--資料區域距離右邊的距離-->
        <attr name="width_data_margin_right" format="integer"/>
        <!--資料區域內部padding距離-->
        <attr name="padding_data_top" format="integer"/>
        <attr name="padding_data_bottom" format="integer"/>
        <attr name="padding_data_left" format="integer"/>
        <attr name="padding_data_right" format="integer"/>
    </declare-styleable>
<!--android:layerType="software" 繪製虛線需要設定-->
    <com.example.administrator.vfxbesselchart.view.VFXBesselChart
        android:layout_width="match_parent"
        android:layout_height="240dp"
        android:layerType="software" 
        app:text_title_color="#000000"/>

點選開啟連結免費下載原始碼