如何使用线程实现缓动函数

我正在尝试找到一种有效,正常或简单的方法来将缓动函数实现到我的java程序中。 我得到了缓动function,但我觉得有一种更有效的方法可以做到; 一个我看不到的,可能是因为隧道视野。 这是我的代码; 有人可以告诉我我应该采取的不同做法,或指出我需要去研究的方向

public class slide extends JPanel implements Runnable { Thread ease = new Thread(this); float total = 0; float dur; slide() { ease.start(); setLayout(null); } public float calc(float t, float b, float c, float d) { return c * t / d + b; } public void run() { while (true) { try { if (total < 50) { total += 1; } else { ease.stop(); } setBounds(400, Math.round(200 * total / 50 + 0), 250, 150); repaint(); System.out.println(total + " " + dur); ease.sleep(10); } catch (Exception e) { } } } } 

我试图实现我在网上发现的线性缓动函数的calc()方法,但它实际上没用,因为我被迫无法使它工作,除非将方程直接插入到

好的,所以动画是一个相当复杂和深入的主题,我不打算在这里介绍,它还涉及许多我不太了解的数学,所以我们不会进入大量的深度或细节,有更好的人,然后我可以解释它,你可以在网上阅读它

首先,我们做出一些假设……

动画是随时间变化的,时间是可变的。 缓解是(在这种情况下)速度随时间的变化。 这意味着动画的速度对于任何给定的时间点都是可变的。

基本上,我们想要做的是“规范化”一切。 也就是说,在动画开始时,时间为0,结束时为1,其间的所有其他内容都是这两个值之间的一小部分。

如果你能这样想,事情变得容易多了。 因此,基于时间轴上的给定点,您可以决定应该做什么。 例如,在50%的情况下,您应该在起点和终点之间

好的,但这对我们有什么帮助? 如果我们要绘制一个易于放入和缓出动画的图形,它看起来就像……

钟形曲线

其中x轴是时间,y轴是速度(两轴都在0和1之间)。 因此,在x(在时间上)的任何给定点,我们应该能够计算速度。

现在,我们可以使用Bézierspine / curve的一些数学来计算,并计算时间轴上给定点的对象速度。

现在,我直接从Timing Framework中借用了大部分代码,但是如果你真的很感兴趣,你也可以看看BézierCurves的游戏:教程

(nb:我确实写了这样的东西,然后2天后,发现Timing Framework已经实现了……这是一个有趣的练习……)

现在,关于这个实现的重要注意事项是,它实际上不会返回对象的速度,但它会沿着时间轴(0-1)返回一个时间进度,好吧,这听起来很奇怪,但它是什么允许你做的是沿时间线计算起点和终点之间的当前位置(startValue + ((endValue - startValue) * progress))

我不会详细介绍这个,因为我真的不懂数学,我只知道如何应用它,但基本上,我们计算沿曲线的点(x / y),我们然后将这些值(0-1)标准化,以便更容易查找。

