自定義View之顏色漸變折線圖

Smilyyy發表於2017-03-16

* 本篇文章已授權微信公眾號 guolin_blog (郭霖)獨家釋出

* 本文同步發表在簡書,轉載請註明出處。

首先看下要實現的效果圖。
這裡寫圖片描述
折線圖的繪製主要有一下幾個步驟。
一、定義LineChartView類並繼承View。
二、新增自定義屬性。以在value目錄下建立attrs.xml檔案,檔案中我們可以定義一些用到的屬性,比如折線顏色、字型大小等屬性。檔案內容如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <declare-styleable name="LineChartView">
    <attr name="axesColor" format="color"/> <!--座標軸顏色-->
    <attr name="axesWidth" format="dimension"/><!--座標軸寬度-->
    <attr name="textColor" format="color"/> <!--字型顏色-->
    <attr name="textSize" format="dimension"/> <!--字型大小-->
    <attr name="lineColor" format="color"/> <!--折線顏色-->
    <attr name="bgColor" format="color"/> <!--背景色-->
  </declare-styleable>
</resources>

接下來在LineChartView的構造方法中解析自定義屬性的值並做相應的處理。在構造方法裡還初始化了漸變顏色、折線資料的List集合以及初始化畫筆等操作程式碼如下:

 public LineChartView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.LineChartView);
        mAxesColor = typedArray.getColor(R.styleable.LineChartView_axesColor, Color.parseColor("#CCCCCC"));
        mAxesWidth = typedArray.getDimension(R.styleable.LineChartView_axesWidth, 1);
        mTextColor = typedArray.getColor(R.styleable.LineChartView_textColor, Color.parseColor("#ABABAB"));
        mTextSize = typedArray.getDimension(R.styleable.LineChartView_textSize, 32);
        mLineColor = typedArray.getColor(R.styleable.LineChartView_lineColor, Color.RED);
        mBgColor = typedArray.getColor(R.styleable.LineChartView_bgColor, Color.WHITE);
        typedArray.recycle();

        //  初始化漸變色
        shadeColors = new int[]{
                Color.argb(100, 255, 86, 86), Color.argb(15, 255, 86, 86),
                Color.argb(0, 255, 86, 86)};
        //  初始化折線資料集合
        mItems = new ArrayList<>();
        mMargin10 = ScreenUtils.dp2px(context, 10);
        init();
    }

另外,折現資料需要實體類,實體類可直接新增到LineChartView內部。如下:

//  折線資料的實體類
    public static class ItemBean {

        private long Timestamp;
        private int value;

        public ItemBean(){}


        public ItemBean(long timestamp, int value) {
            super();
            Timestamp = timestamp;
            this.value = value;
        }

        public long getTimestamp() {
            return Timestamp;
        }

        public void setTimestamp(long timestamp) {
            Timestamp = timestamp;
        }

        public int getValue() {
            return value;
        }

        public void setValue(int value) {
            this.value = value;
        }

    }

三、初始化畫筆和路徑。程式碼如下:

private void init() {
        //  初始化座標軸畫筆
        mPaintAxes = new Paint();
        mPaintAxes.setColor(mAxesColor);
        mPaintAxes.setStrokeWidth(mAxesWidth);

        //  初始化文字畫筆
        mPaintText = new Paint();
        mPaintText.setStyle(Paint.Style.FILL);
        mPaintText.setAntiAlias(true); //抗鋸齒
        mPaintText.setTextSize(mTextSize);
        mPaintText.setColor(mTextColor);
        mPaintText.setTextAlign(Paint.Align.LEFT);

        //  初始化折線畫筆
        mPaintLine = new Paint();
        mPaintLine.setStyle(Paint.Style.STROKE);
        mPaintLine.setAntiAlias(true);
        mPaintLine.setStrokeWidth(mAxesWidth / 2);
        mPaintLine.setColor(mLineColor);

        //  初始化折線路徑
        mPath = new Path();
        mPathShader = new Path();

        //  陰影畫筆
        mPaintShader = new Paint();
        mPaintShader.setAntiAlias(true);
        mPaintShader.setStrokeWidth(2f);
    }

