JavaFX实现普通函数曲线变速动画

       现在有一个Node,它的坐标是(x, y),我们想要把这个Node平移到(x1, y1)的位置,但是普通的线性动画不好看,于是就想到了在JavaFX中实现一个曲线变速动画。

        现在有2个问题。1,用什么代表曲线;2,用什么工具实现。我们依次来解决这两个问题。

用函数图像来代表曲线

        数学中的函数是一种对应关系

                                                                 y=f(x)

        对于一个特定的 x ,一定有一个唯一的 y 与之对应。我们可以利用这个特性建立组件坐标与时间 t 的函数 ,让每一个特定的时间点,组件有一个确定的位置。数学中有非常多的函数图像,有二次函数,三次函数,正弦函数,余弦函数,我们可以利用这些函数图像,来描述组件的位置。

用Timeline动画实现动画帧

        Timeline是JavaFX中最基本的动画工具,它可以按照指定的时间间隔执行一系列动作。KeyFrameTimeline的基本单位,包含了特定时间点上要执行的动作。

        因此,我们可以利用Timeline,结合数学函数,来控制一个组件在某一时刻的位置。理论存在,实践开始。

确定函数图像

        为了简化问题,这里给出一个假设。有一个时长总共为 5s 的曲线变速动画,该动画能够将一个矩形(Rectange)从(0,0)的位置移动到(500, 0)的位置。也就是在X方向上向右平移了500个单位。由于只平移了X坐标,因此我们在这里将问题简化为X坐标上的曲线变速平移分析。

        我们决定使用正弦函数的图像,取[0, π]的区间,如果 v = sin x, 那么 x ∈[0, π] 的图像刚好满足一个组件曲线加速后再曲线减速。

        我们知道,如果用正弦函数的图像做vt图像,取x∈[0, π]的话,那么组件移动过的距离等于vt图像所围的面积,而

                                                ​​​​​​​        ​​​​​​​        \int_{0}^{\pi }sinxdx=2

        我们想要让组件移动500单位的距离,而不是2个单位的距离,很显然,用正弦函数图像是不行的,要用正弦型函数图像。

        由于速度总从0开始,t=0时v=0,于是设组件的速度函数

                                                            V_{node}(t)=Asin(wt)

        这里应该有当 t∈[0, 5000] 时,wt∈[0, π],于是解得

                                                                   w=\frac{\pi}{5000}

                                                        V_{node}(t)=Asin(\frac{\pi}{5000}t)

        这样我们就能得到,每一毫秒组件的瞬时速率。而vt图像的面积代表位移,于是得到

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        \int_{0}^{5000}V_{node}(x)dx=500

        整理得

                                                      A\int_{0}^{5000}sin(\frac{\pi}{5000}x)dx=500

        利用一点点现代科技解出A

      

        于是得到

                        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        V_{node}(t)=\frac{\pi}{20}sin(\frac{\pi}{5000}t)

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​    \frac{\pi}{20}\int_{0}^{5000}sin(\frac{\pi}{5000}x)dx=500

        假设时间为t时,这个组件的位置为X_{node},不难得出

                ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​   X_{node}=\frac{\pi}{20}\int_{0}^{t}sin(\frac{\pi}{5000}x)dx

        那么,再次利用一丢丢的现代科技

        就能够得到

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        X_{node}=250-250cos(\frac{\pi}{5000}t)

        很明显我们的 t = 5000(ms) 在 (0, 10000]范围内,该式成立。接下来进入代码实操

        

代码部分

        我们创建一个 takeCurveShiftXTranslateAnimation 方法用于实现我们的功能。

        

    public static void takeCurveShiftXTranslateAnimation(Node node,
                                                              IntToDoubleFunction compute,
                                                              int millis) {
        Timeline timeline = new Timeline();

        for (int i = 0; i < millis; i ++) {
            int finalI = i;
            timeline.getKeyFrames().add(new KeyFrame(Duration.millis(i), event -> node.setLayoutX(compute.applyAsDouble(finalI))));
        }

        timeline.play();
    }

        这里,node是我们需要操作的组件,compute是一个函数式接口,用于接受一个int,也就是动画进行的时间,返回一个Double,也就是返回我们的X_{node},这里让调用者自己实现compute,可以实现更多函数的曲线变速效果。millis是动画时间。

        方法做的事情很简单,为每一毫秒添加一个关键帧用于移动组件。每一帧做的操作就是将组件移动到指定的位置。

        用类似的方法还可以写 takeCurveShiftXTranslateAnimation 用于操作Y坐标,这里不在阐述。

        接下来让我们实现利用正弦函数的compute部分

public static void takeSinCSTranslateAnimation(Node node, double fromX, double toX, int millis) {
        if (millis > 10000) 
            throw new IllegalArgumentException("'millis' CANNOT bigger than 10000!");
        takeCurveShiftXTranslateAnimation(node, mil -> fromX + (250 - 250 * Math.cos((Math.PI/5000) * mil)), millis);
    }

        这里面的

mil -> fromX + (250 - 250 * Math.cos((Math.PI/5000) * mil))

        其实就是实现了

        ​​​​​​​        ​​​​​​​        X_{node}=250-250cos(\frac{\pi}{5000}t)

        这个公式。只不过,我们推导这个公式时假设的组件起始位置是0,而在代码中加入了fromX作为起始位置罢了。

        注意到这段代码其实还有优化空间。由于Timeline动画需要非常多的KeyFrame,这意味着如果要实现一个相对长时间的动画就需要new非常多的对象。这里5000ms的动画就new了5000个KeyFrame。由于动画只进行setLayoutX操作,因此我们完全可以抛弃KeyFrame,采用一个double[]来保存每一毫秒node的LayoutX,然后遍历这个double[],每一毫秒执行一次Platform.runLater(...),里面加上node.setLayoutX的代码,最终实现相同的效果。

        这里没有给出优化完的代码是因为,作者写完这篇博客去吃饭了,回来重新看才发现这个性能问题。因此这里不再给出代码,只是给出优化思路,当然也许还有更优的优化思路,具体代码还望各位读者自行编写。

        另外再说一下。上面的代码只可借鉴不可copy。请不要直接cv到你的项目里。因为上面的代码并不是通用代码,实际上是为上面的假设“一个组件X方向偏移500单位用时5s”写的,具体原因就在于推导X_{code}时,偷懒直接使用了时间5000ms和距离500单位,直接带入算式计算了。这就导致了上面的代码仅仅只适用于上面的假设。如果你想要实现其他的效果,请自行推导X_{code}公式,并按照自己的公式重写compute,否则你直接copy出来的效果将与本博客的效果大相径庭。

测试

        

JavaFX实现的曲线变速动画

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值