Android自定义横向滚动折线图

时间:2021-7-20 作者:qvyue

在2020年的最后一天,来一个滚动折线图收尾吧

Android自定义横向滚动折线图
1609405639(1).jpg

不多说直接看view

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        if (changed) {
            mWidth = getWidth();
            mHeight = getHeight();
            // 测量Y轴数据的文本宽度
            Rect rect = getTextBounds(mYValue.get(mYValue.size() - 1).value, mYTextPaint);
            // 计算X轴的左边距
            mYLeftInterval = rect.width() + mYTextLeftInterval * 2;
            // 设置选中的位置在最后一个
            mCurrentSelectPoint = mXValue.size();
            // 第一个X轴点的位置
            mXFirstPoint = mYLeftInterval + mInterval;
            // 遍历数据最大值 如果为0那么默认为1
            for (int i = 0; i 

获取宽高,测量Y轴数据文本的宽度加上边距,有注释,相信能看明白
下面是取数据最大值为计算做准备,最大和最小第一个点的距离

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(mBackgroundColor);
        drawXYLine(canvas);
        drawYText(canvas);
        drawBrokenLineAndPoint(canvas);
        if (!isScrolling && !aniLock) {
            scrollAtStart();
        }
    }
   /**
     * 绘制X、Y轴
     */
    private void drawXYLine(Canvas canvas) {
        mXYPaint.setColor(mXYColor);
        // 绘制X轴
        canvas.drawLine(mYLeftInterval, mHeight - getPaddingBottom() - mXBottomInterval,
                mWidth - getPaddingRight(), mHeight - getPaddingBottom() - mXBottomInterval, mXYPaint);
        // 绘制Y轴
        canvas.drawLine(mYLeftInterval, 0, mYLeftInterval, mHeight - getPaddingBottom() - mXBottomInterval, mXYPaint);
        //绘制y轴箭头
        mXYPaint.setStyle(Paint.Style.STROKE);
        Path path = new Path();
        path.moveTo(mYLeftInterval - dpToPx(5), mXBottomInterval);
        path.lineTo(mYLeftInterval, 0);
        path.lineTo(mYLeftInterval + dpToPx(5), mXBottomInterval);
        canvas.drawPath(path, mXYPaint);
    }
   /**
     * 绘制Y轴文本
     */
    private void drawYText(Canvas canvas) {
        for (int i = 0; i 

注释写的很清楚, 上面最后注释的那些事我为了测试文本的距离写的,不必在意。

   /**
     * 绘制折线和折线交点处对应的点
     */
    private void drawBrokenLineAndPoint(Canvas canvas) {
        //重新开一个图层
        int layerId = canvas.saveLayer(0, 0, mWidth, mHeight, null, Canvas.ALL_SAVE_FLAG);
        drawLine(canvas);
        drawLinePoint(canvas);
        // 将折线超出x轴坐标的部分截取掉
        mXYPaint.setStyle(Paint.Style.FILL);
        mXYPaint.setColor(mBackgroundColor);
        mXYPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
        RectF rectF = new RectF(0, 0, mYLeftInterval, mHeight);
        canvas.drawRect(rectF, mXYPaint);
        mXYPaint.setXfermode(null);
        //保存图层
        canvas.restoreToCount(layerId);
    }

接下来就是主要的折线和折线点位了

   /**
     * 绘制折线
     **/
    private void drawLine(Canvas canvas) {
        if (mXValue.size() 

以上都用注释, 只要肯看一下就能看懂,看不懂直接拿去拷贝。
提示框那个注释是为了让提示框好看些, 就画个矩形, 周边圆角, 不然路径画出来是直角。

   /**
     * 当宽度不足以呈现全部数据时 滚动
     */
    private void scrollAtStart() {
        // 整体数据的宽度 大于 绘制区域宽度
        if (mInterval * mXValue.size() > mWidth - mYLeftInterval) {
            float scrollLength = maxXFirstPoint - minXFirstPoint;
            ValueAnimator animator = ValueAnimator.ofFloat(0, scrollLength);
            animator.setDuration(500L);//时间最大为1000毫秒,此处使用比例进行换算
            animator.setInterpolator(new DecelerateInterpolator());
            animator.addUpdateListener(animation -> {
                float value = (float) animation.getAnimatedValue();
                mXFirstPoint = (int) Math.max(mXFirstPoint - value, minXFirstPoint);
                invalidate();
            });
            animator.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animator) {
                    isScrolling = true;
                }

                @Override
                public void onAnimationEnd(Animator animator) {
                    isScrolling = false;
                    aniLock = true;
                }

                @Override
                public void onAnimationCancel(Animator animator) {
                    isScrolling = false;
                    aniLock = true;
                }

                @Override
                public void onAnimationRepeat(Animator animator) {

                }
            });
            animator.start();
        }
    }
private float startX;

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (isScrolling) return super.onTouchEvent(event);
        this.getParent().requestDisallowInterceptTouchEvent(true);//当该view获得点击事件,就请求父控件不拦截事件
        obtainVelocityTracker(event);
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                startX = event.getX();
                break;
            case MotionEvent.ACTION_MOVE:
                if (mInterval * mXValue.size() > mWidth - mYLeftInterval) {//当期的宽度不足以呈现全部数据
                    float scrollX = event.getX() - startX;
                    startX = event.getX();
                    if (mXFirstPoint + scrollX = x - dp8 && eventX = y - dp8 && eventY 

以上是点击事件, 点击点位返回点位的数据
基本上没有什么难点,就是滑动的时候计算那地方需要注意一下, 之前就没注意导致刚滑动一点就会到最边上,后来才发现滑动的时候没计算X轴。

地址:https://github.com/xiaobinAndroid421726260/Android_CustomAllCollection.git

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:qvyue@qq.com 进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。