自定义View_柱形图-玩一把牛逼的~

两个月没有写过文章了,最近比较忙,今天抽个时间写一篇,最近做的一个项目中有一个关于柱形图的,而且不是传统的单列柱形图,而是双列柱形图,类似于下面这种效果~

双列柱状图.png

然后我就本能的去GitHub上搜一些图表的框架,后来总结以下特点:
1.很难吻合自己的需求,总是很多地方不一样;
2.框架要么很全[复杂],会存在很多不需要的东西;要么很单一,不符合自己的需要;
3.使用起来复杂。等等
而且我这个双列柱状图好像还没有类似的框架,百度上搜也是寥寥无几,怎么办呢,自己造一个吧!于是这篇博客诞生了~照例先放个GitHub传送门:https://github.com/SuperKotlin/DoubleLineChatView
然后看一下今天要实现的效果图:

DoubleLineChatView.gif

其实这样的柱状图绘制起来特别简单,难处理的是里面的计算和一些细节等等。

1.自定义我们需要的属性:


首先为了一定的扩展性,我们要先思考哪些属性需要自定义,这里我想到了这些

<declare-styleable name="DoubleLineChatView">
        <!--左边的柱状图的颜色-->
        <attr name="chatview_left_background_color" format="color" />
        <!--右边的柱状图的颜色-->
        <attr name="chatview_right_background_color" format="color" />

        <!--字体(左数据)的颜色-->
        <attr name="chatview_left_text_data_color" format="color" />
        <!--字体(右数据)的颜色-->
        <attr name="chatview_right_text_data_color" format="color" />
        <!--字体(数据)的大小-->
        <attr name="chatview_text_data_size" format="dimension" />

        <!--(xy轴)的颜色-->
        <attr name="chatview_text_xy_color" format="color" />
        <!--(xy轴)的字体大小-->
        <attr name="chatview_text_xy_size" format="dimension" />

        <!--左右柱状图之间的小距离-->
        <attr name="chatview_line_small_distance" format="dimension" />
        <!--柱状图的宽度-->
        <attr name="chatview_line_width" format="dimension" />
        <!--柱状图之间的大距离-->
        <attr name="chatview_line_big_distance" format="dimension" />
        <!--Y轴数据的宽度,当数据字符串过大的时候需要调整这个宽度-->
        <attr name="chatview_y_distance" format="dimension" />
        <!--X轴数据的宽度-->
        <attr name="chatview_x_distance" format="dimension" />

        <!--动画时长-->
        <attr name="chatview_animation_duration" format="integer" />
        <!--是否显示坐标轴的箭头-->
        <attr name="chatview_show_arrow" format="boolean" />
        <!--是否显示Y坐标轴的间断标记-->
        <attr name="chatview_show_y_interval" format="boolean" />

    </declare-styleable>

画个草图解释一下:

属性点示意图.png

OK,这就是大致上我能想到的需求了,还有就是这里解释一下X轴和Y轴的数值显示,X轴上一般显示某某文字,比如项目名称等等,Y轴上面的数据是自动计算的,根据最大值计算显示,计算方式下面会讲。

声明相关变量和获取自定义属性值我就不详细讲了,之前博客讲过很多,这里重要讲一下绘制过程中需要考虑的东西。
假设现在有两组数据,这里默认我都写成了0,还有一组字符串数组是X轴的值;

    /**
     * 左边柱状图数据
     */
    private int[] mDataLeft = {0, 0, 0, 0};
    /**
     * 右边柱状图数据
     */
    private int[] mDataRight = {0, 0, 0, 0};
    /**
     * X轴底部文字
     */
    private String[] mDataTextX = {"", "", "", ""};

计算:

想要绘制出Y轴的数字,那么必须计算,这里我是从0开始显示,后面分成5等分,那么Y轴肯定有个最大值,这个最大值的定义规则如下:先找出两个数据数组中的最大值,然后判断这个最大值是不是5的整倍数,如果不是则自加加到是5的整倍数为止;

      /**
     * 数据
     *
     * @param dataLeft  左边柱状图数据
     * @param dataRight 右边柱状图数据
     * @param dataTextX X轴文字数组
     */
    public void setData(int[] dataLeft, int[] dataRight, String[] dataTextX) {
        this.mDataLeft = dataLeft;
        this.mDataRight = dataRight;
        this.mDataTextX = dataTextX;
        //找出两个数组中的最大值
        int maxLeft = getMax(mDataLeft);
        int maxRight = getMax(mDataRight);
        mMaxData = maxLeft > maxRight ? maxLeft : maxRight;
        Log.i(TAG, "mMaxCount=" + mMaxData);
        //计算Y轴坐标最大值,根据当前最大数据并且是5的整倍数计算
        while (mMaxData % 5 != 0) {
            mMaxData++;
        }
        Log.i(TAG, "mMaxCount=" + mMaxData);
    }

