自定义view走势图(二、加入动画和触摸事件)

        很早就想来完善之前写的走势图(一)了,结果两个月感觉天天都在忙,无脑写代码,还偶尔通宵,尼玛啊,一脸要死的样子,现在总算有点时间了~~~~      

这篇是在第一篇的基础上进行的完善和改进,主要就是加入了动画和触摸效果,效果图如下:


       在之前的基础上,首先改善的第一点就是重新计算了x轴和y轴文字的坐标,让这些文字显示出来的位置更准确,主要就是计算了这些文字的宽高,然后根据这些文字的宽高来计算它们相对x轴和y轴的偏移量,改进后的代码如下:

        Paint.FontMetrics fm = textPaint.getFontMetrics();
        if(isShowXText){     //绘制x轴文字
            float h1 = fm.descent - fm.ascent; //x轴文字高度,相对x轴向下偏移的值
            for(int x1 = 0;(x1 * 4) < points.size();x1++){
                String xStr = points.get(x1 * 4).getDate();
                float xw = textPaint.measureText(xStr);  //x轴文字的宽度,为了计算文字宽度的中心点
                canvas.drawText(points.get(x1 * 4).getDate(), left + xDivider * x1 - xw/2,bottom + h1, textPaint);
            }
        }

        if(isShowYText){    //绘制y轴数值
            float h2 = Math.abs(fm.descent + fm.ascent);  //y轴文字相对于文字baseline的高度,为了计算高度的中心点
            float avIncome = maxPoint.getIncome() / 5;
            for(int y1 = 1;y1 <=5 ;y1++){
                String income = new DecimalFormat("0.00").format(avIncome * y1);
                float textWidth = textPaint.measureText(income);  //y轴文字宽度,相对y轴向左偏移的值
                canvas.drawText(income,left - textWidth - 5,bottom - yDivider * y1 + h2/2, textPaint);
            }
        }

在上面代码中计算文字宽高,为了保证横竖刻度线和文字的中心点是对齐的(强迫症!!!???)

接下来要做的就是加入x和y方向上的动画效果,这里就用属性动画来实现这种效果:

    private float phaseY = 1f;  
    private int xCount = 0;
首先定义的这两个属性,就是要通过属性动画来改变的两个值,xCount是用来控制点的个数,phaseY用来控制所有点的y值的变化,主要在描点的时候来控制:

    private void drawDataLines(Canvas canvas, List<Point> points){
        for(int i=0;i<xCount;i++){
            Point point1 = points.get(i == 0? 0: i-1);
            Point point2 = points.get(i);
            Location location1 = getLocation(point1,i == 0?0: i-1 );
            Location location2 = getLocation(point2, i);
            canvas.drawCircle(location2.x, location2.y, 5f, textPaint);

            if(i > 0){
                canvas.drawLine(location1.x, location1.y, location2.x,location2.y, linePaint);
            }
        }
    }
    private Location getLocation(Point point, int n){
        if(specY == 0 || specX == 0){
            specY = (bottom - top) / maxPoint.getIncome();
            specX = (right - left) / (points.size() - 1);
        }
        float x = n * specX + left;
        float y = bottom  - point.getIncome() * specY * phaseY;
        return new Location(x, y);
    }
上面的drawDataLines()方法就是描点连线的方法,用xCount来控制for循环的最大值,也就控制了需要描点的数量;getLocation()方法对点的一个转换的方法,里面对获取点的y值的时候加入了phaseY,来控制y值的变化,然后就是实现两个属性动画:

    public void animX(long duration){
        ObjectAnimator animator = ObjectAnimator.ofInt(this, "xCount", 0, points.size());
        animator.setDuration(duration);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                invalidate();
            }
        });
        animator.start();
    }

    public void animY(long duration){
        ObjectAnimator animator = ObjectAnimator.ofFloat(this, "phaseY", 0f, 1f);
        animator.setDuration(duration);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                invalidate();
            }
        });
        animator.start();
    }