interpolate方法使用二分搜索来找到给定时间段内最接近的匹配点,然后计算该点的速度/ y位置

 public class SplineInterpolator { private final double points[]; private final List normalisedCurve; public SplineInterpolator(double x1, double y1, double x2, double y2) { points = new double[]{ x1, y1, x2, y2 }; final List baseLengths = new ArrayList<>(); double prevX = 0; double prevY = 0; double cumulativeLength = 0; for (double t = 0; t <= 1; t += 0.01) { Point2D xy = getXY(t); double length = cumulativeLength + Math.sqrt((xy.getX() - prevX) * (xy.getX() - prevX) + (xy.getY() - prevY) * (xy.getY() - prevY)); baseLengths.add(length); cumulativeLength = length; prevX = xy.getX(); prevY = xy.getY(); } normalisedCurve = new ArrayList<>(baseLengths.size()); int index = 0; for (double t = 0; t <= 1; t += 0.01) { double length = baseLengths.get(index++); double normalLength = length / cumulativeLength; normalisedCurve.add(new PointUnit(t, normalLength)); } } public double interpolate(double fraction) { int low = 1; int high = normalisedCurve.size() - 1; int mid = 0; while (low <= high) { mid = (low + high) / 2; if (fraction > normalisedCurve.get(mid).getPoint()) { low = mid + 1; } else if (mid > 0 && fraction < normalisedCurve.get(mid - 1).getPoint()) { high = mid - 1; } else { break; } } /* * The answer lies between the "mid" item and its predecessor. */ final PointUnit prevItem = normalisedCurve.get(mid - 1); final double prevFraction = prevItem.getPoint(); final double prevT = prevItem.getDistance(); final PointUnit item = normalisedCurve.get(mid); final double proportion = (fraction - prevFraction) / (item.getPoint() - prevFraction); final double interpolatedT = prevT + (proportion * (item.getDistance() - prevT)); return getY(interpolatedT); } protected Point2D getXY(double t) { final double invT = 1 - t; final double b1 = 3 * t * invT * invT; final double b2 = 3 * t * t * invT; final double b3 = t * t * t; final Point2D xy = new Point2D.Double((b1 * points[0]) + (b2 * points[2]) + b3, (b1 * points[1]) + (b2 * points[3]) + b3); return xy; } protected double getY(double t) { final double invT = 1 - t; final double b1 = 3 * t * invT * invT; final double b2 = 3 * t * t * invT; final double b3 = t * t * t; return (b1 * points[2]) + (b2 * points[3]) + b3; } public class PointUnit { private final double distance; private final double point; public PointUnit(double distance, double point) { this.distance = distance; this.point = point; } public double getDistance() { return distance; } public double getPoint() { return point; } } } 

如果我们这样做......

 SplineInterpolator si = new SplineInterpolator(1, 0, 0, 1); for (double t = 0; t <= 1; t += 0.1) { System.out.println(si.interpolate(t)); } 

我们得到像......

 0.0 0.011111693284790492 0.057295031944523504 0.16510933001160544 0.3208510585798438 0.4852971690762217 0.6499037832761319 0.8090819765428142 0.9286158775101805 0.9839043020410436 0.999702 

好吧,现在你可能在想,“等一下,这是一个线性的进展!”,但事实并非如此,如果你把它绘制成图形,你会发现前三个和后三个值非常接近,其他值差别很大在不同程度上,这是我们的“进步”价值,我们应该在时间轴上走多远

所以现在,你的头应该爆炸(我的) - 这就是为什么我说,使用一个框架!

但是你会怎么用呢?! 这是有趣的部分,现在记住,一切都是可变的,动画的持续时间,对象随时间的速度,刻度或更新的数量,它都是变量......

这很重要,因为这就是这种东西的力量所在! 例如,如果由于某些外部因素导致动画停滞,则此实现能够简单地跳过那些“帧”,而不是陷入瓶颈和蹒跚。 这可能听起来像是一件坏事,但相信我,这就是愚弄眼睛“思考”某些事情正在发生变化;)

(以下就像8fps,所以很糟糕)

