Android鬼点子 创造一个条形统计图

好久没有发文了,分享一个自己做的条形统计图,有动效的哦!

实现了以上两种效果,但是不知道哪种好看……

可以设置数值的范围,每个条形可以点击,可以触发点击事件。滚动到开始或末尾有侵入wave式的提示。可以修改的属性都加了注释。目前显示300多条数据无卡顿。

代码中主要展示了自定义View的大体步骤。渐变的画法,两圆角矩形的画法。常见动画的实现。滚动的手势处理。等等

直接代码:

package top.greendami.greendami;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;

import java.util.List;

/**
 * 方块统计图
 * Created by zhaopy on 2017/5/24.
 */

public class PPHistogram extends View {

    //用于画矩形色块
    int[] mColors = {0xff83ccd2, 0xffc0e1ce, 0xfffac55e};
    private int mXDown, mLastX, mLastY;
    float lastStartX = 0;//抬起手指后,当前控件最左边X的坐标
    //数据
    List mDatas;
    //文字画笔,默认画笔
    Paint textPaint, defaultPaint;

    int MAX = 100;//纵坐标 0~MAX ,MAX必须是5的倍数
    float startX = 100;
    float perPx;//每条宽度
    float borderLeft = 10;
    float borderBottom = 10;
    float XofY = 10;//纵轴线的X坐标
    float YofX = 10;//横轴线的Y坐标
    float rx = DPUnitUtil.dip2px(getContext(), 2);
    float ry = DPUnitUtil.dip2px(getContext(), 2);
    float width = DPUnitUtil.dip2px(getContext(), 5);//柱子宽度

    int selectIndex = -1;
    int state = -100;

    int countInOne = 40;//一屏幕宽度显示的圆柱个数

    HistogramClickListener mListener;
    int[] tempdatas;

    boolean isNoMore = false;//时候滚到边界

    public void setHistogramClickListener(HistogramClickListener mListener) {
        this.mListener = mListener;
    }

    public void setmDatas(List mDatas, int max) {
        setmDatas(mDatas, max, true);
    }

    public void setmDatas(List mDatas, int max, boolean isAnim) {
        this.mDatas = mDatas;
        this.MAX = max;
        tempdatas = new int[mDatas.size()];
        for (int i = 0; i < tempdatas.length; i++) {
            if (isAnim) {
                tempdatas[i] = 0;
            } else {
                tempdatas[i] = mDatas.get(i).getValue();
            }
        }
        invalidate();
    }

    public PPHistogram(Context context) {
        super(context);
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        setClickable(true);
    }

    public PPHistogram(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        setClickable(true);
    }

    //初始化单位
    private void initializeTheUnit() {
        perPx = DPUnitUtil.dip2px(getContext(), 10);
        borderLeft = DPUnitUtil.dip2px(getContext(), 3);
        borderBottom = DPUnitUtil.dip2px(getContext(), 15);
        XofY = textPaint.measureText("" + MAX) + borderLeft + DPUnitUtil.dip2px(getContext(), 2);
        if (startX > XofY) {
            startX = XofY;
            lastStartX = startX;
        }
        YofX = getHeight() - (textPaint.getFontMetrics().bottom - textPaint.getFontMetrics().top) - borderBottom - DPUnitUtil.dip2px(getContext(), 2);
    }

    //初始化画笔
    private void initPaint() {
        if (textPaint == null) {
            textPaint = new Paint();
        } else {
            textPaint.reset();
        }
        if (defaultPaint == null) {
            defaultPaint = new Paint();
        } else {
            defaultPaint.reset();
        }
        textPaint.setAntiAlias(true);
        textPaint.setTextSize(getWidth() / 28);
        textPaint.setColor(0xff159461);

        defaultPaint.setAntiAlias(true);
    }

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