四、重寫onLayout方法。在onLayout方法中獲取控制元件的寬高、初始化原點座標以及設定控制元件的背景。程式碼如下:

 @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        if (changed) {

            mWidth = getWidth();
            mHeight = getHeight();
            timeWidth = (int) mPaintText.measureText(startTime);
            //  初始化原點座標
            xOrigin = 0 + mMargin10;
            yOrigin = (mHeight - mTextSize - mMargin10);

            //  設定背景色
            setBackgroundColor(mBgColor);
        }
    }

五、重寫onDraw方法。在onDraw方法中完成折線圖的繪製。程式碼如下:

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //  Y軸座標間距
        yInterval = (max - min) / (yOrigin - mMargin10);
        //  X軸座標間距
        xInterval = (mWidth - xOrigin) / (mItems.size() - 1);
        //  畫座標軸
        drawAxes(canvas);
        //  畫文字
        drawText(canvas);
        //  畫折線
        drawLine(canvas);
        //  設定動畫
        setAnim(canvas)
    }

折線圖的繪製可以分三部分:1.繪製座標軸。2.繪製View上的文字。3.繪製折線。

1.座標軸繪製的是第一象限,即左下角的點為原點。繪製座標軸程式碼如下:

//  畫座標軸
    private void drawAxes(Canvas canvas) {
        //  繪製X軸
        canvas.drawLine(xOrigin, yOrigin, mWidth - mMargin10, yOrigin, mPaintAxes);
        //  繪製X中軸線
        canvas.drawLine(xOrigin, yOrigin / 2, mWidth - mMargin10, yOrigin / 2, mPaintAxes);
        //  繪製X上邊線
        canvas.drawLine(xOrigin, mMargin10, mWidth - mMargin10, mMargin10, mPaintAxes);
        //  繪製畫Y軸
        canvas.drawLine(xOrigin, yOrigin, xOrigin, mMargin10, mPaintAxes);
        //  繪製Y右邊線
        canvas.drawLine(mWidth - mMargin10, mMargin10, mWidth - mMargin10, yOrigin, mPaintAxes);
    }

2.繪製文字,程式碼如下:

private void drawText(Canvas canvas) {
        //  繪製最大值
        canvas.drawText(String.format("%.2f", max * 100 / 100.0) + "%", xOrigin + 6, 2 * mMargin10, mPaintText);
        //  繪製最小值
        canvas.drawText(String.format("%.2f", min * 100 / 100.0) + "%", xOrigin + 6, yOrigin - 6, mPaintText);
        //  繪製中間值
        canvas.drawText((String.format("%.2f", (min + max) * 100 / 200.0) + "%"), xOrigin + 6, (yOrigin + mMargin10) / 2, mPaintText);

        //  繪製開始日期
        canvas.drawText(startTime, xOrigin, mHeight - mMargin10, mPaintText);
        //  繪製結束日期
        canvas.drawText(endTime, mWidth - timeWidth - mMargin10, mHeight - mMargin10, mPaintText);
    }

3.繪製折線及漸變填充

private void drawLine(Canvas canvas) {
        //  畫座標點
        for (int i = 0; i < mItems.size(); i++) {
            float x = i * xInterval + xOrigin + mAxesWidth;
            if (i == 0) {
                mPathShader.moveTo(x, yOrigin - (mItems.get(i).getValue() - min) / yInterval);
                mPath.moveTo(x, yOrigin - (mItems.get(i).getValue() - min) / yInterval);
            } else {
                mPath.lineTo(x - mMargin10 - mAxesWidth, yOrigin - (mItems.get(i).getValue() - min) / yInterval);
                mPathShader.lineTo(x - mMargin10 - mAxesWidth, yOrigin - (mItems.get(i).getValue() - min) / yInterval);
                if (i == mItems.size() - 1) {
                    mPathShader.lineTo(x - mMargin10 - mAxesWidth, yOrigin);
                    mPathShader.lineTo(xOrigin, yOrigin);
                    mPathShader.close();
                }
            }
        }


        //  漸變陰影
        Shader mShader = new LinearGradient(0, 0, 0, getHeight(), shadeColors, null, Shader.TileMode.CLAMP);
        mPaintShader.setShader(mShader);

        //  繪製漸變陰影
        canvas.drawPath(mPathShader, mPaintShader);
    }