活跃

 import java.awt.Color; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.List; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.Timer; import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException; public class Test { public static void main(String[] args) { new Test(); } public Test() { EventQueue.invokeLater(new Runnable() { @Override public void run() { try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) { ex.printStackTrace(); } JFrame frame = new JFrame("Testing"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.add(new TestPane()); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); } }); } public class TestPane extends JPanel { private int startAt = 0; private int endAt; private int x = startAt; private Timer timer; private SplineInterpolator splineInterpolator; private long startTime = -1; private long playTime = 5000; // 5 seconds public TestPane() { splineInterpolator = new SplineInterpolator(1, 0, 0, 1); timer = new Timer(5, new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if (startTime < 0) { startTime = System.currentTimeMillis(); } long now = System.currentTimeMillis(); long duration = now - startTime; double t = (double) duration / (double) playTime; if (duration >= playTime) { t = 1; } double progress = splineInterpolator.interpolate(t); x = startAt + ((int) Math.round((endAt - startAt) * progress)); repaint(); } }); timer.setInitialDelay(0); addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { if (!timer.isRunning()) { startTime = -1; startAt = 0; endAt = getWidth() - 10; timer.start(); } } }); } @Override public Dimension getPreferredSize() { return new Dimension(200, 200); } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D) g.create(); g2d.setColor(Color.RED); g2d.fillRect(x, (getHeight() / 2) - 5, 10, 10); g2d.dispose(); } } public static class SplineInterpolator { private final double points[]; private final List normalisedCurve; public SplineInterpolator(double x1, double y1, double x2, double y2) { points = new double[]{x1, y1, x2, y2}; final List baseLengths = new ArrayList<>(); double prevX = 0; double prevY = 0; double cumulativeLength = 0; for (double t = 0; t <= 1; t += 0.01) { Point2D xy = getXY(t); double length = cumulativeLength + Math.sqrt((xy.getX() - prevX) * (xy.getX() - prevX) + (xy.getY() - prevY) * (xy.getY() - prevY)); baseLengths.add(length); cumulativeLength = length; prevX = xy.getX(); prevY = xy.getY(); } normalisedCurve = new ArrayList<>(baseLengths.size()); int index = 0; for (double t = 0; t <= 1; t += 0.01) { double length = baseLengths.get(index++); double normalLength = length / cumulativeLength; normalisedCurve.add(new PointUnit(t, normalLength)); } } public double interpolate(double fraction) { int low = 1; int high = normalisedCurve.size() - 1; int mid = 0; while (low <= high) { mid = (low + high) / 2; if (fraction > normalisedCurve.get(mid).getPoint()) { low = mid + 1; } else if (mid > 0 && fraction < normalisedCurve.get(mid - 1).getPoint()) { high = mid - 1; } else { break; } } /* * The answer lies between the "mid" item and its predecessor. */ final PointUnit prevItem = normalisedCurve.get(mid - 1); final double prevFraction = prevItem.getPoint(); final double prevT = prevItem.getDistance(); final PointUnit item = normalisedCurve.get(mid); final double proportion = (fraction - prevFraction) / (item.getPoint() - prevFraction); final double interpolatedT = prevT + (proportion * (item.getDistance() - prevT)); return getY(interpolatedT); } protected Point2D getXY(double t) { final double invT = 1 - t; final double b1 = 3 * t * invT * invT; final double b2 = 3 * t * t * invT; final double b3 = t * t * t; final Point2D xy = new Point2D.Double((b1 * points[0]) + (b2 * points[2]) + b3, (b1 * points[1]) + (b2 * points[3]) + b3); return xy; } protected double getY(double t) { final double invT = 1 - t; final double b1 = 3 * t * invT * invT; final double b2 = 3 * t * t * invT; final double b3 = t * t * t; return (b1 * points[2]) + (b2 * points[3]) + b3; } public class PointUnit { private final double distance; private final double point; public PointUnit(double distance, double point) { this.distance = distance; this.point = point; } public double getDistance() { return distance; } public double getPoint() { return point; } } } } 

因此,除了SplineInterpolator ,魔法发生在ActionListener内部的javax.swing.Timer (以及mouseClicked事件处理程序中的一些)

基本上,这会计算动画播放的时间( duration ),这将成为我们在时间线上的标准化时间tfraction值(0-1),然后我们使用它来计算我们在时间轴上的“进展” SplineInterpolator并根据它的起始位置和结束位置之间的差异乘以当前的“进展”来更新对象的位置

 if (startTime < 0) { startTime = System.currentTimeMillis(); } long now = System.currentTimeMillis(); long duration = now - startTime; double t = (double) duration / (double) playTime; if (duration >= playTime) { t = 1; } double progress = splineInterpolator.interpolate(t); x = startAt + ((int) Math.round((endAt - startAt) * progress)); repaint(); 

瞧,我们有一个轻松和轻松的动画!

现在,去使用动画框架! 这只是SOOOO简单得多:P

  • 对于“快进/慢出”,您可以使用0, 0, 1, 1
  • 对于“慢速/快速输出”,您可以使用0, 1, 0, 0
  • 对于“慢进”,您可以使用1, 0, 1, 1
  • 对于“慢速”,您可以使用0, 0, 0, 1

(或者至少那些是我使用的值)

试验,看看你得到了什么