1. 程式人生 > >android:自定義view--橫向柱形圖

android:自定義view--橫向柱形圖


此demo也是從我們真實專案中copy出來的;大致步驟和製作MyTabView差不多,只不過稍微繁瑣一些;

/**
 * Created by zheng on 2017/12/4.
 * 橫向的柱形圖
 *
 * 1:繪製XY軸
 * 2:繪製XY軸刻度和旁邊的文字
 * 3:繪製圓角矩形(六個月份的柱子)
 * 4:繪製和背景顏色一樣的矩形(遮擋圓角矩形左邊的圓角)
 *
 */
public class HPillarView extends View {

    private Paint mPaint;

    //線的顏色,柱子右邊顯示**人的顏色
    private int lineColor, dataFontColor;
    //X軸距離左邊和右邊的距離,Y軸距離上邊和下邊的距離
    private float xLeftSpace, xRightSpace,
            yTopSpace, yBottomSpace;
    //XY軸刻度的大小:X軸刻度的高,Y軸刻度的寬
    private float xDividerHeight, yDividerWidth;
    //XY軸刻度顏色
    private int xDividerColor, yDividerColor;

    //x軸下面的字型距離X軸的距離,Y軸左邊的字型距離Y軸的距離
    private float txtXSpace, txtYSpace;

    //每個柱子的高度
    private float pillarHeight;
    //柱子距離Y軸刻度的距離
    private float pillarMarginY = 10;

    private final float FULL_AMOUNT = 7000;

    private List<Integer> dataList = new ArrayList<>();
    private float xyFontSize;

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

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

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

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

