1. 程式人生 > >Android自定義View實現簡單的折線圖、柱狀圖

Android自定義View實現簡單的折線圖、柱狀圖


首先說第一個柱狀圖,實現很簡單。一個自定義View,重現裡面的OnDraw方法。然後利用paint,canvas繪製帶填充的長方形即可。每個長方形的X軸平方View的x軸即可,長方形的高度通過簡單的計算即可得到。下面上柱狀圖程式碼

package com.hrules.charter.demo.widget;

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.BounceInterpolator;

import com.hrules.charter.demo.R;

import java.util.Random;

/**
 * 柱狀圖
 * Created by 黃海 on 2017/4/17.
 */

public class Histogram extends View implements View.OnClickListener {
    int width, height;
    Paint paintBar, paintText;
    float[] values = new float[15];
    float[] valuesTemp = new float[15];
    int colorBackground = R.color.default_barBackgroundColor;//柱狀背景
    int[] colorBar = new int[]{R.color.lightBlue500, R.color.lightBlue400, R.color.lightBlue300};//柱狀顏色
    float maxY;
    int barMarginLeft = 7;
    int tagHeight = 45;//x和Y軸的數字
    boolean anim;
    boolean showLineXNums = true, showLineYNums = true;//展示X、Y軸的數字
    int lineYNums = 5;//Y軸展示的格數
    ValueAnimator valueAnimator;

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

    public Histogram(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        paintText = paintBar = new Paint();
        paintBar.setAntiAlias(true);
        paintText.setAntiAlias(true);
        paintText.setTextSize(25);
        paintText.setColor(getResources().getColor(colorBar[0]));
        initValuesAndMaxY();
        setOnClickListener(this);
    }

    private void initValuesAndMaxY() {
        Random random = new Random();
        for (int i = 0; i < values.length; i++) {
            values[i] = random.nextFloat() * 100;
        }
        for (float i : values) {
            maxY = maxY < i ? i : maxY;
        }
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        width = getMeasuredWidth();
        height = getMeasuredHeight();
        int barWidth = (width - tagHeight) / values.length;
        int j = 0;
        if (!anim)
            valuesTemp = values.clone();
        for (int i = 0; i < valuesTemp.length; i++) {
            RectF rect = new RectF();
            rect.left = tagHeight + i * barWidth + barMarginLeft;
            rect.top = (height - tagHeight) * (1 - 1.0f * valuesTemp[i] / maxY);
            rect.right = rect.left + barWidth - barMarginLeft;
            rect.bottom = height - tagHeight;
            //draw the barBackground
            paintBar.setColor(getResources().getColor(colorBackground));
            canvas.drawRect(rect.left, 0, rect.right, rect.bottom, paintBar);
            //paint the bar
            j = j > colorBar.length - 1 ? 0 : j;
            paintBar.setColor(getResources().getColor(colorBar[j++]));
            canvas.drawRect(rect, paintBar);
            if (showLineXNums) {
                //draw x-coordinate num
                float textWidth = paintText.measureText(String.valueOf(i));
                float textLeft = rect.left + rect.width() / 2 - textWidth / 2;
                canvas.drawText(String.valueOf(i), textLeft, height, paintText);
            }
        }
        //draw y-coordinate num
        if (showLineYNums) {
            int avgHeight = (height - tagHeight) / lineYNums;
            for (int i = 0; i < lineYNums; i++) {
                float x = 0;
                float y = (height - tagHeight) - avgHeight * (i + 1);
                int valueY = (int) (maxY * (i + 1) / lineYNums);
                // canvas寫字是從x、y軸往右上寫的
                canvas.drawText(String.valueOf(valueY), x, y + 30, paintText);
            }
        }
    }

    @Override
    public void onClick(View v) {
        anim = true;
        valueAnimator = ValueAnimator.ofFloat(0f, 1f);
        valuesTemp = values.clone();
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                for (int i = 0; i < valuesTemp.length; i++) {
                    //update valuesTemps
                    float animatedValue = (float) animation.getAnimatedValue();
                    valuesTemp[i] = maxY * animatedValue < values[i] ? maxY * animatedValue : values[i];
                }
                invalidate();
            }
        });
//        valueAnimator.setInterpolator(new LinearInterpolator());
        //落地回撥效果
        valueAnimator.setInterpolator(new BounceInterpolator());
        valueAnimator.setDuration(2000l);
        valueAnimator.start();
    }
}

實現比較簡單,註釋都有。最後說下onClick方法的作用:valuesTemp是原有柱狀資料的副本,view點選後讓valuesTemp的每個資料從maxY的最小百分比開始不斷的增長,從而實現一個動態改變的動畫效果,如下:

折線圖運用的paint,canvas與path完成,計算每個點的位置和柱狀圖類似,折線圖形成一個填充效果也簡單就是path close一下,然後繪製close後的path就是。下面看原始碼

package com.hrules.charter.demo.widget;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;

import com.hrules.charter.demo.R;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * 折線圖
 * Created by 黃海 on 2017/4/18.
 */

public class LineChart extends View {
    Path path = new Path();
    float[] values = new float[15];
    float maxY;
    Paint paintXy, paintLine;
    int width, height;//view 寬高

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

    public LineChart(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initValuesAndMaxY();
        paintXy = new Paint(Paint.ANTI_ALIAS_FLAG);
        paintXy.setStyle(Paint.Style.STROKE);
        paintXy.setStrokeWidth(6);
        paintXy.setColor(getResources().getColor(R.color.lightBlue500));
        paintLine = new Paint(Paint.ANTI_ALIAS_FLAG);
        paintLine.setStyle(Paint.Style.FILL);
        paintLine.setColor(getResources().getColor(R.color.colorPrimary));
    }

    private void initValuesAndMaxY() {
        Random random = new Random();
        for (int i = 0; i < values.length; i++) {
            values[i] = random.nextFloat() * 100;
            maxY = maxY < values[i] ? values[i] : maxY;
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        width = getMeasuredWidth();
        height = getMeasuredHeight();
        //每格寬度
        int widthAvg = width / values.length;
        //畫x軸y軸
        path.moveTo(0, 0);
        path.lineTo(0, height);
        path.lineTo(width, height);
        canvas.drawPath(path, paintXy);
        List<PointF> list = new ArrayList<>();
        for (int i = 0; i < values.length; i++) {//收集座標資訊
            PointF point = new PointF(widthAvg / 2 + i * widthAvg, 0);
            point.set(widthAvg / 2 + i * widthAvg, (1 - values[i] / maxY) * height);
            list.add(point);
        }

        //畫線
        path.reset();//清除 畫x軸y軸而產生的閉合影響
        path.moveTo(list.get(0).x, list.get(0).y);
        for (int i = 1; i < values.length; i++) {
            path.lineTo(list.get(i).x, list.get(i).y);
//            path.cubicTo(ps.x, ps.y,(ps.x+pe.x)/2,(ps.y+pe.y)/2, pe.x, pe.y);//畫三次貝塞爾曲線
        }
        canvas.drawPath(path, paintXy);
        //折線圖閉合
        path.lineTo(list.get(values.length - 1).x, height);
        path.lineTo(list.get(0).x, height);
        path.close();
        canvas.drawPath(path, paintLine);
        //畫點
        for (int i = 0; i < values.length; i++) {
            canvas.drawCircle(list.get(i).x, list.get(i).y, 9, paintLine);
        }
    }
}