和之前双折线图一样,今天要讲述的单柱和双柱图如出一辙,理解之后原理是一样的,不过这里的算法可能要复杂一些

这里实现单柱和双柱都是通过画布和画笔绘制出来的,和之前折线图api有些差别。如果您觉得自定义麻烦,这里推荐一个GitHub的图表库,还是挺强大的

https://github.com/lecho/hellocharts-android

效果图如下

android多个柱状图和折线图 android柱状图控件_android多个柱状图和折线图

![单柱图]

android多个柱状图和折线图 android柱状图控件_双柱图_02

简要介绍

这里有四个条件筛选,所以绘制图的时候随筛选条件变化而变化,纵坐标不变并且固定,横坐标和柱状图如果数据比较多会左右滑动。点击的时候会弹出柱状图的信息,并且柱状图和文字要加有明显加黑。下面横坐标会显示不下,所以要斜体显示,这里的需求都是我们产品这样设计(遇到这样的产品也是头疼,一个项目不下十个自定义控件),所以我觉得只有自定义才是最好的办法。

这里我们还是一步步的来,先说双柱图,单柱原理和双柱一样,不过在后面我还是会贴出单柱图的代码。大布局包括三部分,四个筛选条件是一个布局,下面是一个大控件,包括固定布局合格率复验率、柱状图纵坐标(固定)、弹出框,和柱状图主要部分chartView(纵坐标也可以画进去,但是需求是固定,故没有画入控件)。

android多个柱状图和折线图 android柱状图控件_android多个柱状图和折线图_03

charView的画法,里面包括两个主要方法onDraw()和onTouchevent(),功能分别是页面绘制和点击事件监听

  1. 具体矩形的大小和他们之间的距离可以自己设定,如果要适配屏幕也可以获取屏幕大小来设定
  2. 先画横坐标的坐标轴,这里加坐标轴有11条背景线,通过控件高度计算距离,和外面纵坐标之间的距离保持一致。下一步也一个难点(这样画斜体字api在网上找了很久才找到),这个画法什么角度的字都能画出来(如弧形,波浪形)
  3. 画柱状图时,首先先话百分比文字,计算好高度即可。下一步画百分比为0的时候一条黑色横线。最后一步画出填充色的矩形图即可,得计算宽高和双柱图中间的间隙。
  4. 监听点击事件时,这里通过手指触摸到屏幕的坐标,来计算坐标是在哪个双主图范围内,通过循环来找出双柱图集合下标并存储(重新绘制时会使用),调用invalidate()方法重新绘制,重新调用onDraw()会改变横坐标文字颜色和给矩形加边框,达到界面效果。
  5. 点击后回调给主控件,返回双柱图高点坐标x,y,主控件(包括固定布局和charView)通过坐标加载出弹框位置。

charView控件

package com.pcjz.dems.widget.chatview;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;

import com.pcjz.dems.R;
import com.pcjz.dems.common.utils.StringUtils;
import com.pcjz.dems.entity.reportform.ReportformRate;
import com.pcjz.dems.entity.will.ProjectAcceptanceStatisticsModel;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;


public class MyChartView1 extends View {

    private int leftColor;//双柱左侧
    private int rightColor;//双柱右侧
    private int lineColor;//横轴线
    private int selectLeftColor;//点击选中左侧
    private int selectRightColor;//点击选中右侧
    private int lefrColorBottom;//左侧底部
    private int rightColorBottom;//右侧底部
    private Paint mPaint, mChartPaint, mShadowPaint;//横轴画笔、柱状图画笔、阴影画笔
    private int mWidth, mHeight, mStartWidth, mChartWidth, mSize;//屏幕宽度高度、柱状图起始位置、柱状图宽度
    private Rect mBound;
    private List<Float> list = new ArrayList<>();//柱状图高度占比
    private getNumberListener listener;//点击接口
    private int selectIndex = -1;//点击选中柱状图索引
    private int textSelectIndex = -1;//点击文字选中的索引
    private List<Integer> selectIndexRoles = new ArrayList<>();
    private List<Integer> textSelectIndexs = new ArrayList<>();
    private Paint mStroke;
    private Paint mPercent;
    private List<ReportformRate> mdatas;

    public void setList(List<Float> list) {
        this.list = list;
        //柱状图的宽度
        mSize = dp2px(20);
        mStartWidth = dp2px(5) + mSize;
        mChartWidth = dp2px(5);
        invalidate();
    }

    public void setMdatas(List<ReportformRate> mdatas) {
        this.mdatas = mdatas;
    }

    public void setListener(getNumberListener listener) {
        this.listener = listener;
    }

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