    private void initXmlAttrs(Context context, AttributeSet attrs) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.hpillar);
        if (typedArray == null) return;
        lineColor = typedArray.getColor(R.styleable.hpillar_line_color_h, Color.BLACK);
        dataFontColor = typedArray.getColor(R.styleable.hpillar_data_font_color_h, Color.BLACK);
        xLeftSpace = typedArray.getDimension(R.styleable.hpillar_x_left_space_h, 50);
        xRightSpace = typedArray.getDimension(R.styleable.hpillar_x_right_space_h, 80);
        yTopSpace = typedArray.getDimension(R.styleable.hpillar_y_top_space_h, 30);
        yBottomSpace = typedArray.getDimension(R.styleable.hpillar_y_bottom_space_h, 100);
        yDividerWidth = typedArray.getDimension(R.styleable.hpillar_y_divider_width, 14);
        xDividerHeight = typedArray.getDimension(R.styleable.hpillar_x_divider_height, 14);
        xDividerColor = typedArray.getColor(R.styleable.hpillar_x_divider_color, Color.BLACK);
        yDividerColor = typedArray.getColor(R.styleable.hpillar_y_divider_color, Color.BLACK);
        xyFontSize = typedArray.getDimension(R.styleable.hpillar_xy_font_size_h, 30);
        txtXSpace = typedArray.getDimension(R.styleable.hpillar_txt_x_space, 20);
        txtYSpace = typedArray.getDimension(R.styleable.hpillar_txt_y_space, 15);
        pillarHeight = typedArray.getDimension(R.styleable.hpillar_pillar_height, 20);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (dataList.size() == 0) return;

        /**
         * 繪製XY軸
         */
        initPaintColor(lineColor);
        float xLineTop = getHeight() - yBottomSpace;//view的高度 - Y軸距離view頂部的距離
        //兩點確定一線
        canvas.drawLine(
                xLeftSpace, xLineTop,
                getWidth() - xRightSpace, xLineTop,
                mPaint
        );
        canvas.drawLine(
                xLeftSpace, yTopSpace,
                xLeftSpace, getHeight() - yBottomSpace,
                mPaint
        );


        /**
         * 繪製Y軸刻度 和 左邊的文字
         */
        //Y軸每個刻度的高度:獲取Y軸真是高度,然後除以6獲取每個刻度的高度
        float spaceVertical = (getHeight() - yTopSpace - yBottomSpace) / 6;
        //Y軸刻度左右的X座標
        float yScaleLeftX = xLeftSpace - yDividerWidth / 2;
        float yScaleRightX = xLeftSpace + yDividerWidth / 2;
        float[] pts = {
                yScaleLeftX, yTopSpace, yScaleRightX, yTopSpace,
                yScaleLeftX, yTopSpace + spaceVertical, yScaleRightX, yTopSpace + spaceVertical,
                yScaleLeftX, yTopSpace + spaceVertical * 2, yScaleRightX, yTopSpace + spaceVertical * 2,
                yScaleLeftX, yTopSpace + spaceVertical * 3, yScaleRightX, yTopSpace + spaceVertical * 3,
                yScaleLeftX, yTopSpace + spaceVertical * 4, yScaleRightX, yTopSpace + spaceVertical * 4,
                yScaleLeftX, yTopSpace + spaceVertical * 5, yScaleRightX, yTopSpace + spaceVertical * 5
        };

        //繪製一組線:pts中得資料四個為一組,同樣代表起點終點座標
        canvas.drawLines(pts, mPaint);

        //繪製Y軸刻度左邊文字
        Rect yTxtRect;
        mPaint.setTextSize(xyFontSize);
        int[] ys = {2, 4, 6, 8, 10, 12};
        for (int i = 0; i < ys.length; i++) {
            String number = ys[(ys.length - 1 - i)] + "";
            yTxtRect = new Rect();
            //為了讓六個月份居中,測量最大數
            mPaint.getTextBounds("12", 0, 2, yTxtRect);
            //設定居中
            mPaint.setTextAlign(Paint.Align.CENTER);
            canvas.drawText(
                    number,
                    xLeftSpace - yTxtRect.width() / 2 - txtYSpace,
                    yTopSpace + (spaceVertical * i) + yTxtRect.height() / 2,
                    mPaint
            );
        }

        /**
         * 繪製X軸刻度 和 X軸下面的文字
         */
        //獲取x軸刻度間距  view寬度 - x軸左邊距 - X軸右邊距 - Y軸刻度寬度一半 - 柱子距離刻度的距離
        float xSpaceVertical = (getWidth() - xLeftSpace - xRightSpace - yDividerWidth / 2 - pillarMarginY) / 7;
        //X刻度上下Y座標
        float xScaleTopY = getHeight() - yBottomSpace - xDividerHeight / 2;
        float xScaleBottomY = getHeight() - yBottomSpace + xDividerHeight / 2;
        float[] xptsx = {
                xLeftSpace + yDividerWidth / 2 + pillarMarginY + xSpaceVertical, xScaleTopY, xLeftSpace + xSpaceVertical + yDividerWidth / 2 + pillarMarginY, xScaleBottomY,
                xLeftSpace + yDividerWidth / 2 + pillarMarginY + xSpaceVertical * 2, xScaleTopY, xLeftSpace + xSpaceVertical * 2 + yDividerWidth / 2 + pillarMarginY, xScaleBottomY,
                xLeftSpace + yDividerWidth / 2 + pillarMarginY + xSpaceVertical * 3, xScaleTopY, xLeftSpace + xSpaceVertical * 3 + yDividerWidth / 2 + pillarMarginY, xScaleBottomY,
                xLeftSpace + yDividerWidth / 2 + pillarMarginY + xSpaceVertical * 4, xScaleTopY, xLeftSpace + xSpaceVertical * 4 + yDividerWidth / 2 + pillarMarginY, xScaleBottomY,
                xLeftSpace + yDividerWidth / 2 + pillarMarginY + xSpaceVertical * 5, xScaleTopY, xLeftSpace + xSpaceVertical * 5 + yDividerWidth / 2 + pillarMarginY, xScaleBottomY,
                xLeftSpace + yDividerWidth / 2 + pillarMarginY + xSpaceVertical * 6, xScaleTopY, xLeftSpace + xSpaceVertical * 6 + yDividerWidth / 2 + pillarMarginY, xScaleBottomY,
                xLeftSpace + yDividerWidth / 2 + pillarMarginY + xSpaceVertical * 7, xScaleTopY, xLeftSpace + xSpaceVertical * 7 + yDividerWidth / 2 + pillarMarginY, xScaleBottomY
        };
        initPaintColor(xDividerColor);
        canvas.drawLines(xptsx, mPaint);

        //繪製X軸刻度下面的文字
        initPaintColor(lineColor);
        mPaint.setTextSize(xyFontSize);
        int[] xs = {1000, 2000, 3000, 4000, 5000, 6000, 7000};
        Rect xNumberBound;
        for (int i = 0; i < xs.length; i++) {
            String number = xs[i] + "";
            xNumberBound = new Rect();
            mPaint.getTextBounds(number, 0, number.length(), xNumberBound);
            canvas.drawText(
                    number,
                    xLeftSpace + yDividerWidth + xSpaceVertical * (i + 1),
                    getHeight() - yBottomSpace + xNumberBound.height() + txtXSpace,
                    mPaint
            );
        }

        /**
         * 繪製六組資料的柱形圖
         */
        //獲取X軸的寬度
        float fullWidth = getWidth() - xLeftSpace - xRightSpace - yDividerWidth / 2 - pillarMarginY;
        for (int x = 0; x < dataList.size(); x++) {
            //柱子Y軸上座標:view高度 - Y軸距離view下邊的距離 - Y軸刻度高度*(x+1) - 柱子高度一半
            float yTop = getHeight() - yBottomSpace - spaceVertical * (x + 1) - pillarHeight / 2;
            //柱子Y軸下座標:view高度 - Y軸距離view下邊的距離 - Y軸刻度高度*(x+1) + 柱子高度一半
            float yBottom = getHeight() - yBottomSpace - spaceVertical * (x + 1) + pillarHeight / 2;
            float xRight = xLeftSpace + yDividerWidth / 2 + pillarMarginY + fullWidth * (dataList.get(x) / FULL_AMOUNT);
            //設定漸變背景
            LinearGradient lg = new LinearGradient(xLeftSpace + xDividerHeight, yTop, xRight, yBottom, Color.parseColor("#45b0ff"), Color.parseColor("#5dcaa9"), Shader.TileMode.MIRROR);
            //Shader就是著色器
            mPaint.setShader(lg);
            //繪製圓角矩形
            canvas.drawRoundRect(
                    new RectF(
                            xLeftSpace + yDividerWidth / 2,
                            yTop,
                            xRight,
                            yBottom
                    ),
                    15,
                    15,
                    mPaint
            );
            //最後將畫筆去除掉Shader
            mPaint.setShader(null);

            initPaintColor(dataFontColor);
            mPaint.setTextAlign(Paint.Align.LEFT);
            String number = dataList.get(x) + "人";
            Rect numRect = new Rect();
            mPaint.getTextBounds(number, 0, number.length(), numRect);
            canvas.drawText(number, xRight + xDividerHeight, getHeight() - yBottomSpace - spaceVertical * (x + 1) + numRect.height() / 2, mPaint
            );
        }

        //按著Y軸刻度右邊繪製一個矩形,遮擋圓角矩形左邊的圓角
        mPaint.setColor(Color.parseColor("#fff9ef"));
        canvas.drawRect(
                new RectF(
                        xLeftSpace + yDividerWidth / 2,
                        0,
                        xLeftSpace + yDividerWidth / 2 + pillarMarginY,
                        getHeight() - yBottomSpace - 10
                ),
                mPaint
        );
    }

    private void initPaintColor(int color) {
        mPaint.setColor(color);
    }

    public void setData(List<Integer> data) {
        dataList.clear();
        dataList.addAll(data);
        invalidate();
    }

}
設定自定義屬性需要在styles.xml中宣告:
    <declare-styleable name="hpillar">
        <!--XY軸顏色-->
        <attr name="line_color_h" format="color"/>
        <!--柱子右邊文字顏色-->
        <attr name="data_font_color_h" format="color" />
        <!--X軸距離view左邊和右邊的距離-->
        <attr name="x_left_space_h" format="dimension" />
        <attr name="x_right_space_h" format="dimension" />
        <!--Y軸距離view上邊和下邊的距離-->
        <attr name="y_bottom_space_h" format="dimension" />
        <attr name="y_top_space_h" format="dimension" />
        <!--X軸刻度的高,Y軸刻度的寬-->
        <attr name="x_divider_height" format="dimension" />
        <attr name="y_divider_width" format="dimension" />
        <!--XY軸刻度顏色-->
        <attr name="x_divider_color" format="color" />
        <attr name="y_divider_color" format="color" />
        <!--刻度文字字型大小-->
        <attr name="xy_font_size_h" format="dimension" />
        <!--軸下面文字離X軸距離-->
        <attr name="txt_x_space" format="dimension" />
        <!--Y軸左邊文字離Y軸距離-->
        <attr name="txt_y_space" format="dimension" />
        <!--每個柱子的高度-->
        <attr name="pillar_height" format="dimension"/>
    </declare-styleable>

這些屬性都有預設初始值,如需修改可以再佈局檔案中設定賦值:
        <com.example.zheng.hpillarview.view.HPillarView
            android:id="@+id/pillar_h"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:background="#fff9ef"
            app:line_color_h="#4eb0f1"
            app:xy_font_size_h="10sp"
            app:data_font_color_h="#5cc9aa"
            app:y_top_space_h="20dp"
            app:x_divider_color="#f8cac2"/>

最後在頁面中使用就非常簡單了(一步搞定):
        //初始化資料
        chartList.add(3999);
        chartList.add(6001);
        chartList.add(1888);
        chartList.add(6666);
        chartList.add(4999);
        chartList.add(2999);

        hPillarView= (HPillarView) findViewById(R.id.pillar_h);
        //設定資料(一步搞定)
        hPillarView.setData(chartList);