手撸一个Android线形图表

手撸一个现形图表:
效果:
手撸一个Android线形图表_第1张图片

我们自定义一个View
实现起来还是很简单的就是使用canvas的绘制path的api。最主要的就是绘制的坐标的计算。滑动方面以前使onTouchEvent里面监听手势重新绘制,后来改成使用Scroller,动画方面使用属性动画。
(1)
按照自定义View的步骤,先重写他的几个构造方法。在构造方法中初始化我们需要用到的一些变量,画笔,path路径,间距等。
(2)
在onSizeChanged(在控件大小发生改变时调用。所以这里初始化会被调用一次)方法中初始化控件的宽和高,绘图区域的高度等尺寸值

 @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        mTotalWidth = getMeasuredWidth();
        mTotalHeight = getMeasuredHeight();
        setNeedHeight();
        leftWhiteRect = new Rect(0, 0, 0, mTotalHeight);
        rightWhiteRect = new Rect(mTotalWidth - leftMargin * 2, 0, mTotalWidth, mTotalHeight);
        topWhiteRect = new Rect(0, 0, mTotalWidth, topMargin / 2);
        bottomWhiteRect = new Rect(0, (int) yStartIndex, mTotalWidth, mTotalHeight);
        super.onSizeChanged(w, h, oldw, oldh);
    }

    /**
     * 设置矩形的顶部 底部 右边Y轴的3部分每部分的高度
     */
    private void setNeedHeight() {
        paintTop = topMargin * 2;
        paintBottom = mTotalHeight - topMargin / 2;
        maxHeight = paintBottom - paintTop;
        yStartIndex = mTotalHeight - topMargin / 2;
        ;
    }

(3)
在onDraw里面进行绘制

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        linePoints.clear();
        canvas.drawColor(BG_COLOR);
        if (mData == null) return;
        //得到每个bar的宽度
        getItemsWidth();
       //重置线
        linePath.reset();
        linePath.incReserve(mData.size());
        checkTheLeftMoving();
        canvas.drawRect(bottomWhiteRect, bgPaint);
        canvas.drawRect(topWhiteRect, bgPaint);
        //画中间的白线
        drawWhiteLine(canvas);
        //画线形图
        drawLines(canvas);
        //画线型图

        //画左边和右边的遮罩层
        leftWhiteRect.right = (int) xStartIndex;
        canvas.drawRect(leftWhiteRect, bgPaint);
        canvas.drawRect(rightWhiteRect, bgPaint);
        //画线上的点
        drawCircles(canvas);
        //画左边的Y轴
        canvas.drawLine(xStartIndex, yStartIndex, xStartIndex, topMargin / 2, axisPaint);
        //左边Y轴的单位
        canvas.drawText(leftAxisUnit, xStartIndex, topMargin / 2 - 14, textPaint);
        //画右边的Y轴
        canvas.drawLine(mTotalWidth - leftMargin * 2, yStartIndex, mTotalWidth - leftMargin * 2, topMargin / 2, axisPaint);
        //画左边的Y轴text
        drawLeftYAxis(canvas);
        //画X轴 下面的和上面
        canvas.drawLine(xStartIndex, yStartIndex, mTotalWidth - leftMargin*2, yStartIndex, axisPaint);
        canvas.drawLine(xStartIndex, topMargin / 2, mTotalWidth - leftMargin*2, topMargin / 2, axisPaint);
        //画X轴的text
        drawXAxisText(canvas);
    }

(4)
当数据很多的时候 我们希望可以通过拖拽来滑动视图,这就需要我们在绘制的时候先加上一个变量leftMoving,在onTouchEvent中改变leftMoving的值然后invalidate();重新绘制达到拖动的效果

 @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                lastPointX = event.getRawX();
                break;
            case MotionEvent.ACTION_MOVE:
                float movex = event.getRawX();
                movingThisTime = lastPointX - movex;
                leftMoving = leftMoving + movingThisTime;
                lastPointX = movex;
                invalidate();
                break;
            case MotionEvent.ACTION_UP:
                new Thread(new SmoothScrollThread(movingThisTime)).start();
                break;
            default:
                return super.onTouchEvent(event);
        }
        return true;
    }