绘制:

第一步:绘制柱状图drawLineData(canvas);,为什么不先绘制XY轴,因为这里有个覆盖的问题,XY轴和柱形图的覆盖问题,虽然差别不大,但是影响美观。

    /**
     * 绘制柱状图
     */
    private void drawLineData(Canvas canvas) {
        for (int i = 0; i < mDataLeft.length; i++) {
            float startX = mYDistance + mBigDistance + mLineWidth / 2 + (i * (mBigDistance + mSmallDistance + 2 * mLineWidth));
            float endY = ((mViewHeight - mXDistance)) - (mDataLeft[i] * (mViewHeight - 2 * mXDistance)) / mMaxData;
            canvas.drawLine(startX, mViewHeight - mXDistance, startX, endY, mPaintLeft);
            String text = mDataLeft[i] + "";
            float textWidth = mPaintTextLeft.measureText(text, 0, text.length());
            canvas.drawText(text, startX - textWidth / 2, endY - 15, mPaintTextLeft);
        }

        for (int i = 0; i < mDataRight.length; i++) {
            float startX = mYDistance + mBigDistance + mSmallDistance + mLineWidth + mLineWidth / 2 + (i * (mBigDistance + mSmallDistance + 2 * mLineWidth));
            float endY = ((mViewHeight - mXDistance)) - (mDataRight[i] * (mViewHeight - 2 * mXDistance)) / mMaxData;
            canvas.drawLine(startX, mViewHeight - mXDistance, startX, endY, mPaintRight);
            String text = mDataRight[i] + "";
            float textWidth = mPaintTextRight.measureText(text, 0, text.length());
            canvas.drawText(text, startX - textWidth / 2, endY - 15, mPaintTextRight);
        }
    }

这里绘制是使用了drawLine方法,那么就需要起始点和终点的坐标值,这里的坐标都是有规律的,讲一大堆不如来个草图(这是左柱状图的计算方式,右柱状图同理)~

drawLine的坐标计算草图.png

第二步:绘制坐标轴drawLineXY(canvas);

    /**
     * 绘制坐标轴
     */
    private void drawLineXY(Canvas canvas) {
        canvas.drawLine(mYDistance, mViewHeight - mXDistance, mYDistance, 15, mPaintTextXY);
        canvas.drawLine(mYDistance, mViewHeight - mXDistance, mViewWidth - 15, mViewHeight - mXDistance, mPaintTextXY);

        if (mIsShowArrow) {
            //Y轴箭头
            canvas.drawLine(mYDistance, 15, mYDistance - 10, 25, mPaintTextXY);
            canvas.drawLine(mYDistance, 15, mYDistance + 10, 25, mPaintTextXY);
            //X轴箭头
            canvas.drawLine(mViewWidth - 15, mViewHeight - mXDistance, mViewWidth - 25, mViewHeight - mXDistance - 10, mPaintTextXY);
            canvas.drawLine(mViewWidth - 15, mViewHeight - mXDistance, mViewWidth - 25, mViewHeight - mXDistance + 10, mPaintTextXY);

        }
    }

第三步:绘制X坐标值drawLineX(canvas);

    /**
     * 绘制X坐标值
     */
    private void drawLineX(Canvas canvas) {
        for (int i = 0; i < mDataTextX.length; i++) {
            //绘制进度数字
            String text = mDataTextX[i];
            //获取文字宽度
            float textWidth = mPaintTextXY.measureText(text, 0, text.length());
            float dx = (mYDistance + mBigDistance + mSmallDistance / 2 + mLineWidth + (i * (mBigDistance + mSmallDistance + 2 * mLineWidth))) - textWidth / 2;
            Paint.FontMetricsInt fontMetricsInt = mPaintTextXY.getFontMetricsInt();
            float dy = (fontMetricsInt.bottom - fontMetricsInt.top) / 2 - fontMetricsInt.bottom;
            float baseLine = mViewHeight - mXDistance / 2 + dy;
            canvas.drawText(text, dx, baseLine, mPaintTextXY);
        }
    }