    public MyChartView1(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyChartView1(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //获取我们自定义的样式属性
        TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyChartView1, defStyleAttr, 0);
        int n = array.getIndexCount();
        for (int i = 0; i < n; i++) {
            int attr = array.getIndex(i);
            switch (attr) {
                case R.styleable.MyChartView1_leftColor:
                    // 默认颜色设置为黑色
                    leftColor = array.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.MyChartView1_selectLeftColor:
                    // 默认颜色设置为黑色
                    selectLeftColor = array.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.MyChartView1_rightColor:
                    rightColor = array.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.MyChartView1_selectRightColor:
                    selectRightColor = array.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.MyChartView1_xyColor:
                    lineColor = array.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.MyChartView1_leftColorBottom:
                    lefrColorBottom = array.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.MyChartView1_rightColorBottom:
                    rightColorBottom = array.getColor(attr, Color.BLACK);
                    break;
            }
        }
        array.recycle();
        initSelects();
        init();
    }

    private void initSelects() {
        if (selectIndexRoles.size() != 0) {
            selectIndexRoles.clear();
        }
        if (textSelectIndexs.size() != 0) {
            textSelectIndexs.clear();
        }
    }

    //初始化画笔
    private void init() {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setTextSize(35);
        mBound = new Rect();
        mChartPaint = new Paint();
        mChartPaint.setAntiAlias(true);
        mShadowPaint = new Paint();
        mShadowPaint.setAntiAlias(true);
        mShadowPaint.setColor(Color.WHITE);
        mStroke = new Paint();
        mStroke.setAntiAlias(true);
        mStroke.setStyle(Paint.Style.STROKE);
        mStroke.setStrokeWidth(3);
        mStroke.setColor(Color.parseColor("#333333"));
        mPercent = new Paint();
        mPercent.setAntiAlias(true);
    }

    //测量高宽度
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width;
        int height;
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

       /* if (widthMode == MeasureSpec.EXACTLY) {
            width = widthSize;
        } else {
            width = widthSize * 1 / 2;
        }
        if (heightMode == MeasureSpec.EXACTLY) {
            height = heightSize;
        } else {
            height = heightSize * 1 / 2;
        }*/
        width = widthSize;
        height = heightSize;

