LeafChart(4)-绘制动画曲线图

1. 介绍

了解更多请看:
LeafChart-实现自己的小型图表库(1)
LeafChart-实现自己的小型图表库(2)
LeafChart(3)-绘制直方图

LeafChart已经支持曲线图和直方图了,现在想升级一下,比如说来个动画绘制啊。之前使用过HelloChart的曲线图,它的动画效果是这样的

hellochart

本来想借鉴一下动画效果的实现,可是我想要的动画效果不是这样子。

先睹为快
animate_line1
animate_line2

我想要的动画效果就如上图:
1. 从左向右逐渐绘制线条
2. 如果设置填充,则填充效果也要和线条绘制同步

2.实现原理

2.1效果1实现原理

一开始这个动画的实现方法真是想不到,还好在网上有大侠介绍思路。
参考博客:
使用DashPathEffect绘制一条动画曲线

这个里面介绍的方法是使用PathEffect子类DashPathEffect来绘制一条动画曲线。

DashPathEffect

DashPathEffect是PathEffect类的一个子类,可以使paint画出类似虚线的样子,并且可以任意指定虚实的排列方式。
举个例子:

Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
p.setStyle(Style.STROKE);
p.setColor(Color.WHITE);
p.setStrokeWidth(1);
PathEffect effects = new DashPathEffect(new float[] { 1, 2, 4, 8}, 1);
p.setPathEffect(effects);
canvas.drawLine(0, 40, mWidth, 40, p);

代码中的float数组,必须是偶数长度,且>=2,指定了多少长度的实线之后再画多少长度的空白。
如本代码中,绘制长度1的实线,再绘制长度2的空白,再绘制长度4的实线,再绘制长度8的空白,依次重复。1是起始位置的偏移量。
效果如下:

技巧然而这跟我们绘制跟踪效果有什么关系呢?
看看这样一个PathEffect:

    PathEffect effect = new DashPathEffect(new float[] { length, length }, 0);

我们可以把DashPathEffect的第一个参数(float数组)只填入两个值,都是path的总长度length,那么按照上面对DashPathEffect的解释,第一次绘制一条实线就已经完全绘制完了,间隔的空白区间得不到绘制的机会。事实上这样绘制完全不能产生虚线效果,跟不设置PathEffect是一样的。
但是我们注意第三个参数即起始位置的偏移量现在是为0的。如果我们不为0呢?
比如为100,那么第一次绘制实线就会跳过100的距离,第一次的实线就只能绘制length-100的长度,那么空白区域就可以绘制100的长度,但是你看不见空白,所以我们只会感觉到绘制了一条length-100的路径。
如果你按照我们的思路去做实验,那么很快你就会想到,把这个偏移量也设置成length,那么第一次的实线区间将完全得不到绘制,而直接进入空白区间,而我们的空白区间总长度也是length,因此它占用了全部的绘制区间,所以此时什么也看不到。如果空白区间小于length的话,是可以看到一点实线的(因为空白区间完了紧接着就是实线了)。
所以,我们可以设置一个百分比,取名叫phase,phase的增长是从0 .0-1.0,如果我们利用属性动画来改变它,然后根据它动态的构造一个这样的DashPathEffect:

    new DashPathEffect(new float[] { length, length },
           length - phase * length);

或者:

    return new DashPathEffect(new float[] { phase * pathLength, pathLength },
           0);

这样就能产生跟踪绘制的效果。
获取path的长度刚刚我们多次提到了path的总长度length,那么对于一条不规则的曲线来讲,要得到其长度是很难的。幸运的是,有相应的api:

    // Measure the path
    PathMeasure measure = new PathMeasure(path, false);
    float length = measure.getLength();

这样就可以把一个path从头到尾的慢慢绘制出来。

注意:上面那种方式实现动画只是针对paint的style为STROKE效果比较好,当paint的style为FILL或者是STROKE_AND_FILL时,一般不会有人需要这种效果。

2.2 效果2实现原理

找了好多方法终于把第一个动画效果实现了,那么问题了,第二个效果怎么实现?

