自定义双线性与柱状统计图

啥都不说,先上效果图:

1.总共三个小demo,先说图1

一、自定义曲线与柱状图的类

(1).BarChartView 柱状

public class BarChartView extends View {
    private Context mContext;

    private Paint mPaintBar;
    private Paint mPaintLline;
    private Paint mPaintText;
    //柱状条对应的颜色数组
    private int[] colors;
    private int keduTextSpace = 10;//刻度与文字之间的间距
    private int keduWidth = 20; //坐标轴上横向标识线宽度(X、Y轴交叉底部延伸的宽度)
    private int keduSpace = 100; //每个刻度之间的间距 px(Y轴上刻度的距离)
    private int itemSpace = 20;//柱状条之间的间距
    private int itemWidth = 60;//柱状条的宽度
    //刻度递增的值
    private int valueSpace = 5;
    //绘制柱形图的坐标起点
    private int startX;
    private int startY;
    private int mTextSize = 30;
    private int mMaxTextWidth;
    private int mMaxTextHeight;
    private Rect mXMaxTextRect;
    private Rect mYMaxTextRect;
    //是否要展示柱状条对应的值
    private boolean isShowValueText = true;
    //数据值
    private List<Integer> mData = new ArrayList<>();
    private List<Integer> yAxisList = new ArrayList<>();
    private List<String> xAxisList = new ArrayList<>();

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

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

    }

    public BarChartView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.mContext = context;