六、折線圖新增動畫。
1.首先需要計算動畫的進度,因此在LineChartView中定義成員變數mProgress,並新增以下方法:

    /**
     * Animate this property. It is the percentage of the path that is drawn.
     * It must be [0,1].
     *
     * @param percentage float the percentage of the path.
     */
    public void setPercentage(float percentage) {
        if (percentage < 0.0f || percentage > 1.0f) {
            throw new IllegalArgumentException(
                    "setPercentage not between 0.0f and 1.0f");
        }
        mProgress = percentage;
        invalidate();
    }

2.接下來設定動畫效果,程式碼如下:

 private void setAnim(Canvas canvas) {

        PathMeasure measure = new PathMeasure(mPath, false);
        float pathLength = measure.getLength();
        PathEffect effect = new DashPathEffect(new float[]{pathLength,
                pathLength}, pathLength - pathLength * mProgress);
        mPaintLine.setPathEffect(effect);
        canvas.drawPath(mPath, mPaintLine);
    }

3.新增開啟動畫的方法:

  /**
     * @param lineChartView
     * @param duration      動畫持續時間
     */
    public void startAnim(LineChartView lineChartView, long duration) {
        ObjectAnimator anim = ObjectAnimator.ofFloat(lineChartView, "percentage", 0.0f, 1.0f);
        anim.setDuration(duration);
        anim.setInterpolator(new LinearInterpolator());
        anim.start();
    }

至此,折線圖的繪製已經全部完成。最後還可以新增get() set()方法,暴露出屬性介面,以供外部呼叫。程式碼就不再貼出來了。

七、使用LineChartView
1.在佈局檔案中新增LineChartView,可設定折線顏色、字型顏色、等屬性,如下:

<com.example.zhpan.linechartview.LineChartView
        android:id="@+id/lcv"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:layout_marginBottom="20dp"
        android:layout_marginLeft="15dp"
        android:layout_marginRight="15dp"
        android:layout_marginTop="15dp"
        app:lineColor="#FF0000"
        app:textColor="#ABABAB"
        app:textSize="12dp"/>

2.在Activity中為LineChartView設定資料,也可以通過程式碼為其設定屬性。

private void initData() {
        //  初始化折線資料
        mItems = new ArrayList<>();
        mItems.add(new LineChartView.ItemBean(1489507200, 23));
        mItems.add(new LineChartView.ItemBean(1489593600, 88));
        mItems.add(new LineChartView.ItemBean(1489680000, 60));
        mItems.add(new LineChartView.ItemBean(1489766400, 50));
        mItems.add(new LineChartView.ItemBean(1489852800, 70));
        mItems.add(new LineChartView.ItemBean(1489939200, 10));
        mItems.add(new LineChartView.ItemBean(1490025600, 33));
        mItems.add(new LineChartView.ItemBean(1490112000, 44));
        mItems.add(new LineChartView.ItemBean(1490198400, 99));
        mItems.add(new LineChartView.ItemBean(1490284800, 17));

        shadeColors= new int[]{
                Color.argb(100, 255, 86, 86), Color.argb(15, 255, 86, 86),
                Color.argb(0, 255, 86, 86)};

        //  設定折線資料
        mLineChartView.setItems(mItems);
        //  設定漸變顏色
        mLineChartView.setShadeColors(shadeColors);
        //  開啟動畫
        mLineChartView.startAnim(mLineChartView,2000);
    }

原始碼下載

相關文章