        setMeasuredDimension(width, height);
    }

    /*//计算高度宽度
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        initView();
    }*/

    private void initView(Canvas canvas) {
        mWidth = getWidth();
        mHeight = getHeight();
        mSize = dp2px(20);
        mStartWidth = dp2px(5) + mSize;
        mChartWidth = dp2px(5);
    }


    //重写onDraw绘制
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        initView(canvas);
        mPaint.setColor(lineColor);
        //画坐标轴
        for (int i = 0; i <= 10; i++) {
            mPaint.setTextSize(dp2px(11));
            canvas.drawLine(0, (mHeight * 24 / 30) - (mHeight * 18 / 30) * i / 10, mWidth, (mHeight * 24 / 30) - (mHeight * 18 / 30) * i / 10, mPaint);
        }
        if (mdatas != null) {
            List<String> periodName = new ArrayList<>();
            for (int i = 0; i < mdatas.size(); i++) {
                if (mdatas.get(i).getPeriodName() != null) {
                    periodName.add(mdatas.get(i).getPeriodName());
                } else if (mdatas.get(i).getTeamName() != null) {
                    periodName.add(mdatas.get(i).getTeamName());
                } else if (mdatas.get(i).getCorporateName() != null) {
                    periodName.add(mdatas.get(i).getCorporateName());
                }
            }
            if (periodName.size() != 0) {
                for (int i = 0; i < periodName.size(); i++) {
                    //画数字
                    mPaint.setTextSize(dp2px(10));
                    mPaint.setTextAlign(Paint.Align.LEFT);
                    //获得textview的高度或宽度 计算文字所在的矩形mBound 来得到文字的宽高
                    mPaint.getTextBounds("月12345678", 0, String.valueOf(i).length(), mBound);
                    /*float[] pos = new float[20];
                    for (int j = 0; j < 20; j++) {
                        if (j % 2 == 0) {
                            pos[j] = mStartWidth + mBound.height() * j / 2;
                        } else {
                            pos[j] = mHeight * 24 / 30 + mBound.height() + mBound.height() * j / 2;
                        }
                    }*/
                    mPaint.getTextBounds("安托山花园一期一栋一层", 0, String.valueOf(i).length(), mBound);
                    Path paths = new Path();
                    paths.moveTo(mStartWidth - mBound.height() / 2, mHeight * 24 / 30 + mBound.height());
                    paths.lineTo(mStartWidth + mBound.height() * 10, mHeight * 24 / 30 + mBound.height() * 10);
                    if (textSelectIndexs.contains(i)) {
                        mPaint.setColor(Color.parseColor("#333333"));
                        textSelectIndexs.clear();
                    } else {
                        mPaint.setColor(lineColor);
                    }
                    //canvas.drawPosText(periodName.get(i), pos, mPaint);
                    canvas.drawTextOnPath(periodName.get(i), paths, 0, 0, mPaint);
                    mStartWidth += 5 + 3 * mSize;
                }
            }
            //画柱状图
            for (int i = 0; i < list.size(); i++) {
                //添加百分比
                mPercent.setTextSize(dp2px(8));
                mPercent.setTextAlign(Paint.Align.CENTER);
                DecimalFormat df = new DecimalFormat("#.#");
                String format = df.format(list.get(i));
                canvas.drawText(format + "%", mChartWidth + mSize / 2, (mHeight * 18 / 30) * (100 - list.get(i)) / 100 + mHeight * 6 / 30 - 10, mPercent);
                //画柱状图
                mChartPaint.setStyle(Paint.Style.FILL);
                if (StringUtils.equals(format, "0")) {
                    canvas.drawLine(mChartWidth, mHeight * 24 / 30, mChartWidth + mSize, mHeight * 24 / 30, mStroke);
                }
                //如果包含就变色
                if (selectIndexRoles.contains(i)) {
                    canvas.drawRect(mChartWidth, (mHeight * 18 / 30) * (100 - list.get(i)) / 100 + mHeight * 6 / 30, mChartWidth + mSize, mHeight * 24 / 30, mStroke);
                    //重新请求数据时避免之前点击的柱状图还处于变色状态
                    if (i % 2 == 1) {
                        selectIndexRoles.clear();
                    }
                } else {

                }
                if (i % 2 == 0) {
                    mChartPaint.setColor(selectLeftColor);
                } else {
                    mChartPaint.setColor(selectRightColor);
                }
                //画柱状图
                //左上的坐标和右下的坐标确定矩形
                canvas.drawRect(mChartWidth, (mHeight * 18 / 30) * (100 - list.get(i)) / 100 + mHeight * 6 / 30, mChartWidth + mSize, mHeight * 24 / 30, mChartPaint);
                // ;// 长方形
                mChartWidth += (i % 2 == 0) ? (5 + mSize) : (2 * mSize);
            }
        }
    }

    @Override
    public void onWindowFocusChanged(boolean hasWindowFocus) {
        super.onWindowFocusChanged(hasWindowFocus);
        if (hasWindowFocus) {

        }
    }

    /**
     * 注意:
     * 当屏幕焦点变化时重新侧向起始位置,必须重写次方法,否则当焦点变化时柱状图会跑到屏幕外面
     */

    @Override
    protected void onWindowVisibilityChanged(int visibility) {
        super.onWindowVisibilityChanged(visibility);
        if (visibility == VISIBLE) {
            mSize = dp2px(20);
            mStartWidth = dp2px(5) + mSize;
            mChartWidth = dp2px(5);
        }
    }

    /**
     * 柱状图touch事件
     * 获取触摸位置计算属于哪个月份的
     *
     * @param ev
     * @return
     */
    @Override
    public boolean onTouchEvent(MotionEvent ev) {

        int x = (int) ev.getX();
        int y = (int) ev.getY();
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                int mChartWidthd = dp2px(5);
                for (int i = 0; i < list.size(); i++) {
                    int x1 = mChartWidthd;
                    int x2 = mChartWidthd + mSize;
                    int y1 = (int) ((mHeight * 18 / 30) * (100 - list.get(i)) / 100 + mHeight * 6 / 30);
                    int y2 = mHeight * 24 / 30;
                    if (x >= x1 && x <= x2 && y >= y1 && y <= y2) {
                        if (i % 2 == 0) {
                            selectIndex = i;
                            //传入mData中的i, 和两个圆柱中间位置
                            textSelectIndex = i / 2;
                            if (list.get(i + 1) != null) {
                                if (list.get(i + 1) - list.get(i) >= 0){
                                    listener.getNumber(i / 2, x2, (int) ((mHeight * 18 / 30) * (100 - list.get(i + 1)) / 100 + mHeight * 6 / 30 - 10));
                                } else {
                                    listener.getNumber(i / 2, x2, y1 - 10);
                                }
                            }

                        } else {
                            selectIndex = i - 1;
                            textSelectIndex = (i - 1) / 2;
                            if (list.get(i) - list.get(i - 1) >= 0) {
                                //传入下标,横坐标中间和纵坐标上面的位置
                                listener.getNumber((i - 1) / 2, x2 - mSize - 3, y1 - 10);
                            } else {
                                listener.getNumber((i - 1) / 2, x2 - mSize - 3, (int) ((mHeight * 18 / 30) * (100 - list.get(i - 1)) / 100 + mHeight * 6 / 30 - 10));
                            }
                        }
                        selectIndexRoles.clear();
                        textSelectIndexs.clear();
                        selectIndexRoles.add(selectIndex);
                        selectIndexRoles.add(selectIndex + 1);
                        textSelectIndexs.add(textSelectIndex);
                        invalidate();
                    }
                    mChartWidthd += (i % 2 == 0) ? (5 + mSize) : (2 * mSize);
                }
                break;
            case MotionEvent.ACTION_UP:

                break;
        }
        return true;
    }

    private int dp2px(int dp) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
    }

    public interface getNumberListener {
        void getNumber(int number, int x, int y);
    }

    public int getLeftColor() {
        return leftColor;
    }

    public void setLeftColor(int leftColor) {
        this.leftColor = leftColor;
    }

    public int getRightColor() {
        return rightColor;
    }

    public void setRightColor(int rightColor) {
        this.rightColor = rightColor;
    }

    public int getLineColor() {
        return lineColor;
    }

    public void setLineColor(int lineColor) {
        this.lineColor = lineColor;
    }

    public int getSelectLeftColor() {
        return selectLeftColor;
    }

    public void setSelectLeftColor(int selectLeftColor) {
        this.selectLeftColor = selectLeftColor;
    }

    public int getSelectRightColor() {
        return selectRightColor;
    }

    public void setSelectRightColor(int selectRightColor) {
        this.selectRightColor = selectRightColor;
    }

    public int getLefrColorBottom() {
        return lefrColorBottom;
    }

    public void setLefrColorBottom(int lefrColorBottom) {
        this.lefrColorBottom = lefrColorBottom;
    }

    public int getRightColorBottom() {
        return rightColorBottom;
    }

    public void setRightColorBottom(int rightColorBottom) {
        this.rightColorBottom = rightColorBottom;
    }
}