第四步:绘制Y坐标值drawLineY(canvas);

    /**
     * 绘制Y坐标值
     * 这里的坐标值是根据最大值计算出来对应的间隔,然后从0显示出6个数据
     */
    private void drawLineY(Canvas canvas) {
        for (int i = 0; i < 6; i++) {
            //绘制进度数字
            String text = (mMaxData / 5 * i) + "";
            //获取文字宽度
            float textWidth = mPaintTextXY.measureText(text, 0, text.length());
            float dx = mYDistance / 2 - textWidth / 2;
            Paint.FontMetricsInt fontMetricsInt = mPaintTextXY.getFontMetricsInt();
            float dy = (fontMetricsInt.bottom - fontMetricsInt.top) / 2 - fontMetricsInt.bottom;
            float baseLine = (mViewHeight - mXDistance) - (i * (mViewHeight - 2 * mXDistance) / 5) + dy;
            canvas.drawText(text, dx, baseLine, mPaintTextXY);
            if (mIsShowArrowYInterval) canvas.drawLine(mYDistance, (mViewHeight - mXDistance)
                            - (i * (mViewHeight - 2 * mXDistance) / 5), mYDistance + 10,
                    (mViewHeight - mXDistance) - (i * (mViewHeight - 2 * mXDistance) / 5), mPaintTextXY);
        }
    }

最后再处理一下动画这块就差不多了:

    /**
     * 开启动画,并且绘制图表
     */
    public void start() {
        AnimatorSet set = new AnimatorSet();
        for (int i = 0; i < mDataLeft.length; i++) {
            ValueAnimator animator = ValueAnimator.ofInt(0, mDataLeft[i]);
            final int finalI = i;
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator valueAnimator) {
                    mDataLeft[finalI] = (int) valueAnimator.getAnimatedValue();
                    invalidate();
                }
            });
            set.playTogether(animator);
        }

        for (int i = 0; i < mDataRight.length; i++) {
            ValueAnimator animator = ValueAnimator.ofInt(0, mDataRight[i]);
            final int finalI = i;
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator valueAnimator) {
                    mDataRight[finalI] = (int) valueAnimator.getAnimatedValue();
                    invalidate();
                }
            });
            set.playTogether(animator);
        }
        set.setInterpolator(new DecelerateInterpolator());
        set.setDuration(mAnimationDuration);
        set.start();
    }

好的,写到这里就可以在Activity中使用了,下面上全家福咯,注释很详细。

DoubleLineChatView.java

import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.DecelerateInterpolator;

/**
 * Created by zhuyong on 2017/10/12.
 */

public class DoubleLineChatView extends View {

    private static final String TAG = "DoubleLineChatView";
    /**
     * 上下文
     */
    private Context mContext;
    /**
     * 左边柱状图画笔
     */
    private Paint mPaintLeft;
    /**
     * 右边柱状图画笔
     */
    private Paint mPaintRight;
    /**
     * 左边柱状图文字画笔
     */
    private Paint mPaintTextLeft;
    /**
     * 右边柱状图文字画笔
     */
    private Paint mPaintTextRight;
    /**
     * XY轴画笔
     */
    private Paint mPaintTextXY;

    /**
     * 左边柱状图数据
     */
    private int[] mDataLeft = {0, 0, 0, 0};
    /**
     * 右边柱状图数据
     */
    private int[] mDataRight = {0, 0, 0, 0};
    /**
     * X轴底部文字
     */
    private String[] mDataTextX = {"", "", "", ""};