首先需要知道的是,现在的填充效果是通过设置paint的style为FILL绘制封闭path实现的。所以,我首先想到的是可不可以根据曲线当前绘制宽度来获取封闭path图案,然而当前曲线最右边的一个点的坐标无法获得。然后想到是不是可以动态填充一个封闭path图案,上网找了一下没找到,……

终于,终于,终于,我想到了一个实现方法,还记得前面的提到的歌词变色吗?
Android 仿应用宝下载进度条

实现原理是, canvas.clipRect()方法从左向右动态截取矩形,矩形最右边就是当前曲线绘制的左右端,让后再绘制这块区域内的path,这样就实现了效果2,原来 就是这么简单。当然,如果你有其他的思路,请告诉我。

2.3 原理总结

上面说到的两种动画方法实现原理可以应用与不同使用场景。第一个原理主要可以把一条path从无到有的勾勒出来。第二个原理适用场景是按某一个方向对图形进行填充。

打个比方说,第一种像藤蔓慢慢向前曲伸,第二种像油漆刷,所到之处皆变色。(看看不说话)

3.实现

3.1定义相关成员变量

    /**
     * 路径总长度
     */
    private float pathLength;

    /**
     * 动画结束标志
     */
    private boolean isAnimateEnd = false;
    /**
     * 变化因子
     */
    private float phase;

3.2代码实现曲线动态绘制

  • 在drawLines方法中添加:
PathMeasure measure = new PathMeasure(path, false);
            pathLength = measure.getLength();//测量path的总长度
  • 同样在drawCubicPath添加:
PathMeasure measure = new PathMeasure(path, false);
            pathLength = measure.getLength();//测量path的总长度
  • 为了使用属性动画改变变化因子,定义以下方法:
    //showWithAnimation动画开启后会调用该方法
    public void setPhase(float phase) {
        linePaint.setPathEffect(createPathEffect(pathLength, phase, 0.0f));
        invalidate();
    }
    //创建DashPathEffect
   private PathEffect createPathEffect(float pathLength, float phase, float offset) {
        return new DashPathEffect(new float[] { phase * pathLength, pathLength }, 0);
    }
  • 带动画的绘制,调用该方法,实现动画绘制。
    /**
     * 带动画的绘制
     * @param duration
     */
    public void showWithAnimation(int duration){
        isAnimateEnd = false;
        ObjectAnimator animator = ObjectAnimator.ofFloat(this, "phase", 0.0f, 1.0f);
        animator.setDuration(duration);
        animator.start();

        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                phase = (float) animation.getAnimatedValue();
            }
        });

        animator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {
                isAnimateEnd = true;
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
    }
  • 不带动画的绘制,内部调用动画绘制,duration设置为0。
    public void show(){
        showWithAnimation(0);
    }

3.3代码实现path填充

    private void drawFillArea(Canvas canvas) {
        //继续使用前面的 path
        if(line != null && line.getValues().size() > 1){
            List<PointValue> values = line.getValues();
            PointValue firstPoint = values.get(0);
            float firstX = firstPoint.getOriginX();

            PointValue lastPoint = values.get(values.size() - 1);
            float lastX = lastPoint.getOriginX();
            path.lineTo(lastX, axisX.getStartY());
            path.lineTo(firstX, axisX.getStartY());
            path.close();

            linePaint.setStyle(Paint.Style.FILL);
            if(line.getFillColr() == 0)
                linePaint.setAlpha(100);
            else
                linePaint.setColor(line.getFillColr());

    //根据phase计算当前绘制最右端位置
            canvas.save(Canvas.CLIP_SAVE_FLAG);
            canvas.clipRect(firstX, 0, phase * (lastX - firstX) + firstX, getMeasuredHeight());
            canvas.drawPath(path, linePaint);
            canvas.restore();
            path.reset();
        }
    }

注意:
phase是在动画的监听接口里面动态设置的,原因是这样可以使曲线和填充效果更好的同步

    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                phase = (float) animation.getAnimatedValue();
            }
        });

4.使用

使用上改动的地方除了多添加了几个自定义属性外,唯一变化的是现在通过showWithAnimation(duration)或show()方法开启绘制。(具体使用请看demo)

5.下载

LeafChart

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值