主控件

package com.pcjz.dems.widget.chatview;

import android.content.Context;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.pcjz.dems.R;
import com.pcjz.dems.entity.reportform.ReportformRate;
import com.pcjz.dems.entity.will.ProjectAcceptanceStatisticsModel;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;

public class HistogramView extends LinearLayout {
    private MyChartView1 myChartView;
    private List<Float> chartList;
    private RelativeLayout relativeLayout;
    private LinearLayout llChart;
    private Context mContext;
    public List<ReportformRate> mData;

    public HistogramView(Context context) {
        super(context, null);
    }

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

    public HistogramView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.mContext = context;
    }

    public void refreshRecView(List<ReportformRate> data) {
        this.mData = data;
        View view = View.inflate(mContext, R.layout.view_histogram, null);
        LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT,
                LayoutParams.MATCH_PARENT);
        setOrientation(VERTICAL);
        addView(view, lp);
        myChartView = (MyChartView1) findViewById(R.id.my_chart_view);
        RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) myChartView.getLayoutParams();
        params.width = initWidth(mData);
        myChartView.setLayoutParams(params);
        initChatView();
        postInvalidate();
    }

    private int initWidth(List<ReportformRate> mData) {
        WindowManager wm = (WindowManager) getContext()
                .getSystemService(Context.WINDOW_SERVICE);
        int screenWidth = wm.getDefaultDisplay().getWidth();
        int width = dp2px(40) + (5 + 3 * dp2px(20)) * mData.size() + dp2px(20);
        if (width < screenWidth) {
            return screenWidth;
        } else {
            return width;
        }
    }

    private void initChatView() {
        myChartView.setMdatas(mData);
        myChartView.setLefrColorBottom(ContextCompat.getColor(mContext, R.color.leftColorBottom));
        myChartView.setLeftColor(ContextCompat.getColor(mContext, R.color.left_color));
        myChartView.setRightColor(ContextCompat.getColor(mContext, R.color.right_color));
        myChartView.setRightColorBottom(ContextCompat.getColor(mContext, R.color.rightColor));
        myChartView.setSelectLeftColor(ContextCompat.getColor(mContext, R.color.left_color));
        myChartView.setSelectRightColor(ContextCompat.getColor(mContext, R.color.right_color));
        myChartView.setLineColor(ContextCompat.getColor(mContext, R.color.txt_gray_shallow));
        chartList = new ArrayList<>();
        relativeLayout = (RelativeLayout) findViewById(R.id.linearLayout);
        relativeLayout.removeView(llChart);
        List<Float> reviewRateList = new ArrayList<>();
        List<Float> qualifiedRateList = new ArrayList<>();
        for (int i = 0; i < mData.size(); i++) {
            reviewRateList.add(mData.get(i).getReviewRate() * 100);
            qualifiedRateList.add(mData.get(i).getQualifiedRate() * 100);
        }
        for (int i = 0; i < mData.size(); i++) {
            chartList.add(qualifiedRateList.get(i));
            chartList.add(reviewRateList.get(i));
        }
        myChartView.setList(chartList);
        myChartView.setListener(new MyChartView1.getNumberListener() {
            @Override
            public void getNumber(int number, int x, int y) {
                relativeLayout.removeView(llChart);
                //反射加载点击柱状图弹出布局
                llChart = (LinearLayout) LayoutInflater.from(mContext).inflate(R.layout.test_layout_shouru_zhichu, null);
                TextView tvPeriodName = (TextView) llChart.findViewById(R.id.tv_PeriodName);
                TextView tvQualifiedRate = (TextView) llChart.findViewById(R.id.tv_QualifiedRate);
                TextView tvReviewRate = (TextView) llChart.findViewById(R.id.tv_ReviewRate);
                LinearLayout linearLayout = (LinearLayout) llChart.findViewById(R.id.ll_kuang);
                if (null != mData) {
                    if (mData.get(number).getPeriodName() != null) {
                        tvPeriodName.setText(mData.get(number).getPeriodName());
                    } else if (mData.get(number).getCorporateName() != null) {
                        tvPeriodName.setText(mData.get(number).getCorporateName());
                    } else if (mData.get(number).getTeamName() != null) {
                        tvPeriodName.setText(mData.get(number).getTeamName());
                    }
                    DecimalFormat df = new DecimalFormat("#.#");
                    tvQualifiedRate.setText("合格率:" + df.format(mData.get(number).getQualifiedRate() * 100) + "%");
                    tvReviewRate.setText("复验率:" + df.format(mData.get(number).getReviewRate() * 100) + "%");
                    llChart.measure(0, 0);//调用该方法后才能获取到布局的宽度
                    RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT,
                            RelativeLayout.LayoutParams.WRAP_CONTENT);
                    if (x - llChart.getMeasuredWidth() / 2 < 0) {
                        //如果到达了左边界,则为负值
                        params.leftMargin = 0;
                        linearLayout.setBackground(ContextCompat.getDrawable(getContext(), R.mipmap.kuang1));
                    } else {
                        params.leftMargin = x - llChart.getMeasuredWidth() / 2;
                        linearLayout.setBackground(ContextCompat.getDrawable(getContext(), R.drawable.kuang));
                    }
                    if (y - llChart.getMeasuredHeight() < 0) {
                        params.topMargin = 0;
                    } else {
                        params.topMargin = y - llChart.getMeasuredHeight();
                    }
                    llChart.setLayoutParams(params);
                }
                relativeLayout.addView(llChart);
            }
        });
    }

    private int dp2px(int dp) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
    }
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:id="@+id/ll_layout">


    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="5dp">

        <TextView
            android:layout_width="20dp"
            android:layout_height="20dp"
            android:layout_marginRight="5dp"
            android:layout_toLeftOf="@+id/tv_accepte"
            android:background="@color/leftColorBottom" />

        <TextView
            android:id="@+id/tv_accepte"
            android:layout_width="wrap_content"
            android:layout_height="20dp"
            android:layout_alignParentRight="true"
            android:layout_marginRight="10dp"
            android:gravity="center"
            android:text="合格率"
            android:textSize="14sp" />

    </RelativeLayout>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="5dp">

        <TextView
            android:layout_width="20dp"
            android:layout_height="20dp"
            android:layout_marginRight="5dp"
            android:layout_toLeftOf="@+id/tv_again_accept"
            android:background="@color/rightColor" />

        <TextView
            android:id="@+id/tv_again_accept"
            android:layout_width="wrap_content"
            android:layout_height="20dp"
            android:layout_alignParentRight="true"
            android:layout_marginRight="10dp"
            android:gravity="center"
            android:text="复验率"
            android:textSize="14sp" />
    </RelativeLayout>

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal">

        <LinearLayout
            android:layout_width="40dp"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <TextView
                style="@style/ReportformsNumber"
                android:layout_weight="5"
                android:paddingLeft="3dp"
                android:paddingBottom="5dp"
                android:gravity="bottom"
                android:text="百分比" />

            <TextView
                style="@style/ReportformsNumber"
                android:text="100" />

            <TextView
                style="@style/ReportformsNumber"
                android:text="90" />

            <TextView
                style="@style/ReportformsNumber"
                android:text="80" />

            <TextView
                style="@style/ReportformsNumber"
                android:text="70" />

            <TextView
                style="@style/ReportformsNumber"
                android:text="60" />

            <TextView
                style="@style/ReportformsNumber"
                android:text="50" />

            <TextView
                style="@style/ReportformsNumber"
                android:text="40" />

            <TextView
                style="@style/ReportformsNumber"
                android:text="30" />

            <TextView
                style="@style/ReportformsNumber"
                android:text="20" />

            <TextView
                style="@style/ReportformsNumber"
                android:text="10" />

            <TextView
                style="@style/ReportformsNumber"
                android:text="0"
                android:layout_weight="0.9"/>

            <TextView
                android:layout_width="match_parent"
                android:layout_height="0dp"
                android:layout_weight="6" />
        </LinearLayout>

        <HorizontalScrollView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginLeft="40dp"
            android:scrollbars="none">

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:orientation="horizontal">

                <RelativeLayout
                    android:id="@+id/linearLayout"
                    android:layout_width="wrap_content"
                    android:layout_height="match_parent">

                    <com.pcjz.dems.widget.chatview.MyChartView1
                        android:id="@+id/my_chart_view"
                        android:layout_width="wrap_content"
                        android:layout_height="match_parent" />
                </RelativeLayout>
            </LinearLayout>
        </HorizontalScrollView>
    </FrameLayout>
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:id="@+id/ll_kuang"
    android:background="@drawable/kuang"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">
    <TextView
        android:id="@+id/tv_PeriodName"
        android:textSize="12sp"
        android:text="龙光花园一期"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

    <TextView
        android:id="@+id/tv_QualifiedRate"
        android:text="合格率:73"
        android:textSize="12sp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

    <TextView
        android:textSize="12sp"
        android:id="@+id/tv_ReviewRate"
        android:text="复验率:4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