    /**
     * Y轴文字宽度
     */
    private float mYDistance;
    /**
     * X轴底部高度
     */
    private float mXDistance;
    /**
     * 柱状图宽度
     */
    private float mLineWidth;
    /**
     * 颜色字体等变量
     */
    private int mLeftLineBackGroundColor = Color.RED;
    private int mRightLineBackGroundColor = Color.BLUE;
    private int mLeftLineTextColor = Color.RED;
    private int mRightLineTextColor = Color.BLUE;
    private float mLineTextSize;
    private int mLineXYColor = Color.BLACK;
    private float mLineXYSize;
    /**
     * 是否显示XY轴箭头
     */
    private boolean mIsShowArrow = true;
    /**
     * 是否显示Y轴间隔标记
     */
    private boolean mIsShowArrowYInterval = true;
    /**
     * 动画时长,默认1秒
     */
    private int mAnimationDuration = 1000;
    /**
     * View宽度
     */
    private int mViewWidth;
    /**
     * View高度
     */
    private int mViewHeight;
    /**
     * 柱状图的Y轴最大数据,等于或者大于两个数组中的最大值
     */
    private int mMaxData;
    /**
     * 两组柱状图之间的距离(偏大距离)
     */
    private float mBigDistance;
    /**
     * 两个柱状图之间的距离(偏小距离)
     */
    private float mSmallDistance;

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

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