可以看到,animX()方法我用的ofInt来控制的,因为点的个数只能是整数,这样就行了;而animY()方法中,用了ofFloat来控制,让phaseY用0变到1,这样来实现所有点的y值从0变到原值,这里要注意的是对于这两个属性一定要定义set()方法,因为ObjectAnimator是通过反射来调用属性的set方法来赋值的。这个时候你就可以在外面调用这两个方法了,去体验一下流畅顺滑的动画效果(还有一点要注意的是,执行动画的时候会频繁调用invalidate(),所以在执行OnDraw()之前,如果有用到path来存放线条,一定要记得reset,不然执行动画的是时候,点越多,就会越卡)~~~~~

        最后要实现的就是手触摸屏幕时,显示出手触摸在哪个点上,并通过指示线显示出具体的值,先来定义一些要用到的关键属性:

    private Paint indicatorPaint;  //指示线画笔
    private Paint indicateValuePaint; //绘制x和y值画笔
    private Paint indicateRectPaint;  //绘制x和y值显示区域的画笔

    private boolean isTouch = false;
    private float[] indicates = new float[8];  //横竖指示线的坐标
    private RectF rectY;    //显示y值的矩形区域
    private RectF rectX;    //显示x值的矩形区域
    private Point touchedPoint;   //触摸到的点
    private Location xValueLocation;  //绘制x值的坐标
    private Location yValueLocation;  //绘制y值的坐标
 对于这些属性的初始化就不放上来了,这里的关键就是手指触摸时坐标的计算,下面是主要计算代码:

    private void setIndicators(float x, float y){
        if(x < left){
            x = left;
        }
        if(x > right){
            x = right;
        }
        int position = new BigDecimal((x-left)/specX).setScale(0,BigDecimal.ROUND_HALF_UP).intValue();
        Point point = points.get(position);
        touchedPoint = point;
        Location location = getLocation(point, position);

        //计算手指触摸时,两条指示线的坐标并赋值
        indicates[0] = left;
        indicates[1] = location.y;
        indicates[2] = right;
        indicates[3] = location.y;
        indicates[4] = location.x;
        indicates[5] = top;
        indicates[6] = location.x;
        indicates[7] = bottom;

        Paint.FontMetrics fm = indicateValuePaint.getFontMetrics();
        //计算两条指示线在坐标轴上显示值的矩形区域
        String date = point.getDate();
        String income =  new DecimalFormat("0.00").format(point.getIncome());
        float dateWidth = indicateValuePaint.measureText(date);
        float incomeWidth = indicateValuePaint.measureText(income);
        float height = fm.bottom - fm.top;
        //绘制x值的矩形区域
        rectX.left = location.x - dateWidth/2 - 5;
        rectX.top = bottom;
        rectX.right = location.x + dateWidth/2 + 5;
        rectX.bottom = bottom + height;
        //绘制y值的矩形区域
        rectY.left = left - incomeWidth - 10;
        rectY.top = location.y - height/2;
        rectY.right = left;
        rectY.bottom = location.y + height/2;

        //计算绘制x,y值的坐标
        xValueLocation.x = rectX.left + 5;
        xValueLocation.y = rectX.bottom - fm.descent;
        yValueLocation.x = rectY.left + 5;
        yValueLocation.y = rectY.bottom - fm.descent;
    }
关键的计算在代码都给出了注释,方法中传进来的x,y就是手指触摸时的屏幕上的坐标,这里对手指触摸是做了一个判断,离哪个点更近,哪个点就是被触摸的那个点。接着就是重写该view 的onTouchEvent()方法:

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getX();
        float y = event.getY();
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                isTouch = true;
                setIndicators(x, y);
                break;
            case MotionEvent.ACTION_MOVE:
                setIndicators(x, y);
                break;
            case MotionEvent.ACTION_UP:
                isTouch = false;
                break;
        }
        invalidate();
        return true;
    }
在Down和Move的事件中,调用了上面的setIndicators()的方法,需要的东西都准备完了,接着要来根据这些值画出指示线和值了了:

    //绘制手触摸的时候的指示线和对应的值
    private void drawIndicateLines(Canvas canvas){
        canvas.drawLines(indicates, indicatorPaint);
        canvas.drawRect(rectX, indicateRectPaint);
        canvas.drawRect(rectY, indicateRectPaint);
        canvas.drawText(touchedPoint.getDate(), xValueLocation.x, xValueLocation.y, indicateValuePaint);
        String income =  new DecimalFormat("0.00").format(touchedPoint.getIncome());
        canvas.drawText(income, yValueLocation.x, yValueLocation.y, indicateValuePaint);
    }
上面这个方法中,draw方法绘制的内容由上到下依次为:

        ①绘制两条指示线;②绘制x值显示的矩形背景;③绘制y值显示的矩形背景;④绘制x值;⑤绘制y值。

最后再onDraw中调用:

       if(isTouch){
            drawIndicateLines(canvas);
        }

到这里,最开始图上的效果就基本实现了





  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值