**摘要**


本文主要内容是:在Android系统下自定义图形。效果如下图:

android 柱状波浪 android柱状图控件_获取数据

**思路**


如上图所示,我们应该把问题简单化,看上去图片是有规律的,类似一个列表,那么我们就当一列是一个Item吧。

android 柱状波浪 android柱状图控件_android_02


再把一个Item分解: 分成文字和图形部分。

文字部分

文字部分就是对当个柱状图的说明,我们暂且叫他“itemName”吧。我们看到了itemName是右对齐的,那么我们就得获取所有数据的itemName,然后看谁最长,然后计算这段文字有多长,此时,我们就知道了itemName所占的长度了(当个文字的长度 * 文字个数),获取当个文字的长宽方法如下:

/**
     * 获取单个字符的高和宽
     */
    private int[] getTextWH() {
        int[] wh = new int[2];
        // 一个矩形
        Rect rect = new Rect();
        String text = "我";
        Paint paint = new Paint();
        // 设置文字大小
        paint.setTextSize(dip2px(getContext(), 16));
        paint.getTextBounds(text, 0, text.length(), rect);
        wh[0] = rect.width();
        wh[1] = rect.height();
        return wh;
    }

那么我们就得设置文字画笔是右对齐的。

/**
     *绘制文字说明  右对齐
     * @param canvas
     * @param text
     */
    private void drawText(Canvas canvas, String text) {
        int x = getWidth();
        int y = getHeight();

        Paint textPaint = new Paint();
        textPaint.setColor(getResources().getColor(R.color.text_line_chart));
        textPaint.setTextSize(dip2px(getContext(), 16));
        // 设置文字右对齐
        textPaint.setTextAlign(Paint.Align.RIGHT);
        float tX = (x - getFontlength(textPaint, text)) / 2;
        float tY = (y - getFontHeight(textPaint)) / 2 + getFontLeading(textPaint);
        // 注意第二个参数,右对齐,文字是从右开始写的,那么  x 就是对齐处的X坐标
        canvas.drawText(text, mTextW, tY, textPaint);
    }

图形部分

图形部分主要是绘制矩形,那么怎么绘制呢?很简单的嘛: 先获取屏幕宽度SW,然后获取数值的大小DV, SW / DV 就得到了一个单位DV所占的位置了,然后直接canvas.drawRect …..但是,但是 我们是画一个列表的,要对其的,我们要的是数据有参考、对比性的。是吧???那么我们就得算出所有 DV的最大值。然后计算一个DV所占的位置,最后才能绘制。 记得,我们的图形是在 ItemName右边的。那么我们图形的X坐标就是 ItemName所占的宽度开始的。

/**绘制图形
     * @param canvas
     */
    private void drawLine(Canvas canvas) {
        double chart_length = (getWidth() - mTextW) / (double) mMaxV;
        int start_complete_left = mTextW + 10,
                start_complete_top = 4,
                start_complete_right = start_complete_left + (int) (chart_length * mData.getRecover_complete())- dip2px(getContext(), 6),
                start_uncomplete_right = start_complete_left + (int) (chart_length * (mData.getRecover_complete() + mData.getRecover_uncomplete()))- dip2px(getContext(), 6);

        Log.e("TAG", start_complete_left + "..." + start_complete_top + ",,," + start_complete_right + "ds"
                + start_uncomplete_right);
        this.arcPaint = new Paint();
        this.arcPaint.setColor(getResources().getColor(R.color.line_chart_uncomplete));
        this.arcPaint.setAntiAlias(true);// 去除锯齿
        // 绘制未完成的,
        canvas.drawRect(start_complete_left, start_complete_top, start_uncomplete_right, mChartH, arcPaint);

        // 绘制完成的
        this.arcPaint.setColor(getResources().getColor(R.color.line_chart_complete));
        canvas.drawRect(start_complete_left, start_complete_top, start_complete_right, mChartH, arcPaint);
    }

文字加图形

画好一个,那么我们就得把 一个个item 放进列表里面去, 那么就得重写一个LinearLayout, 然后add()上去。 为什么不用ListView或者RecycleView?你一定在想,因为itemName的长度不确定,我们又得右对齐,所以只能用LinearLayout了

