Android自定义折线图(可拖动显示)

  • 废话不多说先上图咯


    图一
  • 至于怎么做呢 咱们可以先获取下折线图数据分析一波

{
    "code": 200,
    "message": "",
    "data": {
        "list": [
            {
                "day": "2020-10-22",
                "readCount": 0,
                "okGroupCount": 0,
                "streamCount": 0,
                "buyCount": 0
            },
            {
                "day": "2020-10-21",
                "readCount": 0,
                "okGroupCount": 0,
                "streamCount": 0,
                "buyCount": 0
            },
            {
                "day": "2020-10-20",
                "readCount": 1,
                "okGroupCount": 0,
                "streamCount": 17,
                "buyCount": 0
            },
            {
                "day": "2020-10-19",
                "readCount": 0,
                "okGroupCount": 0,
                "streamCount": 0,
                "buyCount": 0
            },
            {
                "day": "2020-10-18",
                "readCount": 0,
                "okGroupCount": 0,
                "streamCount": 0,
                "buyCount": 0
            },
            {
                "day": "2020-10-17",
                "readCount": 1,
                "okGroupCount": 0,
                "streamCount": 0,
                "buyCount": 0
            },
            {
                "day": "2020-10-16",
                "readCount": 4,
                "okGroupCount": 0,
                "streamCount": 0,
                "buyCount": 0
            },
            {
                "day": "2020-10-15",
                "readCount": 3,
                "okGroupCount": 0,
                "streamCount": 0,
                "buyCount": 0
            },
            {
                "day": "2020-10-14",
                "readCount": 1,
                "okGroupCount": 0,
                "streamCount": 2,
                "buyCount": 0
            },
            {
                "day": "2020-10-13",
                "readCount": 4,
                "okGroupCount": 0,
                "streamCount": 0,
                "buyCount": 0
            },
            {
                "day": "2020-10-12",
                "readCount": 2,
                "okGroupCount": 0,
                "streamCount": 0,
                "buyCount": 0
            },
            {
                "day": "2020-10-11",
                "readCount": 0,
                "okGroupCount": 0,
                "streamCount": 0,
                "buyCount": 0
            },
            {
                "day": "2020-10-10",
                "readCount": 3,
                "okGroupCount": 0,
                "streamCount": 0,
                "buyCount": 0
            }
        ]
    }
}
  • 获取到数据后 我对数据先进行了稍微处理 循环遍历获取他们里面最大值,当做表格的上限,当然 我最后表格的上限是用的最大值又乘了1.1


    图二
  • javaBean AdvLineChart

//广告折线图bean
public class AdvLineChart {
    private String day;
    private long readCount;     //阅读人数
    private long okGroupCount;  //拼成数量
    private long streamCount;   //引流量
    private long buyCount;      //下单量

    private String dayDetail;   //自己转换的日期 不带年
    private float x;
    private int position;

    public String getDay() {
        return day;
    }

    public void setDay(String day) {
        this.day = day;
    }

    public long getReadCount() {
        return readCount;
    }

    public void setReadCount(long readCount) {
        this.readCount = readCount;
    }

    public long getOkGroupCount() {
        return okGroupCount;
    }

    public void setOkGroupCount(long okGroupCount) {
        this.okGroupCount = okGroupCount;
    }

    public long getStreamCount() {
        return streamCount;
    }

    public void setStreamCount(long streamCount) {
        this.streamCount = streamCount;
    }

    public long getBuyCount() {
        return buyCount;
    }

    public void setBuyCount(long buyCount) {
        this.buyCount = buyCount;
    }

    public String getDayDetail() {
        return dayDetail;
    }

    public void setDayDetail(String dayDetail) {
        this.dayDetail = dayDetail;
    }

    public float getX() {
        return x;
    }

    public void setX(float x) {
        this.x = x;
    }

    public int getPosition() {
        return position;
    }

    public void setPosition(int position) {
        this.position = position;
    }
}
  • 处理过数据后 我们就开始绘制了,我们可以把list里的每一个数据 当做一条竖线 for循环挨个绘制他们
    下面我直接贴出自定义view的代码了 里面注释还是蛮清晰的:
    里面有一些缺少的类 就是一些dp、数量转换 可以自己定义