        //画纵坐标轴
        paintingYAxis(canvas);
        //画横坐标轴
        paintingXAxis(canvas);
        //画数据
        paintingDatas(canvas);
        if (isNoMore) {
            paintingWave(canvas);
        }
        postInvalidate();
    }

    //当滚动到边界是显示波浪
    private void paintingWave(Canvas canvas) {
        float waveWidth = getWidth() / 6;//最大宽度约为控件宽度的六分之一
        WindowManager wm = (WindowManager) getContext()
                .getSystemService(Context.WINDOW_SERVICE);
        Path mPath = new Path();

        int width = wm.getDefaultDisplay().getWidth();
        initPaint();
        defaultPaint.setColor(0x4349c29d);
        defaultPaint.setStyle(Paint.Style.FILL);

        if (startX == XofY) {
            //左边
            waveWidth = waveWidth * (mLastX / (float) width);
            mPath.moveTo(XofY, YofX);
            mPath.lineTo(XofY, borderBottom);
            mPath.lineTo(XofY + 30, borderBottom);
            mPath.quadTo(XofY + 30 + waveWidth, mLastY, XofY + 30, YofX);
            mPath.lineTo(XofY, YofX);
            mPath.close();
        } else {
            //右边
            waveWidth = waveWidth * ((width - mLastX) / (float) width);
            mPath.moveTo(getWidth(), YofX);
            mPath.lineTo(getWidth(), borderBottom);
            mPath.lineTo(getWidth() - 30, borderBottom);
            mPath.quadTo(getWidth() - 30 - waveWidth, mLastY, getWidth() - 30, YofX);
            mPath.lineTo(getWidth(), YofX);
            mPath.close();
        }

        canvas.drawPath(mPath, defaultPaint);
    }

    private void paintingXAxis(Canvas canvas) {
        initPaint();
        defaultPaint.setStrokeWidth(2);
        defaultPaint.setColor(0xffCAF2E6);
        canvas.drawLine(XofY, YofX, getWidth(), YofX, defaultPaint);
        defaultPaint.setColor(0xff49c29d);
        canvas.drawRect(0, YofX + DPUnitUtil.dip2px(getContext(), 1), getWidth(), getHeight(), defaultPaint);
        if (mDatas == null || mDatas.size() == 0) {
            return;
        }
        float step = (getWidth() - XofY - borderLeft) / countInOne;
        //第一行的Y轴坐标
        float textY = YofX + (textPaint.getFontMetrics().bottom - textPaint.getFontMetrics().top) - DPUnitUtil.dip2px(getContext(), 1f);
        //第二行的Y轴坐标
        float textY2 = textY + (textPaint.getFontMetrics().bottom - textPaint.getFontMetrics().top) - DPUnitUtil.dip2px(getContext(), 6f);
        //X轴日期的颜色
        textPaint.setColor(0xffffffff);
        for (int i = 0; i < mDatas.size(); i++) {
            PPHistogramBean pphb = mDatas.get(i);
            String text = pphb.getEndTime();
            //每12个柱子,画一个横坐标数值
            if (i % 12 == 0 && startX + (i + 0.5f) * step > 0 && startX + (i + 0.5f) * step < getWidth()) {
                textPaint.setTextSize(getWidth() / 34);
                canvas.drawText(text.split(" ")[0], startX + (i + 0.5f) * step - textPaint.measureText(text.split(" ")[0]) / 2, textY2, textPaint);
                textPaint.setTextSize(getWidth() / 32);
                canvas.drawText(text.split(" ")[1], startX + (i + 0.5f) * step - textPaint.measureText(text.split(" ")[1]) / 2, textY, textPaint);
            }

        }

    }

    private void paintingYAxis(Canvas canvas) {
        initPaint();
//        defaultPaint.setColor(0xff49c29d);
//        canvas.drawLine(XofY, 0, XofY, YofX, defaultPaint);
        defaultPaint.setStrokeWidth(1);
        defaultPaint.setColor(0xffCAF2E6);

        textPaint.setColor(0xff49c29d);
        textPaint.setTextSize(getWidth() / 32);

        float step = (YofX - borderBottom) / 5;
        float textH = (textPaint.getFontMetrics().bottom + textPaint.getFontMetrics().top) / 2;
        //写纵坐标数字
        for (int i = 0; i <= 5; i++) {
            //文字右对齐
            float x = textPaint.measureText(MAX + "") - textPaint.measureText(i * MAX / 5 + "");

            if (i == 0) {
                //0的位置不居中
                canvas.drawText(i * MAX / 5 + "", borderLeft + x, YofX, textPaint);
            } else {

                canvas.drawText(i * MAX / 5 + "", borderLeft + x, YofX - i * step - textH, textPaint);
            }

            canvas.drawLine(XofY, YofX - i * step, getWidth(), YofX - i * step, defaultPaint);
        }

    }

    private void paintingDatas(Canvas canvas) {
        if (mDatas == null || mDatas.size() == 0) {
            return;
        }
        initPaint();
        defaultPaint.setColor(0xff83ccd2);
        float step = (getWidth() - XofY - borderLeft) / countInOne;

//        LinearGradient lg;
//        lg = new LinearGradient(getWidth(), 0, getWidth(), YofX, 0xff23AC75, 0xff95ECD1, Shader.TileMode.CLAMP);
//        defaultPaint.setShader(lg);

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

            //屏幕之外不用绘制
            if (startX + (i + 0.5f) * step - width / 2 < XofY || startX + (i + 0.5f) * step + width / 2 > getWidth()) {
                continue;
            }

            PPHistogramBean pphb = mDatas.get(i);

            if (tempdatas[i] < pphb.getValue() * 0.9f) {
                int raiseStep = (int) (pphb.getValue() * 0.1f);
                if (raiseStep < 1) {
                    raiseStep = 1;
                }
                tempdatas[i] = tempdatas[i] + raiseStep;
            } else {
                tempdatas[i] = pphb.getValue();
            }
            if (selectIndex != i) {
                //正常颜色
                defaultPaint.setColor(0xff49c29d);
            } else {
                //选中颜色
                defaultPaint.setColor(0xff4988c2);
            }
            //选择了的与其他的颜色不一样
//            if (selectIndex != i) {
                lg = new LinearGradient(startX + (i + 0.5f) * step - width / 2, YofX - tempdatas[i] * (YofX - borderBottom) / MAX, startX + (i + 0.5f) * step + width / 2, YofX, 0xff23AC75, 0xff49c29d, Shader.TileMode.CLAMP);
//            } else {
//                lg = new LinearGradient(startX + (i + 0.5f) * step - width / 2, YofX - tempdatas[i] * (YofX - borderBottom) / MAX, startX + (i + 0.5f) * step + width / 2, YofX, 0xff4988c2, 0xffE1EBF5, Shader.TileMode.CLAMP);
//            }
//            defaultPaint.setShader(lg);

            RectF oval = new RectF(startX + (i + 0.5f) * step - width / 2, YofX - tempdatas[i] * (YofX - borderBottom) / MAX, startX + (i + 0.5f) * step + width / 2, YofX);
//            canvas.drawLine(XofY + (i + 0.5f) * step, pphb.getValue() * (YofX - borderBottom) / MAX + borderBottom, XofY + (i + 0.5f) * step, YofX, defaultPaint);
            canvas.drawRoundRect(oval, rx, ry, defaultPaint);
            canvas.save();
            oval = new RectF(
                    startX + (i + 0.5f) * step - width / 2,
                    Math.min(YofX - tempdatas[i] * (YofX - borderBottom) / MAX + ry, YofX),
                    startX + (i + 0.5f) * step + width / 2,
                    YofX);
            canvas.drawRoundRect(oval, 0, 0, defaultPaint);
            canvas.save();
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (mDatas == null || mDatas.size() == 0) {
            selectIndex = -1;
            return super.onTouchEvent(event);
        }
        final int action = event.getAction();
        float step = (getWidth() - XofY - borderLeft) / countInOne;
        isNoMore = false;
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                // 按下
                mXDown = (int) event.getRawX();
                state = MotionEvent.ACTION_DOWN;
                break;

            case MotionEvent.ACTION_MOVE:
                // 移动
                mLastX = (int) event.getRawX();
                mLastY = (int) event.getY();
                //滑动限制
                if (lastStartX + (mLastX - mXDown) * 2 > XofY) {
                    startX = XofY;
                    state = MotionEvent.ACTION_MOVE;
                    isNoMore = true;
                    postInvalidate();
                    break;
                }
                if (lastStartX + (mLastX - mXDown) * 2 + step * (mDatas.size() + 0.5f) < getWidth()) {
                    startX = -step * (mDatas.size() + 0.5f) + getWidth();
                    state = MotionEvent.ACTION_MOVE;
                    isNoMore = true;
                    postInvalidate();
                    break;
                }
                state = MotionEvent.ACTION_MOVE;
                startX = lastStartX + (mLastX - mXDown) * 2;

                postInvalidate();
                break;

            case MotionEvent.ACTION_UP:
                // 抬起
                lastStartX = startX;
                state = MotionEvent.ACTION_UP;
                if (Math.abs(event.getRawX() - mXDown) < 2) {
                    selectIndex = (int) ((event.getRawX() - startX) / step) - 1;
                    if (mListener != null && selectIndex >= 0) {
                        mListener.onHistogramClick(selectIndex);
                    }
                } else {
                    selectIndex = -1;
                }

                postInvalidate();
                break;
            default:
                break;
        }
        return super.onTouchEvent(event);
    }

    public abstract static class HistogramClickListener {
        public abstract void onHistogramClick(int position);
    }

    /**
     * 数据结构
     */
    public static class PPHistogramBean {
        int value;
        String startTime;
        String endTime;

        public int getValue() {
            return value;
        }

        public void setValue(int value) {
            this.value = value;
        }

        public String getStartTime() {
            return startTime;
        }

        public void setStartTime(String startTime) {
            this.startTime = startTime;
        }

        public String getEndTime() {
            return endTime;
        }

        public void setEndTime(String endTime) {
            this.endTime = endTime;
        }
    }
}

复制代码

各种统计图简直画吐了,有兴趣百度“Android鬼点子”系列其他文章。 欢迎加QQ 545330895 交流。(注明Android)

你可能感兴趣的:(Android鬼点子 创造一个条形统计图)