public LinChartLayout(Context context) {
        super(context);
        this.setOrientation(VERTICAL);
        setView();
    }

    public void setView() {
        if (mData != null && !mData.isEmpty()) {
            int text_max_length = 0;
            int value_max = 0;
            for (LineChartData data : mData) {
                // 获取最长文字的个数
                if (text_max_length <= data.getName().length()) {
                    text_max_length = data.getName().length();
                }
                // 获取数据值的大小
                int total = data.getRecover_complete() + data.getRecover_uncomplete();

                // 获取数据的值
                if (value_max <= total) {
                    value_max = total;
                }
            }
            int[] wh = getTextWH();
            // 文字区域的宽
            int textAreW = text_max_length * wh[0] + dip2px(getContext(), 10);

            // 图形区域的宽
            int chartAreW = scrW - textAreW - 10;

            LinearLayout.LayoutParams layoutParams = new LayoutParams(scrW - dip2px(getContext(), 10),dip2px(getContext(), 32));
            // 设置居中
            layoutParams.gravity = Gravity.CENTER;
            // 设置Margin
            layoutParams.topMargin = dip2px(getContext(), 4);
            layoutParams.bottomMargin = dip2px(getContext(), 4);

            // 遍历添加LinChartView
            for (LineChartData i : mData) {
                LinChartView chartView = new LinChartView(getContext());
                chartView.setData(textAreW, chartAreW,value_max, i);
                this.addView(chartView, layoutParams);
            }

        }
    }

**全部代码**


下面是一些参考的代码:

LinChartLayout.java

/**
 * @author bishiqiangan@yeah.net
 * 重写一个LinearLayout, 遍历添加LinChartView
 */
public class LinChartLayout extends LinearLayout {

    /**
     *  列表的数据源
     */
    private List<LineChartData> mData;

    /**
     * 屏幕的宽
     *
     */
    private int scrW;

    public LinChartLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public LinChartLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public LinChartLayout(Context context) {
        super(context);
        this.setOrientation(VERTICAL);
        setView();
    }

    public void setView() {
        if (mData != null && !mData.isEmpty()) {
            int text_max_length = 0;
            int value_max = 0;
            for (LineChartData data : mData) {
                // 获取最长文字的个数
                if (text_max_length <= data.getName().length()) {
                    text_max_length = data.getName().length();
                }
                // 获取数据值的大小
                int total = data.getRecover_complete() + data.getRecover_uncomplete();

                // 获取数据的值
                if (value_max <= total) {
                    value_max = total;
                }
            }
            int[] wh = getTextWH();
            // 文字区域的宽
            int textAreW = text_max_length * wh[0] + dip2px(getContext(), 10);

            // 图形区域的宽
            int chartAreW = scrW - textAreW - 10;

            LinearLayout.LayoutParams layoutParams = new LayoutParams(scrW - dip2px(getContext(), 10),dip2px(getContext(), 32));
            // 设置居中
            layoutParams.gravity = Gravity.CENTER;
            // 设置Margin
            layoutParams.topMargin = dip2px(getContext(), 4);
            layoutParams.bottomMargin = dip2px(getContext(), 4);

            // 遍历添加LinChartView
            for (LineChartData i : mData) {
                LinChartView chartView = new LinChartView(getContext());
                chartView.setData(textAreW, chartAreW,value_max, i);
                this.addView(chartView, layoutParams);
            }

        }
    }

    /**
     * 获取单个字符的高和宽
     */
    private int[] getTextWH() {
        int[] wh = new int[2];
        // 一个矩形
        Rect rect = new Rect();
        String text = "我";
        Paint paint = new Paint();
        // 设置文字大小
        paint.setTextSize(dip2px(getContext(), 16));
        paint.getTextBounds(text, 0, text.length(), rect);
        wh[0] = rect.width();
        wh[1] = rect.height();
        return wh;
    }


    public void setData(List<LineChartData> d,int scrw) {
        this.mData = d;
        this.scrW = scrw;
    }

    /**
     * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
     */
    public static int dip2px(Context context, float dpValue) {
     final float scale = context.getResources().getDisplayMetrics().density;
     return (int) (dpValue * scale + 0.5f);
    }



    /**
     * 根据手机的分辨率从 px(像素) 的单位 转成为 dp
     */
    public static int px2dip(Context context, float pxValue) {
     final float scale = context.getResources().getDisplayMetrics().density;
     return (int) (pxValue / scale + 0.5f);
    }

LinChartView.java

/**
 * @author bishiqiangan@yeah.net
 * 重写一个View , 绘制一个横向柱状图
 */
public class LinChartView extends View {