</LinearLayout>

单柱图画法

单柱图相对双柱图要简单很多,我这里单柱图是根据双柱图延伸出来的

package com.pcjz.dems.widget.chatview;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;

import com.pcjz.dems.R;
import com.pcjz.dems.common.utils.StringUtils;
import com.pcjz.dems.entity.reportform.inspector.InspectorRate;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;

/**
 * created by yezhengyu on 2017/8/29 09:40
 */

public class SingleChartView extends View{
    private Paint mPaint, mChartPaint, mPercent, mStroke;
    private Rect mBound;
    private int mStartWidth, mHeight, mWidth, mChartWidth, mSize;
    private int lineColor, selectColor;
    private List<Float> list = new ArrayList<>();
    private getNumberListener listener;
    private int selectIndex = -1;
    private List<Integer> selectIndexRoles = new ArrayList<>();
    private List<InspectorRate> mDatas;

    public void setList(List<Float> list) {
        this.list = list;
        //柱状图的宽度
        mSize = dp2px(24);
        mStartWidth = dp2px(15) + mSize / 2;
        mChartWidth = dp2px(15);
        invalidate();
    }

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

    public SingleChartView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SingleChartView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyChartView1, defStyleAttr, 0);
        int n = array.getIndexCount();
        for (int i = 0; i < n; i++) {
            int attr = array.getIndex(i);
            switch (attr) {
                case R.styleable.MyChartView1_xyColor:
                    lineColor = array.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.MyChartView1_selectLeftColor:
                    // 默认颜色设置为黑色
                    selectColor = array.getColor(attr, Color.BLACK);
                    break;
                default:
                    bringToFront();
            }
        }
        array.recycle();
        if (selectIndexRoles.size() != 0) {
            selectIndexRoles.clear();
        }
        init();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width;
        int height;
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        width = widthSize;
        height = heightSize;
        setMeasuredDimension(width, height);
    }

    private void initView() {
        mWidth = getWidth();
        mHeight = getHeight();
        mSize = dp2px(24);
        mStartWidth = dp2px(15) + mSize / 2;
        mChartWidth = dp2px(15);
    }

    private void init() {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mBound = new Rect();
        mChartPaint = new Paint();
        mChartPaint.setAntiAlias(true);
        mPercent = new Paint();
        mPercent.setAntiAlias(true);
        mStroke = new Paint();
        mStroke.setAntiAlias(true);
        mStroke.setStyle(Paint.Style.STROKE);
        mStroke.setStrokeWidth(3);
        mStroke.setColor(Color.parseColor("#333333"));
    }

    @Override
    public void onWindowFocusChanged(boolean hasWindowFocus) {
        super.onWindowFocusChanged(hasWindowFocus);
        if (hasWindowFocus) {

        }
    }

    @Override
    protected void onWindowVisibilityChanged(int visibility) {
        super.onWindowVisibilityChanged(visibility);
        if (visibility == VISIBLE) {
            mSize = dp2px(24);
            mStartWidth = dp2px(15) + mSize / 2;
            mChartWidth = dp2px(15);
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        initView();
        mPaint.setColor(lineColor);
        //画坐标轴
        for (int i = 0; i <= 10; i++) {
            mPaint.setTextSize(dp2px(11));
            canvas.drawLine(0, (mHeight * 25 / 30) - (mHeight * 2 / 3) * i / 10, mWidth, (mHeight * 25 / 30) - (mHeight * 2 / 3) * i / 10, mPaint);
        }
        if (mDatas != null) {
            List<String> name = new ArrayList<>();
            for (int i = 0; i < mDatas.size(); i++) {
                if (mDatas.get(i).getUserName() != null) {
                    name.add(mDatas.get(i).getUserName());
                } else if (mDatas.get(i).getCorporateName() != null) {
                    name.add(mDatas.get(i).getCorporateName());
                }
            }
            if (name.size() != 0) {
                for (int i = 0; i < name.size(); i++) {
                    mPaint.setTextSize(dp2px(11));
                    mPaint.setTextAlign(Paint.Align.CENTER);
                    //获得textview的高度或宽度 计算文字所在的矩形mBound 来得到文字的宽高
                    mPaint.getTextBounds(String.valueOf(i + 1) + "月", 0, String.valueOf(i).length(), mBound);
                    if (selectIndexRoles.contains(i)) {
                        mPaint.setColor(Color.parseColor("#333333"));
                    } else {
                        mPaint.setColor(lineColor);
                    }
                    String text;
                    if (name.get(i).length() <= 4) {
                        text = name.get(i);
                    } else {
                        text = name.get(i).substring(0, 4) + "..";
                    }
                    canvas.drawText(text, mStartWidth, mHeight * 25 / 30 + 2 * mBound.height(), mPaint);
                    mStartWidth += mSize + 3 * mSize / 2;
                }
            }

            //画柱状图
            for (int i = 0; i < list.size(); i++) {
                //添加百分比
                mPercent.setTextSize(dp2px(8));
                mPercent.setTextAlign(Paint.Align.CENTER);
                DecimalFormat df = new DecimalFormat("#.#");
                String format = df.format(list.get(i));
                canvas.drawText(format + "%", mChartWidth + mSize / 2, (mHeight * 2 / 3) * (100 - list.get(i)) / 100 + mHeight * 5 / 30 - 10, mPercent);
                mChartPaint.setStyle(Paint.Style.FILL);
                if (StringUtils.equals(format, "0")) {
                    canvas.drawLine(mChartWidth, mHeight * 25 / 30, mChartWidth + mSize, mHeight * 25 / 30, mStroke);
                }
                //如果包含就变色
                if (selectIndexRoles.contains(i)) {
                    canvas.drawRect(mChartWidth, (mHeight * 2 / 3) * (100 - list.get(i)) / 100 + mHeight * 5 / 30, mChartWidth + mSize, mHeight * 25 / 30, mStroke);
                    selectIndexRoles.clear();
                } else {

                }
                mChartPaint.setColor(selectColor);
                //画柱状图
                //左上的坐标和右下的坐标确定矩形
                canvas.drawRect(mChartWidth, (mHeight * 2 / 3) * (100 - list.get(i)) / 100 + mHeight * 5 / 30, mChartWidth + mSize, mHeight * 25 / 30, mChartPaint);
                // ;// 长方形
                mChartWidth += mSize + 3 * mSize / 2;
            }
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {

        int x = (int) ev.getX();
        int y = (int) ev.getY();
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                int mChartWidthd = dp2px(15);
                for (int i = 0; i < list.size(); i++) {
                    int x1 = mChartWidthd;
                    int x2 = mChartWidthd + mSize;
                    int y1 = (int) ((mHeight * 2 / 3) * (100 - list.get(i)) / 100 + mHeight * 5 / 30);
                    int y2 = mHeight * 25 / 30;
                    if (x >= x1 && x <= x2 && y >= y1 && y <= y2) {
                        selectIndex = i;
                        //传入mData中的i, 和两个圆柱中间位置, 和圆柱的顶点坐标
                        listener.getNumber(i, x2 - mSize / 2, y1 - 15);
                        selectIndexRoles.clear();
                        selectIndexRoles.add(selectIndex);
                        invalidate();
                    }
                    mChartWidthd += mSize + 3 * mSize / 2;
                }
                break;
            case MotionEvent.ACTION_UP:

                break;
        }
        return true;
    }

    public void setMdatas(List<InspectorRate> mDatas) {
        this.mDatas = mDatas;
        invalidate();
    }

    public void setListener(getNumberListener listener) {
        this.listener = listener;
    }

    public interface getNumberListener {
        void getNumber(int number, int x, int y);
    }

    private int dp2px(int dp) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
    }

    public int getLineColor() {
        return lineColor;
    }

    public void setLineColor(int lineColor) {
        this.lineColor = lineColor;
    }

    public int getSelectColor() {
        return selectColor;
    }

    public void setSelectColor(int selectColor) {
        this.selectColor = selectColor;
    }
}
package com.pcjz.dems.widget.chatview;