//        colors = new int[]{ContextCompat.getColor(context, R.color.color_07f2ab),
//                ContextCompat.getColor(context, R.color.color_79d4d8),
//                ContextCompat.getColor(context, R.color.color_4388bc),
//                ContextCompat.getColor(context, R.color.color_07f2ab),
//                ContextCompat.getColor(context, R.color.color_4388bc)};
        init(context, false);
    }

    private void init(Context context, boolean isUpdate) {
        if (!isUpdate) {
            initData();
        }
        //设置边缘特殊效果
        BlurMaskFilter PaintBGBlur = new BlurMaskFilter(
                1, BlurMaskFilter.Blur.INNER);
        //绘制柱状图的画笔
        mPaintBar = new Paint();
        mPaintBar.setStyle(Paint.Style.FILL);
        mPaintBar.setStrokeWidth(4);
        mPaintBar.setMaskFilter(PaintBGBlur);

        //绘制直线的画笔
        mPaintLline = new Paint();
        mPaintLline.setColor(ContextCompat.getColor(context, R.color.color_xy));
        mPaintLline.setAntiAlias(true);
        mPaintLline.setStrokeWidth(2);

        //绘制文字的画笔
        mPaintText = new Paint();
        mPaintText.setTextSize(mTextSize);
        mPaintText.setColor(ContextCompat.getColor(context, R.color.color_xy));
        mPaintText.setAntiAlias(true);
        mPaintText.setStrokeWidth(1);

        mYMaxTextRect = new Rect();
        mXMaxTextRect = new Rect();
        mPaintText.getTextBounds(Integer.toString(yAxisList.get(yAxisList.size() - 1)), 0, Integer.toString(yAxisList.get(yAxisList.size() - 1)).length(), mYMaxTextRect);
        mPaintText.getTextBounds(xAxisList.get(xAxisList.size() - 1), 0, xAxisList.get(xAxisList.size() - 1).length(), mXMaxTextRect);
        //绘制的刻度文字的最大值所占的宽高
        mMaxTextWidth = mYMaxTextRect.width() > mXMaxTextRect.width() ? mYMaxTextRect.width() : mXMaxTextRect.width();
        mMaxTextHeight = mYMaxTextRect.height() > mXMaxTextRect.height() ? mYMaxTextRect.height() : mXMaxTextRect.height();

        if (yAxisList.size() >= 2) {
            valueSpace = yAxisList.get(1) - yAxisList.get(0);
        }
        //文字+刻度宽度+文字与刻度之间间距
        startX = mMaxTextWidth + keduWidth + keduTextSpace;
        //坐标原点 y轴起点
        startY = keduSpace * (yAxisList.size() - 1) + mMaxTextHeight + (isShowValueText ? keduTextSpace : 0);

    }

    /**
     * 初始化数据
     */
    private void initData() {
        int[] dataY = {0, 4, 8, 12, 16, 20, 24, 28};
        for (int i = 0; i < dataY.length; i++) {
            yAxisList.add(dataY[i]);
        }
        int[] data = {28, 24, 20, 16, 12};
        for (int i = 0; i < data.length; i++) {
            mData.add(data[i]);
        }
        String[] xAxis = {"1月", "2月", "3月", "4月", "5月"};
        for (int i = 0; i < mData.size(); i++) {
            xAxisList.add(xAxis[i]);
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        Log.e("TAG", "onMeasure()");
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        if (heightMode == MeasureSpec.AT_MOST) {
            if (keduWidth > mMaxTextHeight + keduTextSpace) {
                heightSize = (yAxisList.size() - 1) * keduSpace + keduWidth + mMaxTextHeight;
            } else {
                heightSize = (yAxisList.size() - 1) * keduSpace + (mMaxTextHeight + keduTextSpace) + mMaxTextHeight;
            }
            heightSize = heightSize + keduTextSpace + (isShowValueText ? keduTextSpace : 0);//x轴刻度对应的文字距离底部的padding:keduTextSpace
        }
        if (widthMode == MeasureSpec.AT_MOST) {
            widthSize = startX + mData.size() * itemWidth + (mData.size() + 1) * itemSpace;
        }
        Log.e("TAG", "heightSize=" + heightSize + "widthSize=" + widthSize);
        //保存测量结果
        setMeasuredDimension(widthSize, heightSize);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Log.e("TAG", "onDraw()");

        //从下往上绘制Y 轴
        canvas.drawLine(startX, startY + keduWidth, startX, startY - (yAxisList.size() - 1) * keduSpace, mPaintLline);

        for (int i = 0; i < yAxisList.size(); i++) {

            //绘制Y轴的文字
            Rect textRect = new Rect();
            mPaintText.getTextBounds(Integer.toString(yAxisList.get(i)), 0, Integer.toString(yAxisList.get(i)).length(), textRect);
            canvas.drawText(Integer.toString(yAxisList.get(i)), (startX - keduWidth) - textRect.width() - keduTextSpace, startY - (i + 1) * keduSpace + keduSpace, mPaintText);

            //画X轴及上方横向的刻度线
            canvas.drawLine(startX - keduWidth, startY - keduSpace * i, startX + mData.size() * itemWidth + itemSpace * (mData.size() + 1), startY - keduSpace * i, mPaintLline);

        }

        for (int j = 0; j < xAxisList.size(); j++) {
            //绘制X轴的文字
            Rect rect = new Rect();
            mPaintText.getTextBounds(xAxisList.get(j), 0, xAxisList.get(j).length(), rect);
            canvas.drawText(xAxisList.get(j), startX + itemSpace * (j + 1) + itemWidth * j + itemWidth / 2 - rect.width() / 2, startY + rect.height() + keduTextSpace, mPaintText);

            if (isShowValueText) {
                Rect rectText = new Rect();
                mPaintText.getTextBounds(mData.get(j) + "", 0, (mData.get(j) + "").length(), rectText);
                //绘制柱状条上的值
                canvas.drawText(mData.get(j) + "", startX + itemSpace * (j + 1) + itemWidth * j + itemWidth / 2 - rectText.width() / 2, (float) (startY - keduTextSpace - (mData.get(j) * (keduSpace * 1.0 / valueSpace))), mPaintText);
            }
            //绘制柱状条
//            mPaintBar.setColor(colors[j]); //可以每个柱状图颜色不同
            mPaintBar.setColor(ContextCompat.getColor(mContext, R.color.color_true));
            //(mData.get(j) * (keduSpace * 1.0 / valueSpace)):为每个柱状条所占的高度值px
            int initx = startX + itemSpace * (j + 1) + j * itemWidth;
            canvas.drawRect(initx, (float) (startY - (mData.get(j) * (keduSpace * 1.0 / valueSpace))), initx + itemWidth, startY, mPaintBar);
        }
    }

    /**
     * 根据真实的数据刷新界面
     *
     * @param datas
     * @param xList
     * @param yList
     * private int itemSpace = 20;//柱状条之间的间距
     *     private int itemWidth = 60;//柱状条的宽度
     */
    public void updateValueData( List<Integer> datas,  List<String> xList, List<Integer> yList , int itemSpace ,int itemWidth) {
        this.itemSpace = itemSpace;
        this.itemWidth = itemWidth;
        this.mData = datas;
        this.xAxisList = xList;
        this.yAxisList = yList;
        init(mContext, true);
        invalidate();
    }
    public void updateValueData( List<Integer> datas,  List<String> xList, List<Integer> yList) {
        this.mData = datas;
        this.xAxisList = xList;
        this.yAxisList = yList;
        init(mContext, true);
        invalidate();
    }
}
(2).LineChartView 曲线
public class LineChartView extends View {
    private Context mContext;
    //绘制坐标轴的画笔
    private Paint mAxisPaint;
    //绘制曲线的画笔
    private Paint mPaint;
    private Paint mPaint2;
    //绘制X轴上方的画笔
    private Paint mXAxisLinePaint;
    private Paint mPaintText;
    //向上的曲线图的绘制起点(px)
    private int startX;
    private int startY;
    //向下的曲线图的绘制起点(px)
    private int downStartX;
    private int downStartY;
    //上方Y轴每单位刻度所占的像素值
    private float YAxisUpUnitValue;
    //下方Y轴每单位刻度所占的像素值
    private float YAxisDownUnitValue;
    //根据具体传入的数据,在坐标轴上绘制点
    private Point[] mPoints;
    private Point[] mPoints2;
    //传入的数据,决定绘制的纵坐标值
    private List<Integer> mDatas = new ArrayList<>();
    private List<Integer> mDatas2 = new ArrayList<>();
    //Y轴刻度集合
    private List<Integer> mYAxisList = new ArrayList<>();
    //X轴刻度集合
    private List<String> mXAxisList = new ArrayList<>();
    //X轴的绘制距离
    private int mXAxisMaxValue;
    //Y轴的绘制距离
    private int mYAxisMaxValue;
    //Y轴刻度间距(px)
    private int yAxisSpace = 120;
    //X轴刻度间距(px)
    private int xAxisSpace = 100;
    //Y轴刻度线宽度
    private int mKeduWidth = 20;
    //X Y轴字体的大小
    private float keduTextSize = 30;
    //刻度值距离坐标的padding距离
    private int textPadinng = 10;
    //Y轴递增的实际值
    private int yIncreaseValue;
    //true:绘制曲线 false:折线
    private boolean isCurve = true;
    private Rect mYMaxTextRect;
    private Rect mXMaxTextRect;
    private int mMaxTextHeight;
    private int mMaxTextWidth;

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

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