(5)
当手指停下的时候 动画不立即停止,先滑动一会慢慢的停止。这样体验比较好。 做法就是起一个线程动态改变leftMoving的值刷新界面

    /**
     * 左右滑动的时候 当手指抬起的时候  使滑动慢慢停止 不会立刻停止
     */
    private class SmoothScrollThread implements Runnable {
        float lastMoving;
        boolean scrolling = true;

        private SmoothScrollThread(float lastMoving) {
            this.lastMoving = lastMoving;
            scrolling = true;
        }

        @Override
        public void run() {
            while (scrolling) {
                long start = System.currentTimeMillis();
                lastMoving = (int) (0.9f * lastMoving);
                leftMoving += lastMoving;

                checkTheLeftMoving();
                postInvalidate();

                if (Math.abs(lastMoving) < 5) {
                    scrolling = false;
                }

                long end = System.currentTimeMillis();
                if (end - start < 20) {
                    try {
                        Thread.sleep(20 - (end - start));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

(6)滑动优化

上面的第五步,当手指抬起的时候视图继续滑动一段距离。这种fling效果是通过一个子线程动态减少滑动的值然后更新视图实现的。效果虽然实现了,但是不够流畅,其实安卓已经提供了速度相关的api供我们使用。体验也会更好。
速度相关:VelocityTracker
滑动相关:Scroller

 @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                lastPointX = event.getX();
                scroller.abortAnimation();//如果在滑动终止动画
                initOrResetVelocityTracker();//初始化速度跟踪器
                velocityTracker.addMovement(event);//将用户的action添加到跟踪器中。
                break;
            case MotionEvent.ACTION_MOVE:
                float movex = event.getX();
                movingThisTime = lastPointX - movex;
                leftMoving = leftMoving + movingThisTime;
                lastPointX = movex;
                invalidate();
                velocityTracker.addMovement(event);//将用户的action添加到跟踪器中。
                break;
            case MotionEvent.ACTION_UP:
//                new Thread(new SmoothScrollThread(movingThisTime)).start();
                velocityTracker.addMovement(event);
                velocityTracker.computeCurrentVelocity(1000, maxVelocity);//根据已经到达的点计算当前速度。
                int initialVelocity = (int) velocityTracker.getXVelocity();//获得最后的速度
                velocityTracker.clear();
                //通过scroller让它飞起来
                scroller.fling((int) event.getX(), (int) event.getY(), -initialVelocity / 2,
                        0, Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0);
                invalidate();
                lastPointX = event.getX();
                break;
            case MotionEvent.ACTION_CANCEL:
                recycleVelocityTracker();//回收速度跟踪器
                break;
            default:
                return super.onTouchEvent(event);
        }
        if (mGestureListener != null) {
            mGestureListener.onTouchEvent(event);
        }
        return true;
    }

使用Scroller需要重写计算滑动的方法 因为我们的滑动最终还是通过leftMoving 的值来控制的,所以Scroller中计算的也是leftMoving 的值的变化

  @Override
    public void computeScroll() {
        if (scroller.computeScrollOffset()) {
            movingThisTime = (scroller.getCurrX() - lastPointX);
            leftMoving = leftMoving + movingThisTime;
            lastPointX = scroller.getCurrX();
            postInvalidate();
        }
    }

OK这样在滑动的时候就会感觉更流畅了

(7)动画 使用属性动画控制

private float percent = 1f;
    private TimeInterpolator pointInterpolator = new DecelerateInterpolator();
    public void startAnimation(int duration){
        ValueAnimator mAnimator = ValueAnimator.ofFloat(0,1);
        mAnimator.setDuration(duration);
        mAnimator.setInterpolator(pointInterpolator);
        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                percent = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
        mAnimator.start();
    }

上面是一个0-1的属性动画来改变percent 的值,所以percent 就是从0慢慢增长到1的值,我们绘制的时候对于Y坐标的处理就可以用Y坐标的值乘以percent 。这样Y值就是从0到它本来的值慢慢增长完成动画。

代码地址:https://github.com/chsmy/EasyChartWidget

你可能感兴趣的:(android,自定义view)