import android.content.Context;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.pcjz.dems.R;
import com.pcjz.dems.common.utils.StringUtils;
import com.pcjz.dems.entity.reportform.inspector.InspectorRate;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;

/**
 * created by yezhengyu on 2017/8/30 16:02
 */

public class SingleHistogramView extends LinearLayout {

    private Context mContext;
    private List<InspectorRate> mData;
    private SingleChartView mSingleView;
    private List<Float> chartList;
    private RelativeLayout relativeLayout;
    private LinearLayout llChart;
    private String type;

    public SingleHistogramView(Context context) {
        super(context, null);
    }

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

    public SingleHistogramView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.mContext = context;
    }

    public void refreshRecView(List<InspectorRate> mData, String type) {
        this.type = type;
        this.mData = mData;
        View view = View.inflate(mContext, R.layout.view_single_histogram, null);
        LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT,
                LayoutParams.MATCH_PARENT);
        setOrientation(VERTICAL);
        addView(view, lp);
        mSingleView = (SingleChartView) findViewById(R.id.my_single_chart_view);
        RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) mSingleView.getLayoutParams();
        params.width = initWidth(mData);
        mSingleView.setLayoutParams(params);
        initChatView();
        postInvalidate();
    }

    private int initWidth(List<InspectorRate> mData) {
        WindowManager wm = (WindowManager) getContext()
                .getSystemService(Context.WINDOW_SERVICE);
        int screenWidth = wm.getDefaultDisplay().getWidth();
        int width = dp2px(55) +  5 * dp2px(24) * mData.size() / 2;
        if (width < screenWidth) {
            return screenWidth;
        } else {
            return width;
        }
    }


    private void initChatView() {
        mSingleView.setMdatas(mData);
        mSingleView.setLineColor(ContextCompat.getColor(mContext, R.color.txt_gray_shallow));
        mSingleView.setSelectColor(ContextCompat.getColor(mContext, R.color.left_color));
        chartList = new ArrayList<>();
        relativeLayout = (RelativeLayout) findViewById(R.id.linearLayout);
        relativeLayout.removeView(llChart);
        List<Float> rateList = new ArrayList<>();
        if (StringUtils.equals(type, "quality")) {
            for (int i = 0; i < mData.size(); i++) {
                float conformRate = mData.get(i).getConformRate();
                if ( conformRate >= 0 && conformRate <= 1) {
                    rateList.add(mData.get(i).getConformRate() * 100);
                } else {
                    rateList.add(1.0F * 100);
                }
            }
        } else {
            for (int i = 0; i < mData.size(); i++) {
                float qualifiedRate = mData.get(i).getQualifiedRate();
                rateList.add(qualifiedRate * 100);
            }
        }
        mSingleView.setList(rateList);
        mSingleView.setListener(new SingleChartView.getNumberListener() {
            @Override
            public void getNumber(int number, int x, int y) {
                relativeLayout.removeView(llChart);
                //反射加载点击柱状图弹出布局
                llChart = (LinearLayout) LayoutInflater.from(mContext).inflate(R.layout.test_layout_shouru_zhichu, null);
                TextView tvPeriodName = (TextView) llChart.findViewById(R.id.tv_PeriodName);
                TextView tvQualifiedRate = (TextView) llChart.findViewById(R.id.tv_QualifiedRate);
                LinearLayout linearLayout = (LinearLayout) llChart.findViewById(R.id.ll_kuang);
                llChart.findViewById(R.id.tv_ReviewRate).setVisibility(GONE);
                if (mData != null) {
                    if (mData.get(number).getUserName() != null) {
                        tvPeriodName.setText(mData.get(number).getUserName());
                    } else if (mData.get(number).getCorporateName() != null) {
                        tvPeriodName.setText(mData.get(number).getCorporateName());
                    }
                    DecimalFormat df = new DecimalFormat("#.#");
                    float rate;
                    if (type.equals("quality")) {
                        float conformRate = mData.get(number).getConformRate();
                        if (conformRate >= 0 && conformRate <= 1) {
                            rate = conformRate * 100;
                        } else {
                            rate = 1.0F * 100;
                        }
                        tvQualifiedRate.setText("符合率:" + df.format(rate) + "%");
                    } else {
                        float qualifiedRate = mData.get(number).getQualifiedRate();
                        rate = qualifiedRate * 100;
                        tvQualifiedRate.setText("合格率:" + df.format(rate) + "%");
                    }
                }
                llChart.measure(0, 0);//调用该方法后才能获取到布局的宽度
                RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT,
                        RelativeLayout.LayoutParams.WRAP_CONTENT);
                if (x - llChart.getMeasuredWidth() / 2 < 0) {
                    //如果到达了左边界,则为负值
                    params.leftMargin = 0;
                    linearLayout.setBackground(ContextCompat.getDrawable(getContext(), R.mipmap.kuang1));
                } else {
                    params.leftMargin = x - llChart.getMeasuredWidth() / 2;
                    linearLayout.setBackground(ContextCompat.getDrawable(getContext(), R.drawable.kuang));
                }
                params.topMargin = y - llChart.getMeasuredHeight();
                llChart.setLayoutParams(params);
                relativeLayout.addView(llChart);
            }
        });
    }

    private int dp2px(int dp) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
    }
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:id="@+id/ll_layout">

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal">

        <LinearLayout
            android:layout_width="40dp"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <TextView
                style="@style/SingleNumber"
                android:layout_weight="4"
                android:paddingLeft="3dp"
                android:paddingBottom="5dp"
                android:gravity="bottom"
                android:text="百分比" />

            <TextView
                style="@style/SingleNumber"
                android:text="100" />

            <TextView
                style="@style/SingleNumber"
                android:text="90" />

            <TextView
                style="@style/SingleNumber"
                android:text="80" />

            <TextView
                style="@style/SingleNumber"
                android:text="70" />

            <TextView
                style="@style/SingleNumber"
                android:text="60" />

            <TextView
                style="@style/SingleNumber"
                android:text="50" />

            <TextView
                style="@style/SingleNumber"
                android:text="40" />

            <TextView
                style="@style/SingleNumber"
                android:text="30" />

            <TextView
                style="@style/SingleNumber"
                android:text="20" />

            <TextView
                style="@style/SingleNumber"
                android:text="10" />

            <TextView
                style="@style/SingleNumber"
                android:text="0"
                android:layout_weight="1"/>

            <TextView
                android:layout_width="match_parent"
                android:layout_height="0dp"
                android:layout_weight="5" />
        </LinearLayout>

        <HorizontalScrollView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginLeft="40dp"
            android:scrollbars="none">

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:orientation="horizontal">

                <RelativeLayout
                    android:id="@+id/linearLayout"
                    android:layout_width="wrap_content"
                    android:layout_height="match_parent">

                    <com.pcjz.dems.widget.chatview.SingleChartView
                        android:id="@+id/my_single_chart_view"
                        android:layout_width="wrap_content"
                        android:layout_height="match_parent" />
                </RelativeLayout>
            </LinearLayout>
        </HorizontalScrollView>
    </FrameLayout>
</LinearLayout>

最后给一句忠告,知其然更要知其然,脱离cv工程师。这样才能优秀,一起努力