    public LineChartView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.mContext = context;
        initData();
        initView();

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        if (heightMode == MeasureSpec.AT_MOST) {
            heightSize = (mYAxisList.size() - 1) * yAxisSpace + mMaxTextHeight * 2 + textPadinng * 2;
        }
        if (widthMode == MeasureSpec.AT_MOST) {

            widthSize = startX + (mDatas.size() - 1) * xAxisSpace + mMaxTextWidth;
        }
        //保存测量结果
        setMeasuredDimension(widthSize, heightSize);
    }

    private void initView() {
        //初始化画笔
        mPaint = new Paint();
        mPaint.setColor(ContextCompat.getColor(mContext, R.color.color_true));
        mPaint.setStrokeWidth(5);
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.STROKE);

        mPaint2 = new Paint();
        mPaint2.setColor(ContextCompat.getColor(mContext, R.color.color_yuyue));
        mPaint2.setStrokeWidth(5);
        mPaint2.setAntiAlias(true);
        mPaint2.setStyle(Paint.Style.STROKE);

        //绘制X,Y轴坐标的画笔
        mAxisPaint = new Paint();
        mAxisPaint.setColor(ContextCompat.getColor(mContext, R.color.color_xy));
        mAxisPaint.setStrokeWidth(2);
        mAxisPaint.setAntiAlias(true);
        mAxisPaint.setStyle(Paint.Style.STROKE);

        //绘制坐标轴上方的横线的画笔
        mXAxisLinePaint = new Paint();
        mXAxisLinePaint.setColor(ContextCompat.getColor(mContext, R.color.color_xy));
        mXAxisLinePaint.setStrokeWidth(1);
        mXAxisLinePaint.setAntiAlias(true);
        mXAxisLinePaint.setStyle(Paint.Style.STROKE);

        //绘制刻度值文字的画笔
        mPaintText = new Paint();
        mPaintText.setTextSize(keduTextSize);
        mPaintText.setColor(ContextCompat.getColor(mContext, R.color.color_black));
        mPaintText.setAntiAlias(true);
        mPaintText.setStrokeWidth(5);


        mYMaxTextRect = new Rect();
        mXMaxTextRect = new Rect();
        mPaintText.getTextBounds(Integer.toString(mYAxisList.get(mYAxisList.size() - 1)), 0, Integer.toString(mYAxisList.get(mYAxisList.size() - 1)).length(), mYMaxTextRect);
        mPaintText.getTextBounds(mXAxisList.get(mXAxisList.size() - 1), 0, mXAxisList.get(mXAxisList.size() - 1).length(), mXMaxTextRect);
        //绘制的刻度文字的最大值所占的宽高
        mMaxTextWidth = mYMaxTextRect.width() > mXMaxTextRect.width() ? mYMaxTextRect.width() : mXMaxTextRect.width();
        mMaxTextHeight = mYMaxTextRect.height() > mXMaxTextRect.height() ? mYMaxTextRect.height() : mXMaxTextRect.height();


        //指定绘制的起始位置
        startX = mMaxTextWidth + textPadinng + mKeduWidth;
        //坐标原点Y的位置(+1的原因:X轴画笔的宽度为2 ; +DP2PX.dip2px(mContext, 5)原因:为刻度文字所占的超出的高度 )——>解决曲线画到最大刻度值时,显示高度不够,曲线显示扁扁的问题
        startY = yAxisSpace * (mYAxisList.size() - 1) + mMaxTextHeight;

        if (mYAxisList.size() >= 2) {
            yIncreaseValue = mYAxisList.get(1) - mYAxisList.get(0);
        }
        //X轴绘制距离
        mXAxisMaxValue = (mDatas.size() - 1) * xAxisSpace;
        //Y轴绘制距离
        mYAxisMaxValue = (mYAxisList.size() - 1) * yAxisSpace;

        //坐标起始点Y轴高度=(startY+mKeduWidth)  下方文字所占高度= DP2PX.dip2px(mContext, keduTextSize)