    public DoubleLineChatView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
        //获取自定义属性
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.DoubleLineChatView);
        //柱状图的宽度
        mLineWidth = array.getDimension(R.styleable.DoubleLineChatView_chatview_line_width, dip2px(mContext, 30));
        //左边柱状图的背景色
        mLeftLineBackGroundColor = array.getColor(R.styleable.DoubleLineChatView_chatview_left_background_color, mLeftLineBackGroundColor);
        //右边柱状图的背景色
        mRightLineBackGroundColor = array.getColor(R.styleable.DoubleLineChatView_chatview_right_background_color, mRightLineBackGroundColor);
        //左边柱状图的数据字体颜色
        mLeftLineTextColor = array.getColor(R.styleable.DoubleLineChatView_chatview_left_text_data_color, mLeftLineTextColor);
        //右边柱状图的数据字体颜色
        mRightLineTextColor = array.getColor(R.styleable.DoubleLineChatView_chatview_right_text_data_color, mRightLineTextColor);
        //柱状图的数据字体大小
        mLineTextSize = array.getDimension(R.styleable.DoubleLineChatView_chatview_text_data_size, sp2px(mContext, 14));
        //XY轴的颜色以及字体颜色
        mLineXYColor = array.getColor(R.styleable.DoubleLineChatView_chatview_text_xy_color, mLineXYColor);
        //XY轴的字体大小
        mLineXYSize = array.getDimension(R.styleable.DoubleLineChatView_chatview_text_xy_size, sp2px(mContext, 14));
        //两组柱状图之间的距离(偏大距离)
        mBigDistance = array.getDimension(R.styleable.DoubleLineChatView_chatview_line_big_distance, dip2px(mContext, 20));
        //两个柱状图之间的距离(偏小距离)
        mSmallDistance = array.getDimension(R.styleable.DoubleLineChatView_chatview_line_small_distance, dip2px(mContext, 5));
        //是否显示XY轴的箭头
        mIsShowArrow = array.getBoolean(R.styleable.DoubleLineChatView_chatview_show_arrow, true);
        //是否显示Y轴的数据间隔标志
        mIsShowArrowYInterval = array.getBoolean(R.styleable.DoubleLineChatView_chatview_show_y_interval, true);
        //柱状图生长动画时间,默认1秒
        mAnimationDuration = array.getInteger(R.styleable.DoubleLineChatView_chatview_animation_duration, 1000);
        //Y轴数据的宽度,也就是Y轴距离左边的宽度,这个要根据数据字符串的长度进行调整
        mYDistance = array.getDimension(R.styleable.DoubleLineChatView_chatview_y_distance, dip2px(mContext, 40));
        //X轴数据的高度,也就是X轴距离底部的距离
        mXDistance = array.getDimension(R.styleable.DoubleLineChatView_chatview_x_distance, dip2px(mContext, 40));
        array.recycle();
        initView();
    }

    /**
     * 初始化画笔
     */
    private void initView() {
        //左边柱状图
        mPaintLeft = new Paint();
        mPaintLeft.setColor(mLeftLineBackGroundColor);
        mPaintLeft.setStrokeWidth(mLineWidth);
        mPaintLeft.setAntiAlias(true);

        //右边柱状图
        mPaintRight = new Paint();
        mPaintRight.setColor(mRightLineBackGroundColor);
        mPaintRight.setStrokeWidth(mLineWidth);
        mPaintRight.setAntiAlias(true);

        //左边柱状图字体数据
        mPaintTextLeft = new Paint();
        mPaintTextLeft.setColor(mLeftLineTextColor);
        mPaintTextLeft.setTextSize(mLineTextSize);
        mPaintTextLeft.setAntiAlias(true);

        //右边柱状图字体数据
        mPaintTextRight = new Paint();
        mPaintTextRight.setColor(mRightLineTextColor);
        mPaintTextRight.setTextSize(mLineTextSize);
        mPaintTextRight.setAntiAlias(true);

        //XY轴
        mPaintTextXY = new Paint();
        mPaintTextXY.setStrokeWidth(3);
        mPaintTextXY.setColor(mLineXYColor);
        mPaintTextXY.setTextSize(mLineXYSize);
        mPaintTextXY.setAntiAlias(true);
    }

    /**
     * 数据
     *
     * @param dataLeft  左边柱状图数据
     * @param dataRight 右边柱状图数据
     * @param dataTextX X轴文字数组
     */
    public void setData(int[] dataLeft, int[] dataRight, String[] dataTextX) {
        this.mDataLeft = dataLeft;
        this.mDataRight = dataRight;
        this.mDataTextX = dataTextX;
        //找出两个数组中的最大值
        int maxLeft = getMax(mDataLeft);
        int maxRight = getMax(mDataRight);
        mMaxData = maxLeft > maxRight ? maxLeft : maxRight;
        Log.i(TAG, "mMaxCount=" + mMaxData);
        //计算Y轴坐标最大值,根据当前最大数据并且是5的整倍数计算
        while (mMaxData % 5 != 0) {
            mMaxData++;
        }
        Log.i(TAG, "mMaxCount=" + mMaxData);
    }

    /**
     * 找出数组中的最大值
     *
     * @param arr 目标数组
     * @return 最大值
     */
    private static int getMax(int[] arr) {
        int max = arr[0];
        for (int i = 1; i < arr.length; i++) {
            if (arr[i] > max) {
                max = arr[i];
            }
        }
        return max;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setDimension(heightMeasureSpec);
    }

    /**
     * 重新赋予宽高
     */
    private void setDimension(int heightMeasureSpec) {
        //
        mViewWidth = (int) (mYDistance + mBigDistance + (mDataLeft.length * (mLineWidth * 2 + mBigDistance + mSmallDistance)));
        mViewHeight = MeasureSpec.getSize(heightMeasureSpec);
        Log.i(TAG, "mViewWidth=" + mViewWidth + "px");
        Log.i(TAG, "mViewHeight=" + mViewHeight + "px");
        setMeasuredDimension(mViewWidth, mViewHeight);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        /**
         * 绘制柱状图
         */
        drawLineData(canvas);
        /**
         * 绘制坐标轴
         */
        drawLineXY(canvas);
        /**
         * 绘制X坐标值
         */
        drawLineX(canvas);
        /**
         * 绘制Y坐标值
         */
        drawLineY(canvas);
    }

    /**
     * 绘制X坐标值
     */
    private void drawLineX(Canvas canvas) {
        for (int i = 0; i < mDataTextX.length; i++) {
            //绘制进度数字
            String text = mDataTextX[i];
            //获取文字宽度
            float textWidth = mPaintTextXY.measureText(text, 0, text.length());
            float dx = (mYDistance + mBigDistance + mSmallDistance / 2 + mLineWidth + (i * (mBigDistance + mSmallDistance + 2 * mLineWidth))) - textWidth / 2;
            Paint.FontMetricsInt fontMetricsInt = mPaintTextXY.getFontMetricsInt();
            float dy = (fontMetricsInt.bottom - fontMetricsInt.top) / 2 - fontMetricsInt.bottom;
            float baseLine = mViewHeight - mXDistance / 2 + dy;
            canvas.drawText(text, dx, baseLine, mPaintTextXY);
        }
    }

    /**
     * 绘制Y坐标值
     * 这里的坐标值是根据最大值计算出来对应的间隔,然后从0显示出6个数据
     */
    private void drawLineY(Canvas canvas) {
        for (int i = 0; i < 6; i++) {
            //绘制进度数字
            String text = (mMaxData / 5 * i) + "";
            //获取文字宽度
            float textWidth = mPaintTextXY.measureText(text, 0, text.length());
            float dx = mYDistance / 2 - textWidth / 2;
            Paint.FontMetricsInt fontMetricsInt = mPaintTextXY.getFontMetricsInt();
            float dy = (fontMetricsInt.bottom - fontMetricsInt.top) / 2 - fontMetricsInt.bottom;
            float baseLine = (mViewHeight - mXDistance) - (i * (mViewHeight - 2 * mXDistance) / 5) + dy;
            canvas.drawText(text, dx, baseLine, mPaintTextXY);
            if (mIsShowArrowYInterval) canvas.drawLine(mYDistance, (mViewHeight - mXDistance)
                            - (i * (mViewHeight - 2 * mXDistance) / 5), mYDistance + 10,
                    (mViewHeight - mXDistance) - (i * (mViewHeight - 2 * mXDistance) / 5), mPaintTextXY);
        }
    }

    /**
     * 绘制坐标轴
     */
    private void drawLineXY(Canvas canvas) {
        canvas.drawLine(mYDistance, mViewHeight - mXDistance, mYDistance, 15, mPaintTextXY);
        canvas.drawLine(mYDistance, mViewHeight - mXDistance, mViewWidth - 15, mViewHeight - mXDistance, mPaintTextXY);

        if (mIsShowArrow) {
            //Y轴箭头
            canvas.drawLine(mYDistance, 15, mYDistance - 10, 25, mPaintTextXY);
            canvas.drawLine(mYDistance, 15, mYDistance + 10, 25, mPaintTextXY);
            //X轴箭头
            canvas.drawLine(mViewWidth - 15, mViewHeight - mXDistance, mViewWidth - 25, mViewHeight - mXDistance - 10, mPaintTextXY);
            canvas.drawLine(mViewWidth - 15, mViewHeight - mXDistance, mViewWidth - 25, mViewHeight - mXDistance + 10, mPaintTextXY);

        }
    }

    /**
     * 绘制柱状图
     */
    private void drawLineData(Canvas canvas) {
        for (int i = 0; i < mDataLeft.length; i++) {
            float startX = mYDistance + mBigDistance + mLineWidth / 2 + (i * (mBigDistance + mSmallDistance + 2 * mLineWidth));
            float endY = (mViewHeight - mXDistance) - (mDataLeft[i] * (mViewHeight - 2 * mXDistance)) / mMaxData;
            canvas.drawLine(startX, mViewHeight - mXDistance, startX, endY, mPaintLeft);
            String text = mDataLeft[i] + "";
            float textWidth = mPaintTextLeft.measureText(text, 0, text.length());
            canvas.drawText(text, startX - textWidth / 2, endY - 15, mPaintTextLeft);
        }

        for (int i = 0; i < mDataRight.length; i++) {
            float startX = mYDistance + mBigDistance + mSmallDistance + mLineWidth + mLineWidth / 2 + (i * (mBigDistance + mSmallDistance + 2 * mLineWidth));
            float endY = ((mViewHeight - mXDistance)) - (mDataRight[i] * (mViewHeight - 2 * mXDistance)) / mMaxData;
            canvas.drawLine(startX, mViewHeight - mXDistance, startX, endY, mPaintRight);
            String text = mDataRight[i] + "";
            float textWidth = mPaintTextRight.measureText(text, 0, text.length());
            canvas.drawText(text, startX - textWidth / 2, endY - 15, mPaintTextRight);
        }
    }

    /**
     * 开启动画,并且绘制图表
     */
    public void start() {
        AnimatorSet set = new AnimatorSet();
        for (int i = 0; i < mDataLeft.length; i++) {
            ValueAnimator animator = ValueAnimator.ofInt(0, mDataLeft[i]);
            final int finalI = i;
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator valueAnimator) {
                    mDataLeft[finalI] = (int) valueAnimator.getAnimatedValue();
                    invalidate();
                }
            });
            set.playTogether(animator);
        }

        for (int i = 0; i < mDataRight.length; i++) {
            ValueAnimator animator = ValueAnimator.ofInt(0, mDataRight[i]);
            final int finalI = i;
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator valueAnimator) {
                    mDataRight[finalI] = (int) valueAnimator.getAnimatedValue();
                    invalidate();
                }
            });
            set.playTogether(animator);
        }
        set.setInterpolator(new DecelerateInterpolator());
        set.setDuration(mAnimationDuration);
        set.start();
    }

    /**
     * sp转px
     */
    public static int sp2px(Context context, float spValue) {
        final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
        return (int) (spValue * fontScale + 0.5f);
    }

    /**
     * dp转px
     */
    public static int dip2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical"
    tools:context="com.zhuyong.doublelinechatview.MainActivity">

    <HorizontalScrollView
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:layout_marginTop="15dp"
        android:background="#DEDEDE"
        android:scrollbars="none">

        <com.zhuyong.doublelinechatview.DoubleLineChatView
            android:id="@+id/line_chat_one"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            app:chatview_animation_duration="0"
            app:chatview_left_background_color="@color/colorAccent"
            app:chatview_left_text_data_color="@color/colorAccent"
            app:chatview_line_big_distance="20dp"
            app:chatview_line_small_distance="3dp"
            app:chatview_line_width="25dp"
            app:chatview_right_background_color="@color/colorPrimaryDark"
            app:chatview_right_text_data_color="@color/colorPrimaryDark"
            app:chatview_show_arrow="true"
            app:chatview_show_y_interval="true"
            app:chatview_text_data_size="14sp"
            app:chatview_text_xy_color="@color/black"
            app:chatview_text_xy_size="14sp"
            app:chatview_x_distance="40dp"
            app:chatview_y_distance="45dp" />

    </HorizontalScrollView>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="15dp">

        <Button
            android:id="@+id/btn_start"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="开始动画" />

        <LinearLayout
            android:id="@+id/llayout_uncheck"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:gravity="center"
            android:orientation="horizontal">

            <View
                android:layout_width="15dp"
                android:layout_height="15dp"
                android:background="@color/yellow" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="5dp"
                android:text="未审核数" />

        </LinearLayout>

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_below="@+id/llayout_uncheck"
            android:layout_marginTop="5dp"
            android:gravity="center"
            android:orientation="horizontal">

            <View
                android:layout_width="15dp"
                android:layout_height="15dp"
                android:background="@color/green" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="5dp"
                android:text="已审核数" />

        </LinearLayout>

    </RelativeLayout>

    <com.zhuyong.doublelinechatview.DoubleLineChatView
        android:id="@+id/line_chat_two"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_marginTop="15dp"
        app:chatview_animation_duration="1000"
        app:chatview_left_background_color="@color/yellow"
        app:chatview_left_text_data_color="@color/yellow"
        app:chatview_line_big_distance="20dp"
        app:chatview_line_small_distance="3dp"
        app:chatview_line_width="25dp"
        app:chatview_right_background_color="@color/green"
        app:chatview_right_text_data_color="@color/green"
        app:chatview_show_arrow="true"
        app:chatview_show_y_interval="true"
        app:chatview_text_data_size="14sp"
        app:chatview_text_xy_color="@color/black"
        app:chatview_text_xy_size="14sp"
        app:chatview_x_distance="40dp"
        app:chatview_y_distance="45dp" />

