美文网首页
Android自定义折线图(可拖动显示)

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

作者: 一个多洋 | 来源:发表于2020-10-22 10:46 被阅读0次
    • 废话不多说先上图咯


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

    {
        "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<AdvLineChart> 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<AdvLineChart> 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自定义折线图(可拖动显示)

          本文链接:https://www.haomeiwen.com/subject/ekdumktx.html