//        int viewHeight = startY + 2 * mKeduWidth + DP2PX.dip2px(mContext, keduTextSize);
        int viewHeight = startY + 2 * mKeduWidth + (int)keduTextSize;
        //viewHeight=121
        Log.e("TAG", "viewHeight=" + viewHeight);
    }

    /**
     * 根据传入的数据,确定绘制的点
     *
     * @return
     */
    private Point[] initPoint() {
        Point[] points = new Point[mDatas.size()];
        for (int i = 0; i < mDatas.size(); i++) {
            Integer ybean = mDatas.get(i);
            int drawHeight = (int) (startY * 1.0 - (ybean * yAxisSpace * 1.0 / yIncreaseValue));
            int startx = startX + xAxisSpace * i;
            points[i] = new Point(startx, drawHeight);
        }
        Log.e("TAG", "startX=" + startX + "---startY=" + startY);
        return points;
    }
    private Point[] initPoint2() {
        Point[] points = new Point[mDatas2.size()];
        for (int i = 0; i < mDatas2.size(); i++) {
            Integer ybean = mDatas2.get(i);
            int drawHeight = (int) (startY * 1.0 - (ybean * yAxisSpace * 1.0 / yIncreaseValue));
            int startx = startX + xAxisSpace * i;
            points[i] = new Point(startx, drawHeight);
        }
        Log.e("TAG", "startX=" + startX + "---startY=" + startY);
        return points;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        mPoints = initPoint();
        mPoints2 = initPoint2();

        for (int i = 0; i < mYAxisList.size(); i++) {
            //Y轴方向递增的高度
            int yAxisHeight = startY - yAxisSpace * i;
            //绘制X轴和上方横线
            canvas.drawLine(startX - mKeduWidth, yAxisHeight, startX + (mDatas.size() - 1) * xAxisSpace, yAxisHeight, mXAxisLinePaint);
            //绘制左边Y轴刻度线
//                canvas.drawLine(startX, yAxisHeight, startX - mKeduWidth, yAxisHeight, mAxisPaint);
            //绘制文字时,Y轴方向递增的高度
            int yTextHeight = startY - yAxisSpace * i;
            //绘制Y轴刻度旁边的刻度文字值,10为刻度线与文字的间距
            mPaintText.setTextAlign(Paint.Align.RIGHT);
            if (i == ( mYAxisList.size()-1)){
                canvas.drawText(mYAxisList.get(i) + "  ", (startX - mKeduWidth) - textPadinng, yTextHeight, mPaintText);
            }else {
                canvas.drawText(mYAxisList.get(i) + "", (startX - mKeduWidth) - textPadinng, yTextHeight, mPaintText);
            }
        }
        //绘制Y轴
        canvas.drawLine(startX, startY, startX, startY - mYAxisMaxValue, mAxisPaint);
        //连接所有的数据点,画曲线

        if (isCurve) {
            //画曲线
            drawScrollLine(canvas);
            drawScrollLine2(canvas);
        } else {
            //画折线
            drawLine(canvas);
        }

        //绘制X轴下面显示的文字
        for (int i = 0; i < mXAxisList.size(); i++) {
            int xTextWidth = startX + xAxisSpace * i - mKeduWidth;
            //设置从起点位置的左边对齐绘制文字
            mPaintText.setTextAlign(Paint.Align.LEFT);
            Rect rect = new Rect();
            mPaintText.getTextBounds(mXAxisList.get(i), 0, mXAxisList.get(i).length(), rect);
            canvas.drawText(mXAxisList.get(i), startX - rect.width() / 2 + xAxisSpace * i, startY + rect.height() + textPadinng, mPaintText);
        }

    }


        /**
         * 绘制曲线-曲线图
         *
         * @param canvas
         */
    private void drawScrollLine(Canvas canvas) {
        Point startp;
        Point endp;
        for (int i = 0; i < mPoints.length - 1; i++) {
            startp = mPoints[i];
            endp = mPoints[i + 1];
            int wt = (startp.x + endp.x) / 2;
            Point p3 = new Point();
            Point p4 = new Point();
            p3.y = startp.y;
            p3.x = wt;
            p4.y = endp.y;
            p4.x = wt;
            Path path = new Path();
            path.moveTo(startp.x, startp.y);
            path.cubicTo(p3.x, p3.y, p4.x, p4.y, endp.x, endp.y);
            canvas.drawPath(path, mPaint);
        }
    }
    private void drawScrollLine2(Canvas canvas) {
        Point startp;
        Point endp;
        for (int i = 0; i < mPoints2.length - 1; i++) {
            startp = mPoints2[i];
            endp = mPoints2[i + 1];
            int wt = (startp.x + endp.x) / 2;
            Point p3 = new Point();
            Point p4 = new Point();
            p3.y = startp.y;
            p3.x = wt;
            p4.y = endp.y;
            p4.x = wt;
            Path path = new Path();
            path.moveTo(startp.x, startp.y);
            path.cubicTo(p3.x, p3.y, p4.x, p4.y, endp.x, endp.y);
            canvas.drawPath(path, mPaint2);
        }
    }

    /**
     * 绘制直线-折线图
     *
     * @param canvas
     */
    private void drawLine(Canvas canvas) {
        Point startp;
        Point endp;
        for (int i = 0; i < mPoints.length - 1; i++) {
            startp = mPoints[i];
            endp = mPoints[i + 1];
            canvas.drawLine(startp.x, startp.y, endp.x, endp.y, mPaint);
        }
    }

    private void initData() {
        //外界传入的数据,即为绘制曲线的每个点
        mDatas.add(0);
        mDatas.add(10);
        mDatas.add(5);
        mDatas.add(20);
        mDatas.add(15);

        mDatas2.add(10);
        mDatas2.add(5);
        mDatas2.add(0);
        mDatas2.add(10);
        mDatas2.add(20);

        int[] mYAxisData = new int[]{0, 5, 10, 15, 20 ,25 ,30};
        for (int i = 0; i < mYAxisData.length; i++) {
            mYAxisList.add(mYAxisData[i]);
        }

        //X轴数据
        mXAxisList.add("06-01");
        mXAxisList.add("06-02");
        mXAxisList.add("06-03");
        mXAxisList.add("06-04");
        mXAxisList.add("06-05");
    }

    /**
     * 传入数据,重新绘制图表
     *
     * @param datas
     * @param yAxisData
     */
    public void updateData(List<Integer> datas,List<Integer> datas2, List<String> xAxisData, List<Integer> yAxisData ,int xAxisSpace) {
        this.xAxisSpace = xAxisSpace;
        this.mDatas = datas;
        this.mDatas2 = datas2;
        this.mXAxisList = xAxisData;
        this.mYAxisList = yAxisData;
        initView();
        postInvalidate();
    }
}

3.布局 activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:layout_marginRight="10dp"
        android:orientation="vertical">

        <com.zdmtech.tongjiquxiandemo.LineChartView
            android:id="@+id/lineChartView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>

        <com.zdmtech.tongjiquxiandemo.BarChartView
            android:id="@+id/barChartView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"/>

    </LinearLayout>


</RelativeLayout>

4.Activity 代码

public class MainActivity extends AppCompatActivity {
    private Activity activity;
    private LineChartView lineChartView ;
    private BarChartView barChartView ;
    /**
     * 线形图
     * */
    //传入的数据,决定绘制的纵坐标值
    private List<Integer> mDatas = new ArrayList<>();
    private List<Integer> mDatas2 = new ArrayList<>();
    //Y轴刻度集合
    private List<Integer> mYAxisList = new ArrayList<>();
    //X轴刻度集合
    private List<String> mXAxisList = new ArrayList<>();

    /**
     * 柱状图
     * */
    //柱状体数据 集合
    private List<Integer> mData = new ArrayList<>();
    //Y轴刻度集合(相差大小一定)
    private List<Integer> yAxisList = new ArrayList<>();
    //X轴刻度集合
    private List<String> xAxisList = new ArrayList<>();