</LinearLayout>

MainActivity.java

public class MainActivity extends AppCompatActivity {
    /**
     * 第一组数据
     */
    private int[] mDataLeft = {60, 180, 130, 10, 299, 45, 199, 20, 250};
    private int[] mDataRight = {151, 65, 280, 66, 105, 88, 198, 299, 45};
    private String[] mDataTextX = {"项目1", "项目2", "项目3", "项目4", "项目5", "项目6", "项目7", "项目8", "项目9"};

    /**
     * 第二组数据
     */
    private int[] mDataLeftTwo = {60, 181, 130, 100};
    private int[] mDataRightTwo = {151, 65, 40, 20};
    private String[] mDataTextXTwo = {"测试1", "测试2", "测试3", "测试4"};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final DoubleLineChatView doubleLineChatViewOne = (DoubleLineChatView) findViewById(R.id.line_chat_one);
        doubleLineChatViewOne.setData(mDataLeft, mDataRight, mDataTextX);
        doubleLineChatViewOne.start();

        final DoubleLineChatView doubleLineChatViewTwo = (DoubleLineChatView) findViewById(R.id.line_chat_two);
        doubleLineChatViewTwo.setData(mDataLeftTwo, mDataRightTwo, mDataTextXTwo);

        findViewById(R.id.btn_start).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                doubleLineChatViewTwo.start();
            }
        });
    }
}

GitHub传送门:源码

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,681评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,710评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,623评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,202评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,232评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,368评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,795评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,461评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,647评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,476评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,525评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,226评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,785评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,857评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,090评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,647评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,215评论 2 341

推荐阅读更多精彩内容