栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 面试经验 > 面试问答

如何使用线程实现缓动功能

面试问答 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

如何使用线程实现缓动功能

好的,动画是一个相当复杂且深入的主题,在此不做介绍,它还涉及很多我不太了解的数学,因此我们不做大量介绍深度或细节,比我更好的人可以解释它,您可以在网络上阅读它

因此,首先,我们做一些假设……

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

基本上,我们要做的是对所有内容进行“标准化”。也就是说,在动画开始时,时间为0,在结束时为1,介于两者之间的所有其他值都是这两个值之间的分数。

如果您可以这样想,事情就会变得容易得多。因此,根据时间轴上的给定点,您可以决定应该做什么。对于
例如,在50%的时间,你应该是你的起点和终点之间的中途点

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

钟形曲线

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

现在,我们可以使用贝塞尔曲线脊线/曲线进行一些数学运算,并在时间轴上给定点计算对象的速度。

现在,我直接从Timing framework中借用了大部分代码,但是如果您真的感兴趣,还可以查看游戏的BézierCurves:A Tutorial

(nb:我实际上确实写过这样的内容,然后两天后,发现
Timing framework已经实现了……是一个有趣的练习……)

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

(startValue + ((endValue - startValue) * progress))
沿时间线

我不会对此进行详细介绍,因为我真的不懂数学,我只是知道如何应用它,但是基本上,我们计算曲线上的点(x / y),然后将这些值归一化(0-1),以便于查找。

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

public class SplineInterpolator {    private final double points[];    private final List<PointUnit> normalisedCurve;    public SplineInterpolator(double x1, double y1, double x2, double y2) {        points = new double[]{ x1, y1, x2, y2 };        final List<Double> 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; }        }                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;        }    }}

If we do something like…

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

We get something like…

0.00.0111116932847904920.0572950319445235040.165109330011605440.32085105857984380.48529716907622170.64990378327613190.80908197654281420.92861587751018050.98390430204104360.999702

Okay, now you’re probably thinking, “wait a minute, that’s a linear
progression!”, but it’s not, if you graphed it, you would find that the first
three and last three values are very close together, and the others spread out
by varying degrees, this is our “progress” value, how far along the timeline
we should be

So about now, your head should be about to explode (mine is) - this is why I
say, use a framework!

But how would you use it?! This is the fun part, now remember, everything is
variable, the duration of the animation, the speed of the object over time,
the number of ticks or updates, it’s all variable…

This is important, as this is where the power of something like this comes in!
If for example, the animation is stalled due to some outside factor, this
implementation is capable of simply skipping those “frames”, rather than
getting bottlenecked and staggering. This might sound like a bad thing, but
trust me, this is all about fooling the eye into “think” something is changing
;)

(The following is like 8fps, so it’s pretty crappy)

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<PointUnit> normalisedCurve;        public SplineInterpolator(double x1, double y1, double x2, double y2) { points = new double[]{x1, y1, x2, y2}; final List<Double> 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;     } }  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; }        }    }}

So, apart from the

SplineInterpolator
, the magic happens inside the
ActionListener
for the
javax.swing.Timer
(and some in the
mouseClicked

event handler)

Basically, this calculates the amount of time (

duration
) the animation has
been playing, this becomes our normalised time
t
or
fraction
value (0-1)
over the time line, we then use this to calculate our “progression” through
the timeline with the
SplineInterpolator
and update the position of our
object based on the difference between it’s start and end positions multiplied
by the current “progression”

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();

And voila, we have a ease-in and ease-out animation!

Now, go use an animation framework! It’s just SOOOO much simpler :P

  • For “fast in/slow out”, you can use
    0, 0, 1, 1
  • For “slow in/fast out”, you can use
    0, 1, 0, 0
  • For “slow in”, you can use
    1, 0, 1, 1
  • For “slow out”, you can use
    0, 0, 0, 1

(or at least those are the values I use)

Experiment and see what you get



转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/569271.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号