    private int width_bar = 0 ;
    private int width_chart = 0 ;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        activity = this;
        initData();
        initDataBar();
        lineChartView = findViewById(R.id.lineChartView);
        barChartView = findViewById(R.id.barChartView);

        /**
         * 获取控件宽高
         * 线性
         * */
        ViewTreeObserver vto = lineChartView.getViewTreeObserver();
        vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                lineChartView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                /**
                 * 线形图更新数据
                 * */
                width_chart = lineChartView.getWidth();
                Log.e("++++++" ,"width_chart = "+width_chart);
                int chartWidth = (width_chart-80)/mDatas.size();
                Log.e("++++++" ,"chartWidth = "+chartWidth);
                lineChartView.updateData(mDatas ,mDatas2 ,mXAxisList ,mYAxisList ,chartWidth);
            }
        });

        /**
         * 获取控件宽高
         * 柱状
         * */
        ViewTreeObserver vto2 = barChartView.getViewTreeObserver();
        vto2.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                barChartView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                /**
                 * 柱状图更新数据
                 * */
                width_bar = barChartView.getWidth();
                Log.e("++++++" ,"width_bar = "+width_bar);
                int barWidth = width_bar/(mData.size()+1);
                int itemSpace = (barWidth*3)/10;
                int itemWidth = (barWidth*7)/10;
                Log.e("++++++" ,"itemSpace = "+itemSpace);
                Log.e("++++++" ,"itemWidth = "+itemWidth);
                Log.e("++++++" ,"xAxisList = "+xAxisList.size());
                barChartView.updateValueData(mData, xAxisList , yAxisList ,itemSpace ,itemWidth);
//                barChartView.updateValueData(mData, xAxisList , yAxisList);
            }
        });

    }

    private void initData() {
        //外界传入的数据,即为绘制曲线的每个点
        mDatas.add(30);
        mDatas.add(20);
        mDatas.add(10);
        mDatas.add(5);
        mDatas.add(0);
        mDatas.add(25);
        mDatas.add(10);
        mDatas.add(30);
        mDatas.add(10);
        mDatas.add(5);

        mDatas2.add(0);
        mDatas2.add(30);
        mDatas2.add(10);
        mDatas2.add(5);
        mDatas2.add(0);
        mDatas2.add(10);
        mDatas2.add(15);
        mDatas2.add(20);
        mDatas2.add(25);
        mDatas2.add(30);

        int[] mYAxisData = new int[]{0, 5, 10, 15, 20 ,25 ,30};
        for (int i = 0; i < mYAxisData.length; i++) {
            mYAxisList.add(mYAxisData[i]);
        }

        //X轴数据
        mXAxisList.add("06-01");
        mXAxisList.add("06-02");
        mXAxisList.add("06-03");
        mXAxisList.add("06-04");
        mXAxisList.add("06-05");
        mXAxisList.add("06-06");
        mXAxisList.add("06-07");
        mXAxisList.add("06-08");
        mXAxisList.add("06-09");
        mXAxisList.add("06-10");
    }

    /**
     * 初始化数据
     */
    private void initDataBar() {
        int[] dataY = {0, 4, 8, 12, 16, 20, 24, 28};
        for (int i = 0; i < dataY.length; i++) {
            yAxisList.add(dataY[i]);
        }
        int[] data = {28, 24, 20, 16, 12, 10, 8, 6, 4, 2};
        for (int i = 0; i < data.length; i++) {
            mData.add(data[i]);
        }
        String[] xAxis = {"1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月"};
        for (int i = 0; i < mData.size(); i++) {
            xAxisList.add(xAxis[i]);
        }
    }
}

5.Demo下载地址 https://download.csdn.net/download/yyxhzdm/15365958

后期会添加XY轴根据数据的长短与大小来自动适配XY轴数据

二、利用RecycleView来实现柱状统计图

1.布局类 bar_chart_layout 

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/chart"
    android:layout_width="match_parent"
    android:layout_height="200dp">

    <View
        android:id="@+id/view_y"
        android:layout_width="0.5dp"
        android:layout_height="match_parent"
        android:layout_marginLeft="40dp"
        android:layout_marginTop="30dp"
        android:background="#666" />

    <View
        android:id="@+id/view_x"
        android:layout_width="match_parent"
        android:layout_height="0.5dp"
        android:layout_alignBottom="@+id/view_y"
        android:layout_marginLeft="40dp"
        android:layout_marginBottom="20dp"
        android:background="#666" />

    <TextView
        android:id="@+id/text_low"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_marginLeft="5dp"
        android:layout_marginBottom="30dp"
        android:drawablePadding="2dp"
        android:gravity="right"
        android:minWidth="20dp"
        android:text="低"
        android:textSize="10sp" />

    <TextView
        android:id="@+id/text_mid"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_marginLeft="5dp"
        android:layout_marginBottom="95dp"
        android:drawablePadding="2dp"
        android:gravity="right"
        android:minWidth="20dp"
        android:text="中"
        android:textSize="10sp" />


    <TextView
        android:id="@+id/text_hi"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_marginLeft="5dp"
        android:layout_marginBottom="150dp"
        android:drawablePadding="2dp"
        android:gravity="right"
        android:minWidth="20dp"
        android:text="高"
        android:textSize="10sp" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rcv"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_alignParentBottom="true"
        android:layout_marginLeft="45dp"/>

</RelativeLayout>

