当前位置: 首页 > 面试题库 >

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

越涵衍
2023-03-14
问题内容

我试图找到一种有效,正常或简单的方法来将缓动函数实现到我的Java程序中。我已经启用了缓动功能,但是我觉得
有一种更有效的方法可以实现;我看不到的那一个,可能是因为隧道视线。这是我的代码;有人可以向我展示我应该做些什么吗?或为我指明进行研究的方向

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上的任何给定点上(时间上),我们应该能够计算出速度。

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

现在,我直接从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;
            }
        }
        /*
         * 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;
        }

    }

}

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.0
0.011111693284790492
0.057295031944523504
0.16510933001160544
0.3208510585798438
0.4852971690762217
0.6499037832761319
0.8090819765428142
0.9286158775101805
0.9839043020410436
0.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)

Animate

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;
                }
            }
            /*
             * 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;
            }

        }

    }

}

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



 类似资料:
  • 我刚刚开始研究Java的类和方法。根据API,生成的线程池重用现有的对象来执行新任务。 我有点困惑这是如何实现的,因为我在API中找不到任何方法可以设置现有对象的行为。 例如,可以从对象创建新的,这使得调用的方法。但是,API中没有将作为参数的setter方法。 我会很感激你的指点。

  • 问题内容: 我刚刚开始研究Java的类和方法。根据API,生成的线程池将现有对象重用于新任务。 我对此感到有些困惑,因为我无法在API中找到任何方法来设置现有对象的行为。 例如,您可以创建一个 新的 从一个对象,这使得调用的方法。但是,API中没有使用a 作为参数的setter方法。 我将不胜感激任何指针。 问题答案: 执行人员在后台为您完成所有工作。是的,它仅使用现有的线程API。 下面的链接提

  • 本文向大家介绍微信小程序如何实现在线客服功能,包括了微信小程序如何实现在线客服功能的使用技巧和注意事项,需要的朋友参考一下 其实只需要解决2个问题 第一步,在微信小程序中添加联系在线客服按钮 第二步,接入在线客服功能,设置【自动/人工/转人工】回复等 一、添加小程序客服按钮 ① 在小程序中添加客服按钮功能,点此参考官方文档(开发者通过一行代码,实现客服功能。) ② 常见的客服按钮形式有2种: 1.

  • 问题内容: 最少使用(LFU)是一种高速缓存算法,用于管理计算机内的内存。此方法的标准特性涉及系统跟踪内存中块被引用的次数。当缓存已满且需要更多空间时,系统将以最低参考频率清除项目。 例如,用Java来实现最近使用的对象缓存的最佳方法是什么? 我已经使用LinkedHashMap实现了一个(通过保持访问对象的次数),但是我很好奇是否有任何新的并发集合会更好。 考虑这种情况:假设缓存已满,我们需要为

  • 本文向大家介绍利用Redis如何实现自动补全功能,包括了利用Redis如何实现自动补全功能的使用技巧和注意事项,需要的朋友参考一下 忘了redis从哪个版本开启,能够根据输入的部分命令前缀给出提示,即自动补全。接下来笔者介绍基于redis实现这个很酷的功能。 about sorted set 假设结果中有mara,marabel,marcela。现在我们输入mar,就能得到这三个名字,并且输出结果

  • 问题内容: 我正在寻找实现以下目标的最简单,最直接的方法: 主程序实例化工作线程来执行任务。 只能一次运行任务。 当到达,没有更多的工人开始,直到运行的线程数降回到低于。 问题答案: 我认为Executors.newFixedThreadPool符合您的要求。有多种不同的方法可以使用生成的ExecutorService,具体取决于您是希望将结果返回到主线程,还是该任务完全独立,以及是否有要执行的任