    private LineChartData mData;
    private int mTextW, mChartH, mMaxV;

    private Paint arcPaint = null;

    public LinChartView(Context context) {
        super(context);
    }

    public LinChartView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public LinChartView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mData == null) {
            return;
        }
        // 画文字
        drawText(canvas, mData.getName());

        //画图形
        drawLine(canvas);

    }

    /**绘制图形
     * @param canvas
     */
    private void drawLine(Canvas canvas) {
        double chart_length = (getWidth() - mTextW) / (double) mMaxV;
        int start_complete_left = mTextW + 10,
                start_complete_top = 4,
                start_complete_right = start_complete_left + (int) (chart_length * mData.getRecover_complete())- dip2px(getContext(), 6),
                start_uncomplete_right = start_complete_left + (int) (chart_length * (mData.getRecover_complete() + mData.getRecover_uncomplete()))- dip2px(getContext(), 6);

        Log.e("TAG", start_complete_left + "..." + start_complete_top + ",,," + start_complete_right + "ds"
                + start_uncomplete_right);
        this.arcPaint = new Paint();
        this.arcPaint.setColor(getResources().getColor(R.color.line_chart_uncomplete));
        this.arcPaint.setAntiAlias(true);// 去除锯齿
        // 绘制未完成的,
        canvas.drawRect(start_complete_left, start_complete_top, start_uncomplete_right, mChartH, arcPaint);

        // 绘制完成的
        this.arcPaint.setColor(getResources().getColor(R.color.line_chart_complete));
        canvas.drawRect(start_complete_left, start_complete_top, start_complete_right, mChartH, arcPaint);
    }

    /**
     *绘制文字说明  右对齐
     * @param canvas
     * @param text
     */
    private void drawText(Canvas canvas, String text) {
        int x = getWidth();
        int y = getHeight();

        Paint textPaint = new Paint();
        textPaint.setColor(getResources().getColor(R.color.text_line_chart));
        textPaint.setTextSize(dip2px(getContext(), 16));
        // 设置文字右对齐
        textPaint.setTextAlign(Paint.Align.RIGHT);
        float tX = (x - getFontlength(textPaint, text)) / 2;
        float tY = (y - getFontHeight(textPaint)) / 2 + getFontLeading(textPaint);
        // 注意第二个参数,右对齐,文字是从右开始写的,那么  x 就是对齐处的X坐标
        canvas.drawText(text, mTextW, tY, textPaint);
    }

    /**
     * @return 返回指定笔和指定字符串的长度
     */
    public static float getFontlength(Paint paint, String str) {
        return paint.measureText(str);
    }

    /**
     * @return 返回指定笔的文字高度
     */
    public static float getFontHeight(Paint paint) {
        Paint.FontMetrics fm = paint.getFontMetrics();
        return fm.descent - fm.ascent;
    }

    /**
     * @return 返回指定笔离文字顶部的基准距离
     */
    public static float getFontLeading(Paint paint) {
        Paint.FontMetrics fm = paint.getFontMetrics();
        return fm.leading - fm.ascent;
    }

    public void setData(int textW, int chartW, int max_valur, LineChartData data) {
        Log.e("TAG", max_valur + "...max");
        this.mTextW = textW;
        this.mChartH = chartW;
        this.mMaxV = max_valur;
        this.mData = data;
        this.postInvalidate();
    }

    /**
     * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
     */
    public static int dip2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    /**
     * 根据手机的分辨率从 px(像素) 的单位 转成为 dp
     */
    public static int px2dip(Context context, float pxValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (pxValue / scale + 0.5f);
    }

}

LineChartData.java

/**
 * @author bishiqiangan@yeah.net
 * 数据模型类
 */
public class LineChartData {

    private int recover_complete;
    private String name;
    private int recover_uncomplete;

    public int getRecover_complete() {
        return recover_complete;
    }

    public void setRecover_complete(int recover_complete) {
        this.recover_complete = recover_complete;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getRecover_uncomplete() {
        return recover_uncomplete;
    }

    public void setRecover_uncomplete(int recover_uncomplete) {
        this.recover_uncomplete = recover_uncomplete;
    }
}

调用的方法:
==android:orientation=”vertical”== 记得添加

<com.example.testandroid.LinChartLayout
                android:id="@+id/linechart"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_margin="6dip"
                ==android:orientation="vertical"==
                />