(2).item 的布局bar_chart_item

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="50dp"
    android:id="@+id/rl_chart_item"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/name"
        android:layout_width="wrap_content"
        android:layout_height="20dp"
        android:textSize="11sp"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:singleLine="true"
        android:maxLines="1"
        android:ellipsize="end"
        android:text=""
        android:textColor="#999"
        tools:text = "name"/>

    <ProgressBar
        android:id="@+id/pb_vertical"
        android:layout_width="20dp"
        android:layout_height="150dp"
        android:indeterminateOnly="false"
        android:layout_above="@+id/name"
        android:max="100"
        android:progress="60"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="2dp"
        android:progressDrawable="@drawable/progress_normal" />

</RelativeLayout>

2.adapter

public class BarAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private final LayoutInflater mLayoutInflater;
    private List<ChartData> mDatas;
    private float mLowStandard;
    private float mHighStandard;
    private Context mContext;
    private int indexSelected = -1;

    public BarAdapter(Context context, float lowStandard, float highStandard,List<ChartData> mDatas) {
        mLayoutInflater = LayoutInflater.from(context);
        mContext = context;
        mLowStandard = lowStandard;
        mHighStandard = highStandard;
        this.mDatas=mDatas;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        ViewHolder holder = new ViewHolder(mLayoutInflater.inflate(R.layout.bar_chart_item, parent, false));
        return holder;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        final ChartData data = mDatas.get(position);
        TextView tv_name = ((ViewHolder) holder).getView(R.id.name);
        if(position == indexSelected){  //当用户选中时改变文字颜色
            tv_name.setTextColor(Color.RED);
        } else {
            tv_name.setTextColor(Color.GRAY);
        }
        ProgressBar progressBar = ((ViewHolder) holder).getView(R.id.pb_vertical);
        float pro = data.getProgress();
        progressBar.setVisibility(View.VISIBLE);
        if (pro > mHighStandard) {//大于“高”时使用一种颜色的Drawable
            progressBar.setProgressDrawable(ContextCompat.getDrawable(mContext, R.drawable.progress_high));
        } else if (pro < mLowStandard) {
            progressBar.setProgressDrawable(ContextCompat.getDrawable(mContext, R.drawable.progress_low));
        } else {//小于“低”时使用一种颜色的Drawable
            progressBar.setProgressDrawable(ContextCompat.getDrawable(mContext, R.drawable.progress_normal));
        }
        progressBar.setProgress(Math.round(pro));
        tv_name.setText(data.getName());
    }

    @Override
    public int getItemCount() {
        if (mDatas == null) {
            return 0;
        }
        return mDatas.size();
    }

    public void setSelected(int position) {
        if(indexSelected == -1){
            indexSelected = position;
            notifyItemChanged(indexSelected);
        } else {
            int a = indexSelected;
            indexSelected = position;
            notifyItemChanged(indexSelected);
            notifyItemChanged(a);
        }
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        public ViewHolder(View view) {
            super(view);
        }

        public void setText(int viewId, String text){
            TextView tv = (TextView) itemView.findViewById(viewId);
            tv.setText(text);
        }

        public <T extends View> T getView(int viewId){
            return  (T) itemView.findViewById(viewId);
        }
    }
}

3.Activity

public class MainActivity extends AppCompatActivity {

    protected View viewY;
    protected View viewX;
    protected TextView textLow;
    protected TextView textMid;
    protected TextView textHi;
    protected RecyclerView rcv;
    protected RelativeLayout chart;
    BarAdapter mdapter;
    List<ChartData> mDatas=new ArrayList<>();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        super.setContentView(R.layout.activity_main);
        initView();
        initData();
        initAdapter();
    }

    private void initView() {
        viewY = (View) findViewById(R.id.view_y);
        viewX = (View) findViewById(R.id.view_x);
        textLow = (TextView) findViewById(R.id.text_low);
        textMid = (TextView) findViewById(R.id.text_mid);
        textHi = (TextView) findViewById(R.id.text_hi);
        rcv = (RecyclerView) findViewById(R.id.rcv);
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
        linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
        rcv.setHasFixedSize(true);
        rcv.setLayoutManager(linearLayoutManager);
        chart = (RelativeLayout) findViewById(R.id.chart);
    }

    private void initData(){
        for (int i=1;i<=25;i++){
            ChartData item=new ChartData();
            item.setName("第"+i+"个");
            item.setProgress(i*4);
            mDatas.add(item);
        }
    }

    private void initAdapter(){
        mdapter = new BarAdapter(this, 25, 75,mDatas);
        rcv.setAdapter(mdapter);
        mdapter.notifyDataSetChanged();
    }
}

4.demo下载地址 https://download.csdn.net/download/yyxhzdm/15365449

三、自定义底部斜体字的柱状图

1.自定义柱状图类 : BarChartView

package com.example.mybarchartrecycleviewtwo;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;

import java.util.List;

/**
 * 自定义组件:条形统计图
 * Created by hanj on 14-12-30.
 */
public class BarChartView extends View {
    private int screenW, screenH;
    private List<BarChartItemBean> mItems;
    //max value in mItems.
    private float maxValue;
    //max height of the bar
    private int maxHeight;
    private int[] mBarColors = new int[]{Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW, Color.MAGENTA, Color.CYAN};
    private Paint barPaint, linePaint, textPaint;
    private Rect barRect, leftWhiteRect, rightWhiteRect;
    private Path textPath;
    private int leftMargin, topMargin, smallMargin;
    //the width of one bar item
    private int barItemWidth;
    //the spacing between two bar items.
    private int barSpace;
    //the width of the line.
    private int lineStrokeWidth;
    /**
     * The x-position of y-index and the y-position of the x-index..
     */
    private float x_index_startY, y_index_startX;
    private Bitmap arrowBmp;
    private Rect x_index_arrowRect, y_index_arrowRect;
    private static final int BG_COLOR = Color.parseColor("#FFFFFF"); //整体背景

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