//广告折线图
public class AdvLineChartView extends View {
    private final static int PADDING_START_ADN_END = 10;            //距离左右边距
    private float mWidth;
    private float mHeight;
    private Rect mTopTextRect;
    private Rect mBottomTextRect;
    private Rect mTipTextRect;
    private Paint mLinePaint;
    private Paint mTopTextPaint;
    private Paint mBottomTextPaint;
    private Paint mTipTextPaint;
    private Paint mTipTextBgPaint;

    private long advMax;            //最大价格值
    private float max;              //最大1.1
    private float downX, downY;     //按下的位置
    private float moveY;            //移动的y点
    private float lineHeight;       //线高
    private List advList = new ArrayList<>();    //各个点信息
    private AdvLineChart tipViewData;
    private CallBack mCallBack;

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

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

    public AdvLineChartView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        init();
    }

    private void init() {
        mTopTextRect = new Rect();
        mBottomTextRect = new Rect();
        mTipTextRect = new Rect();

        mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mLinePaint.setStrokeWidth(2);
        mTopTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mTopTextPaint.setColor(getContext().getResources().getColor(R.color.c_1D1D1D));
        mTopTextPaint.setTextSize(DensityUtil.dip2px(getContext(), 8));
        mBottomTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mBottomTextPaint.setColor(getContext().getResources().getColor(R.color.c_1D1D1D));
        mBottomTextPaint.setTextSize(DensityUtil.dip2px(getContext(), 7));
        mTipTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mTipTextBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mTipTextBgPaint.setStrokeWidth(2);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = getMeasuredWidth();
        mHeight = getMeasuredHeight();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //点击记录位置
                downX = event.getX();
                downY = event.getY();
                moveY = event.getY();
                return true;
            case MotionEvent.ACTION_MOVE:
                moveY = event.getY();
                AdvLineChart clickOnPoint1 = getClickOnPoint(event.getX());
                if (null != clickOnPoint1) {
                    //绘制提示view
                    tipViewData = clickOnPoint1;
                    invalidate();
                }
                //父布局不拦截子布局事件
                getParent().requestDisallowInterceptTouchEvent(true);
                return true;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                //松开时判断 松开位置在点击位置的有效范围内
                if (Math.abs(downX - event.getX()) <= 2 && Math.abs(downY - event.getY()) <= 2) {
                    AdvLineChart clickOnPoint2 = getClickOnPoint(event.getX());
                    if (null != clickOnPoint2) {
                        //绘制提示view
                        tipViewData = clickOnPoint2;
                        invalidate();
                        return true;
                    } else {
                        return super.onTouchEvent(event);
                    }
                } else {
                    return super.onTouchEvent(event);
                }
        }
        return super.onTouchEvent(event);
    }

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

        float paddingStartAndEnd = DensityUtil.dip2px(getContext(), PADDING_START_ADN_END);
        for (int i = 0; i < advList.size(); i++) {
            AdvLineChart data = advList.get(i);
            String topText = data.getDay();
            String bottomText = data.getDayDetail();
            mTopTextPaint.setColor(getContext().getResources().getColor(R.color.c_1D1D1D));
            mTopTextPaint.getTextBounds(topText, 0, topText.length(), mTopTextRect);
            mBottomTextPaint.getTextBounds(bottomText, 0, bottomText.length(), mBottomTextRect);
            //计算各条线x点
            float x = calculationPositionX(i);
            //竖线高度
            lineHeight = mHeight - mTopTextRect.height() - mBottomTextRect.height() - DensityUtil.dip2px(getContext(), 16);
            /** 绘制竖线 **/
            drawLine(canvas, x, 0, x, lineHeight);
            /** 绘制文字 **/
            canvas.drawText(topText, x - mTopTextRect.width() / 2f,
                    mHeight - DensityUtil.dip2px(getContext(), 9) - mBottomTextRect.height(), mTopTextPaint);
            canvas.drawText(bottomText, x - mBottomTextRect.width() / 2f,
                    mHeight - DensityUtil.dip2px(getContext(), 7), mBottomTextPaint);
            /** 绘制横线 只绘制一次 **/
            if (i == 0) {
                drawLine(canvas, paddingStartAndEnd, 1, mWidth - paddingStartAndEnd, 1);
                drawLine(canvas, paddingStartAndEnd, lineHeight / 2, mWidth - paddingStartAndEnd, lineHeight / 2);
                drawLine(canvas, paddingStartAndEnd, lineHeight, mWidth - paddingStartAndEnd, lineHeight);
            }
            /** 绘制分数线 **/
            if (i < advList.size() - 1 && max >= 0) {
                AdvLineChart nextData = advList.get(i + 1);
                //下一个x点
                float nextX = calculationPositionX(i + 1);
                /** 阅读人数 **/
                drawLine(canvas, x, calculationPositionY(lineHeight, data.getReadCount()),
                        nextX, calculationPositionY(lineHeight, nextData.getReadCount()),
                        getResources().getColor(R.color.c_BF383E));
                /** 拼成数量 **/
                drawLine(canvas, x, calculationPositionY(lineHeight, data.getOkGroupCount()),
                        nextX, calculationPositionY(lineHeight, nextData.getOkGroupCount()),
                        getResources().getColor(R.color.c_FFC90D));
                /** 引流数量 **/
                drawLine(canvas, x, calculationPositionY(lineHeight, data.getStreamCount()),
                        nextX, calculationPositionY(lineHeight, nextData.getStreamCount()),
                        getResources().getColor(R.color.lightpink));
                /** 下单数量 **/
                drawLine(canvas, x, calculationPositionY(lineHeight, data.getBuyCount()),
                        nextX, calculationPositionY(lineHeight, nextData.getBuyCount()),
                        getResources().getColor(R.color.darkviolet));
                //设置当前位置数据的坐标
                data.setX(x);
                data.setPosition(i);
                //最后一个了 把nextX加进去
                if (i == advList.size() - 2) {
                    nextData.setX(nextX);
                    nextData.setPosition(i + 1);
                    //默认显示成第一个
                    if (null == tipViewData) {
                        tipViewData = advList.get(advList.size() - 1);
                        moveY = 1;//calculationPositionY(lineHeight, advList.get(advList.size() - 1).getReadCount());
                    }
                }
            }
        }

        //绘制提示view
        if (null != tipViewData) {
            moveY = moveY < 1 ? 1 : moveY > lineHeight ? lineHeight : moveY;
            String title = tipViewData.getDay() + "-" + tipViewData.getDayDetail().replace("/", "-");
            String readStr = "阅读";
            String read = StringUtil.getLastNum(tipViewData.getReadCount()) + "人";
            String okGroupStr = "拼成";
            String okGroup = StringUtil.getLastNum(tipViewData.getOkGroupCount()) + "个";
            String streamStr = "引流";
            String stream = StringUtil.getLastNum(tipViewData.getStreamCount()) + "次";
            String buyStr = "下单";
            String buy = StringUtil.getLastNum(tipViewData.getBuyCount()) + "笔";
            mTipTextPaint.setTextSize(DensityUtil.dip2px(getContext(), 12));
            mTipTextPaint.getTextBounds(title, 0, title.length(), mTipTextRect);
            //标题高度
            int titleHeight = mTipTextRect.height();
            mTipTextPaint.setTextSize(DensityUtil.dip2px(getContext(), 10));
            mTipTextPaint.getTextBounds(read, 0, read.length(), mTipTextRect);
            //其他内容的高度
            int otherHeight = mTipTextRect.height();
            //阅读宽度
            int readWidth = mTipTextRect.width();
            mTipTextPaint.getTextBounds(okGroup, 0, okGroup.length(), mTipTextRect);
            //拼成宽度
            int okGroupWidth = mTipTextRect.width();
            mTipTextPaint.getTextBounds(stream, 0, stream.length(), mTipTextRect);
            //引流宽度
            int streamWidth = mTipTextRect.width();
            mTipTextPaint.getTextBounds(buy, 0, buy.length(), mTipTextRect);
            //下单宽度
            int buyWidth = mTipTextRect.width();
            //x点位置
            float tipViewX;
            if (tipViewData.getX() + DensityUtil.dip2px(getContext(), 80) > mWidth - paddingStartAndEnd) {
                tipViewX = mWidth - paddingStartAndEnd - DensityUtil.dip2px(getContext(), 80);
            } else {
                tipViewX = tipViewData.getX();
            }
            //框高度 padding上下7 标题padding下3 其他3个文字padding上下3 最后其他文字的上3 加起来就是38
            int rectHeight = DensityUtil.dip2px(getContext(), 38) + titleHeight + otherHeight * 4;
            //y点位置
            if (moveY + rectHeight > lineHeight) {
                moveY = lineHeight - rectHeight;
            }
            /** 绘制白色覆盖背景 **/
            RectF rectF = new RectF(tipViewX, moveY,
                    tipViewX + DensityUtil.dip2px(getContext(), 80), moveY + rectHeight);
            mTipTextBgPaint.setStyle(Paint.Style.FILL);
            mTipTextBgPaint.setColor(Color.WHITE);
            canvas.drawRoundRect(rectF, 0, 0, mTipTextBgPaint);
            /** 绘制背景边框 **/
            mTipTextBgPaint.setStyle(Paint.Style.STROKE);
            mTipTextBgPaint.setColor(getResources().getColor(R.color.c_1D1D1D));
            canvas.drawRoundRect(rectF, 0, 0, mTipTextBgPaint);
            //strText前面距离
            int strText = DensityUtil.dip2px(getContext(), 4);
            /** 标题 **/
            //标题y位置
            float titleY = moveY + DensityUtil.dip2px(getContext(), 7) + titleHeight;
            mTipTextPaint.setColor(getContext().getResources().getColor(R.color.c_BF383E));
            canvas.drawText(title, tipViewX + strText, titleY, mTipTextPaint);
            /** 阅读 **/
            //阅读y位置
            float readY = titleY + DensityUtil.dip2px(getContext(), 6) + otherHeight;
            mTipTextPaint.setColor(getContext().getResources().getColor(R.color.c_BF383E));
            canvas.drawText(readStr, tipViewX + strText, readY, mTipTextPaint);
            canvas.drawText(read, rectF.right - readWidth - strText, readY, mTipTextPaint);
            /** 拼成 **/
            //拼成y位置
            float okGroupY = readY + DensityUtil.dip2px(getContext(), 6) + otherHeight;
            mTipTextPaint.setColor(getContext().getResources().getColor(R.color.c_FFC90D));
            canvas.drawText(okGroupStr, tipViewX + strText, okGroupY, mTipTextPaint);
            canvas.drawText(okGroup, rectF.right - okGroupWidth - strText, okGroupY, mTipTextPaint);
            /** 引流 **/
            //引流y位置
            float streamY = okGroupY + DensityUtil.dip2px(getContext(), 6) + otherHeight;
            mTipTextPaint.setColor(getContext().getResources().getColor(R.color.lightpink));
            canvas.drawText(streamStr, tipViewX + strText, streamY, mTipTextPaint);
            canvas.drawText(stream, rectF.right - streamWidth - strText, streamY, mTipTextPaint);
            /** 下单 **/
            //下单y位置
            float buyY = streamY + DensityUtil.dip2px(getContext(), 6) + otherHeight;
            mTipTextPaint.setColor(getContext().getResources().getColor(R.color.darkviolet));
            canvas.drawText(buyStr, tipViewX + strText, buyY, mTipTextPaint);
            canvas.drawText(buy, rectF.right - buyWidth - strText, buyY, mTipTextPaint);
            /** 绘制选中的竖线 **/
            drawLine(canvas, tipViewData.getX() < paddingStartAndEnd ? paddingStartAndEnd :
                            tipViewData.getX() > mWidth - paddingStartAndEnd ? mWidth - paddingStartAndEnd : tipViewData.getX(),
                    0, tipViewData.getX() < paddingStartAndEnd ? paddingStartAndEnd :
                            tipViewData.getX() > mWidth - paddingStartAndEnd ? mWidth - paddingStartAndEnd : tipViewData.getX(),
                    tipViewData.getX() > rectF.left && tipViewData.getX() < rectF.right ? rectF.top - 2 : lineHeight,
                    getResources().getColor(R.color.c_ED1C24), 3);
            /** 绘制下半截的竖线 **/
            if (tipViewData.getX() > rectF.left && tipViewData.getX() < rectF.right) {
                drawLine(canvas, tipViewData.getX() < paddingStartAndEnd ? paddingStartAndEnd :
                                tipViewData.getX() > mWidth - paddingStartAndEnd ? mWidth - paddingStartAndEnd : tipViewData.getX(),
                        rectF.bottom + 2, tipViewData.getX() < paddingStartAndEnd ? paddingStartAndEnd :
                                tipViewData.getX() > mWidth - paddingStartAndEnd ? mWidth - paddingStartAndEnd : tipViewData.getX(), lineHeight,
                        getResources().getColor(R.color.c_ED1C24), 3);
            }
            if (null != mCallBack) {
                mCallBack.pointClick(tipViewData, moveY);
            }
        }
    }

    /**
     * 设置数据
     */
    public void setData(List shopList, long shopMax) {
        this.advMax = shopMax;
        if (null != shopList && shopList.size() > 0) {
            this.advList.clear();
            this.advList.addAll(shopList);
            max = (float) (this.advMax * 1.1);
            invalidate();
        }
    }

    /**
     * 移动tipsView pre是否往前 num数量
     */
    public void moveTipsView(boolean pre, int num) {
        if (null != tipViewData) {
            //前移 因为数据是从最新的开始排的 所以往后选
            if (pre) {
                if (tipViewData.getPosition() + num < advList.size()) {
                    tipViewData = advList.get(tipViewData.getPosition() + num);
                } else {
                    tipViewData = advList.get(advList.size() - 1);
                }
                invalidate();
            } else {    //后移
                if (tipViewData.getPosition() - num >= 0) {
                    tipViewData = advList.get(tipViewData.getPosition() - num);
                } else {
                    tipViewData = advList.get(0);
                }
                invalidate();
            }
        }
    }

    private void drawLine(Canvas canvas, float startX, float startY, float endX, float endY) {
        drawLine(canvas, startX, startY, endX, endY, getResources().getColor(R.color.c_1D1D1D), 2);
    }

    private void drawLine(Canvas canvas, float startX, float startY, float endX, float endY, int color) {
        drawLine(canvas, startX, startY, endX, endY, color, 2);
    }

    private void drawLine(Canvas canvas, float startX, float startY, float endX, float endY, int color, int strokeWidth) {
        mLinePaint.setColor(color);
        mLinePaint.setStrokeWidth(strokeWidth);
        canvas.drawLine(startX, startY, endX, endY, mLinePaint);
    }

    //计算x位置
    private float calculationPositionX(int i) {
        //当前数组是从大到小的 计算x点时从最后面开始算
        return (mWidth - DensityUtil.dip2px(getContext(), PADDING_START_ADN_END * 2)) /
                (advList.size() - 1) * (advList.size() - 1 - i) +
                DensityUtil.dip2px(getContext(), PADDING_START_ADN_END);
    }

    //计算y位置
    private float calculationPositionY(float lineHeight, long currentNum) {
        float currentPositionY = lineHeight * advMax / max;
        float scale = (1 - currentNum / (float) advMax);
        return scale == 1 ? lineHeight : lineHeight - currentPositionY + currentPositionY * scale;
    }

    //计算当前点击是否在点上 是的话返回点对象 否则null
    private AdvLineChart getClickOnPoint(float x) {
        AdvLineChart advLineChartReturn = null;
        for (AdvLineChart advLineChart : advList) {
            if (Math.abs(x - advLineChart.getX()) < mWidth / advList.size() / 2) {
                advLineChartReturn = advLineChart;
                break;
            }
        }
        return advLineChartReturn;
    }

    public void setOnMoveListener(CallBack callBack) {
        this.mCallBack = callBack;
    }

    public interface CallBack {
        void pointClick(AdvLineChart advLineChart, float rawY);
    }
}

你可能感兴趣的:(Android自定义折线图(可拖动显示))