    private void init(Context context) {
        screenW = ScreenUtils.getScreenW(context);
//        screenH = (int) (ScreenUtils.getScreenH(context)*2/3);
//        Log.e("OkHttp" ,"screenH2222 = "+screenH);
        screenH = 1080;
        leftMargin = ScreenUtils.dp2px(context, 16);
        topMargin = ScreenUtils.dp2px(context, 15);
        smallMargin = ScreenUtils.dp2px(context, 6);
        barPaint = new Paint();
        barPaint.setColor(mBarColors[0]);
        linePaint = new Paint();
        lineStrokeWidth = ScreenUtils.dp2px(context, 1);
        linePaint.setStrokeWidth(lineStrokeWidth);
        textPaint = new Paint();
        textPaint.setAntiAlias(true);
        barRect = new Rect(0, 0, 0, 0);
        textPath = new Path();
        leftWhiteRect = new Rect(0, 0, 0, screenH);
        rightWhiteRect = new Rect(screenW - leftMargin, 0, screenW, screenH);
        arrowBmp = BitmapFactory.decodeResource(context.getResources(), R.drawable.arrow_up);
    }

    //标记是否已经获取过状态拉的高度
    private boolean statusHeightHasGet;

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (!statusHeightHasGet) {
            subStatusBarHeight();
            statusHeightHasGet = true;
        }
        //draw background
        canvas.drawColor(BG_COLOR);
        //bounds
        checkLeftMoving();
        textPaint.setTextSize(ScreenUtils.dp2px(getContext(), 13));
        for (int i = 0; i < mItems.size(); i++) {
            //draw bar rect
            barRect.left = (int) y_index_startX + barItemWidth * i + barSpace * (i + 1) - (int) leftMoving;
            barRect.top = topMargin * 2 + (int) (maxHeight * (1.0f - mItems.get(i).getItemValue() / maxValue));
            barRect.right = barRect.left + barItemWidth;
            barPaint.setColor(mBarColors[i % mBarColors.length]);
            canvas.drawRect(barRect, barPaint);
            //draw type text
            String typeText = mItems.get(i).getItemType();
            float textPathStartX = barRect.left + barItemWidth / 2 -
                    (float) (Math.sin(Math.PI / 6)) * textPaint.measureText("好") / 2;
            float textPathStartY = barRect.bottom;
            textPath.reset();
            textPath.moveTo(textPathStartX, textPathStartY);
            textPath.lineTo(textPathStartX + (float) (1000 * Math.tan(Math.PI / 6)), textPathStartY + 1000);
            canvas.drawTextOnPath(typeText, textPath, smallMargin * 1.5f, smallMargin * 2, textPaint);
            //draw value text
            String valueText = String.valueOf(mItems.get(i).getItemValue());
            canvas.drawText(valueText, barRect.left - (textPaint.measureText(valueText) - barItemWidth) / 2,
                    barRect.top - smallMargin, textPaint);
        }
        //draw left white space and right white space
        int c = barPaint.getColor();
        barPaint.setColor(BG_COLOR);
        leftWhiteRect.right = (int) y_index_startX;
        canvas.drawRect(leftWhiteRect, barPaint);
        canvas.drawRect(rightWhiteRect, barPaint);
        barPaint.setColor(c);
        //draw x-index line.
        canvas.drawLine(
                y_index_startX - lineStrokeWidth / 2,
                x_index_startY,
                screenW - leftMargin,
                x_index_startY,
                linePaint);
        //draw y-index line.
        canvas.drawLine(
                y_index_startX,
                x_index_startY + lineStrokeWidth / 2,
                y_index_startX,
                topMargin / 2,
                linePaint);
        canvas.drawBitmap(arrowBmp, null, y_index_arrowRect, null);
        canvas.save();
        canvas.rotate(90, (x_index_arrowRect.left + x_index_arrowRect.right) / 2, (x_index_arrowRect.top + x_index_arrowRect.bottom) / 2);
        canvas.drawBitmap(arrowBmp, null, x_index_arrowRect, null);
        canvas.restore();
        //draw division value
        int maxDivisionValueHeight = (int) (maxHeight * 1.0f / maxValue * maxDivisionValue);
        textPaint.setTextSize(ScreenUtils.dp2px(getContext(), 12)); //Y轴字体大小
        for (int i = 1; i <= 10; i++) {
            float startY = barRect.bottom - maxDivisionValueHeight * 0.1f * i;
            if (startY < topMargin / 2) {
                break;
            }
            canvas.drawLine(y_index_startX, startY, y_index_startX + 10, startY, linePaint);
            String text = String.valueOf((int) (maxDivisionValue * 0.1 * i));
            canvas.drawText(text,
                    y_index_startX - textPaint.measureText(text) - 5,
                    startY + textPaint.measureText("0") / 2,
                    textPaint);
        }
    }

    private float leftMoving;
    private float lastPointX;
    private float movingLeftThisTime = 0.0f;

    @Override
    public boolean onTouchEvent(@NonNull MotionEvent event) {
        int type = event.getAction();
        switch (type) {
            case MotionEvent.ACTION_DOWN:
                lastPointX = event.getRawX();
                break;
            case MotionEvent.ACTION_MOVE:
                float x = event.getRawX();
                movingLeftThisTime = lastPointX - x;
                leftMoving += movingLeftThisTime;
                lastPointX = x;
                invalidate();
                break;
            case MotionEvent.ACTION_UP:
                //smooth scroll
                new Thread(new SmoothScrollThread(movingLeftThisTime)).start();
                break;
            default:
                return super.onTouchEvent(event);
        }
        return true;
    }

    /**
     * Check the value of leftMoving to ensure that the view is not out of the screen.
     */
    private void checkLeftMoving() {
        if (leftMoving < 0) {
            leftMoving = 0;
        }
        if (leftMoving > (maxRight - minRight)) {
            leftMoving = maxRight - minRight;
        }
    }

    public List<BarChartItemBean> getItems() {
        return mItems;
    }

    public void setItems(List<BarChartItemBean> items) {
        if (items == null) {
            throw new RuntimeException("BarChartView.setItems(): the param items cannot be null.");
        }
        if (items.size() == 0) {
            return;
        }
        this.mItems = items;
        //Calculate the max value.
        maxValue = mItems.get(0).getItemValue();
        for (BarChartItemBean bean : items) {
            if (bean.getItemValue() > maxValue) {
                maxValue = bean.getItemValue();
            }
        }
        //Calculate the max division value.
        getRange(maxValue, 0);
        //Get the width of each bar.
        getBarItemWidth(screenW, items.size());
        //Refresh the view.
        invalidate();
    }

    private int maxRight, minRight;

    /**
     * Get the width of each bar which is depended on the screenW and item count.
     */
    private void getBarItemWidth(int screenW, int itemCount) {
        //The min width of the bar is 50dp.
        int minBarWidth = ScreenUtils.dp2px(getContext(), 40);
        //The min width of spacing.
        int minBarSpacing = ScreenUtils.dp2px(getContext(), 30);
        barItemWidth = (screenW - leftMargin * 2) / (itemCount + 3);
        barSpace = (screenW - leftMargin * 2 - barItemWidth * itemCount) / (itemCount + 1);
        if (barItemWidth < minBarWidth || barSpace < minBarSpacing) {
            barItemWidth = minBarWidth;
            barSpace = minBarSpacing;
        }
        maxRight = (int) y_index_startX + lineStrokeWidth + (barSpace + barItemWidth) * mItems.size();
        minRight = screenW - leftMargin - barSpace;
    }

    /**
     * Sub the height of status bar and action bar to get the accurate height of screen.
     */
    private void subStatusBarHeight() {
        //The height of the status bar
        int statusHeight = ScreenUtils.getStatusBarHeight((Activity) getContext());
        //The height of the actionBar
        ActionBar ab = ((MainActivity) getContext()).getSupportActionBar();
        int abHeight = ab == null ? 0 : ab.getHeight();
        screenH -= (statusHeight + abHeight);
        barRect.top = topMargin * 2;
        barRect.bottom = screenH - topMargin * 3;
        maxHeight = barRect.bottom - barRect.top;
        x_index_startY = barRect.bottom;
        x_index_arrowRect = new Rect(screenW - leftMargin, (int) (x_index_startY - 10),
                screenW - leftMargin + 10, (int) (x_index_startY + 10));
    }

    //The max and min division value.
    private float maxDivisionValue, minDivisionValue;

    //Get the max and min division value by the max and min value in mItems.
    private void getRange(float maxValue, float minValue) {
        //max
        int scale = Utility.getScale(maxValue);
        float unscaledValue = (float) (maxValue / Math.pow(10, scale));
        maxDivisionValue = (float) (getRangeTop(unscaledValue) * Math.pow(10, scale));
        y_index_startX = getDivisionTextMaxWidth(maxDivisionValue) + 10;
        y_index_arrowRect = new Rect((int) (y_index_startX - 5), topMargin / 2 - 20,
                (int) (y_index_startX + 5), topMargin / 2);
    }

    private float getRangeTop(float value) {
        //value: [1,10)
        if (value < 1.2) {
            return 1.2f;
        }
        if (value < 1.5) {
            return 1.5f;
        }
        if (value < 2.0) {
            return 2.0f;
        }
        if (value < 3.0) {
            return 3.0f;
        }
        if (value < 4.0) {
            return 4.0f;
        }
        if (value < 5.0) {
            return 5.0f;
        }
        if (value < 6.0) {
            return 6.0f;
        }
        if (value < 8.0) {
            return 8.0f;
        }
        return 10.0f;
    }

    /**
     * Get the max width of the division value text.
     */
    private float getDivisionTextMaxWidth(float maxDivisionValue) {
        Paint textPaint = new Paint();
        textPaint.setTextSize(ScreenUtils.dp2px(getContext(), 10));
        float max = textPaint.measureText(String.valueOf(maxDivisionValue * 0.1f));
        for (int i = 2; i <= 10; i++) {
            float w = textPaint.measureText(String.valueOf(maxDivisionValue * 0.1f * i));
            if (w > max) {
                max = w;
            }
        }
        return max;
    }

    /**
     * Use this thread to create a smooth scroll after ACTION_UP.
     */
    private class SmoothScrollThread implements Runnable {
        float lastMoving;
        boolean scrolling = true;

        private SmoothScrollThread(float lastMoving) {
            this.lastMoving = lastMoving;
            scrolling = true;
        }

        @Override
        public void run() {
            while (scrolling) {
                long start = System.currentTimeMillis();
                lastMoving = (int) (0.9f * lastMoving);
                leftMoving += lastMoving;
                checkLeftMoving();
                postInvalidate();
                if (Math.abs(lastMoving) < 5) {
                    scrolling = false;
                }
                long end = System.currentTimeMillis();
                if (end - start < 20) {
                    try {
                        Thread.sleep(20 - (end - start));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

2.布局xml  activity_main

<com.example.mybarchartrecycleviewtwo.BarChartView
    android:layout_marginTop="10dp"
    android:id="@+id/bar_chart"
    android:layout_width="match_parent"
    android:layout_height="400dp"
    android:background="#FFFFFF"/>

3.下载地址:https://download.csdn.net/download/yyxhzdm/15365417

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yyxhzdm

你的鼓励是我创作的最大动力!!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值