如何从扩展线程的类中启动JPanel中的动画(将圆从一个点移动到另一个点)?

时间:2019-05-24 22:33:22

标签: java swing animation

我有一个名为Player的类,该类可扩展线程,其参数为JPanel(称为DrawPanel),坐标为x,y。 在Player构造函数中,我在面板上的位置x,y处绘制了一个圆 (我不知道它的正确性。)

Player运行功能中,我想启动一个动画,该动画将小红色圆圈从玩家坐标移动到另一点。

喜欢这个动画。

我该怎么做?

public class Player extends Thread {
 private  Graphics graphic;
 private Graphics2D g2;
 private int x;
 private int y;
 private DrawPanel panel;
    public Player(int x, int y,DrawPanel panel) 
    {
        this.x = x;
        this.y = y;
        this.panel = panel;
        graphic = panel.getGraphics();
        g2 = (Graphics2D) graphic;
        g2.fillOval( column,row, 10, 10);
    }
    public void run()
    {
     //startAnimation(this.x,this.y,destination.x,destination.y)
    }
}

2 个答案:

答案 0 :(得分:1)

我只是想开始,动画并不容易,好的动画很难。有很多理论可以使动画“看起来”更好,在此不做介绍,为此有更好的人才和资源。

我要讨论的是如何在Swing的基础上完成“好”动画。

第一个问题似乎是您对绘画在Swing中的工作方式没有很好的了解。您应该先阅读Performing Custom Painting in SwingPainting in Swing

接下来,您似乎没有意识到Swing实际上不是线程安全的(并且是单线程的)。这意味着您永远都不应从事件调度线程的上下文外部更新UI或UI依赖的任何状态。有关更多详细信息,请参见Concurrency in Swing

解决此问题的最简单方法是使用Swing Timer,有关更多详细信息,请参见How to Use Swing Timers

现在,您可以简单地运行Timer并进行直线平移,直到所有点都达到目标,但这并不总是最好的解决方案,因为它的缩放比例不好,并且看起来会有所不同在不同的PC上,并基于各自的功能。

在大多数情况下,基于持续时间的动画效果更好。这样,当PC无法跟上时,算法就可以“丢弃”帧。它的伸缩性更好(在时间和距离上),并且可以高度配置。

我喜欢产生可重复使用的代码块,因此我将从一个简单的“基于持续时间的动画引擎”开始...

// Self contained, duration based, animation engine...
public class AnimationEngine {

    private Instant startTime;
    private Duration duration;

    private Timer timer;

    private AnimationEngineListener listener;

    public AnimationEngine(Duration duration) {
        this.duration = duration;
    }

    public void start() {
        if (timer != null) {
            return;
        }
        startTime = null;
        timer = new Timer(5, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                tick();
            }
        });
        timer.start();
    }

    public void stop() {
        timer.stop();
        timer = null;
        startTime = null;
    }

    public void setListener(AnimationEngineListener listener) {
        this.listener = listener;
    }

    public AnimationEngineListener getListener() {
        return listener;
    }

    public Duration getDuration() {
        return duration;
    }

    public double getRawProgress() {
        if (startTime == null) {
            return 0.0;
        }
        Duration duration = getDuration();
        Duration runningTime = Duration.between(startTime, Instant.now());
        double progress = (runningTime.toMillis() / (double) duration.toMillis());

        return Math.min(1.0, Math.max(0.0, progress));
    }

    protected void tick() {
        if (startTime == null) {
            startTime = Instant.now();
        }
        double rawProgress = getRawProgress();
        if (rawProgress >= 1.0) {
            rawProgress = 1.0;
        }

        AnimationEngineListener listener = getListener();
        if (listener != null) {
            listener.animationEngineTicked(this, rawProgress);
        }

        // This is done so if you wish to expand the 
        // animation listener to include start/stop events
        // this won't interfer with the tick event
        if (rawProgress >= 1.0) {
            rawProgress = 1.0;
            stop();
        }
    }

    public static interface AnimationEngineListener {

        public void animationEngineTicked(AnimationEngine source, double progress);
    }
}

它并不太复杂,它有duration的时间,可以运行。它将以固定间隔(不少于5毫秒)tick生成tick事件,报告动画的当前进度(归一化的值,介于0和1之间)。

这里的想法是我们将“引擎”与使用它的那些元素分离。这使我们可以将其用于更广泛的可能性。

接下来,我需要某种方式来跟踪移动对象的位置...

public class Ping {

    private Point point;
    private Point from;
    private Point to;
    private Color fillColor;

    private Shape dot;

    public Ping(Point from, Point to, Color fillColor) {
        this.from = from;
        this.to = to;
        this.fillColor = fillColor;
        point = new Point(from);

        dot = new Ellipse2D.Double(0, 0, 6, 6);
    }

    public void paint(Container parent, Graphics2D g2d) {
        Graphics2D copy = (Graphics2D) g2d.create();
        int width = dot.getBounds().width / 2;
        int height = dot.getBounds().height / 2;
        copy.translate(point.x - width, point.y - height);
        copy.setColor(fillColor);
        copy.fill(dot);
        copy.dispose();
    }

    public Rectangle getBounds() {
        int width = dot.getBounds().width;
        int height = dot.getBounds().height;

        return new Rectangle(point, new Dimension(width, height));
    }

    public void update(double progress) {
        int x = update(progress, from.x, to.x);
        int y = update(progress, from.y, to.y);

        point.x = x;
        point.y = y;
    }

    protected int update(double progress, int from, int to) {
        int distance = to - from;
        int value = (int) Math.round((double) distance * progress);
        value += from;
        if (from < to) {
            value = Math.max(from, Math.min(to, value));
        } else {
            value = Math.max(to, Math.min(from, value));
        }

        return value;
    }
}

这是一个简单的对象,它获取起点和终点,然后根据进度计算对象在这些点之间的位置。可以在需要时自行涂漆。

现在,我们只需要一些方法将其组合在一起...

public class TestPane extends JPanel {

    private Point source;
    private Shape sourceShape;
    private List<Ping> pings;
    private List<Shape> destinations;

    private Color[] colors = new Color[]{Color.BLACK, Color.BLUE, Color.CYAN, Color.DARK_GRAY, Color.GREEN, Color.MAGENTA, Color.ORANGE, Color.PINK, Color.RED, Color.YELLOW};

    private AnimationEngine engine;

    public TestPane() {
        source = new Point(10, 10);
        sourceShape = new Ellipse2D.Double(source.x - 5, source.y - 5, 10, 10);

        Dimension size = getPreferredSize();

        Random rnd = new Random();
        int quantity = 1 + rnd.nextInt(10);
        pings = new ArrayList<>(quantity);
        destinations = new ArrayList<>(quantity);
        for (int index = 0; index < quantity; index++) {
            int x = 20 + rnd.nextInt(size.width - 25);
            int y = 20 + rnd.nextInt(size.height - 25);

            Point toPoint = new Point(x, y);

            // Create the "ping"
            Color color = colors[rnd.nextInt(colors.length)];
            Ping ping = new Ping(source, toPoint, color);
            pings.add(ping);

            // Create the destination shape...
            Rectangle bounds = ping.getBounds();
            Shape destination = new Ellipse2D.Double(toPoint.x - (bounds.width / 2d), toPoint.y - (bounds.height / 2d), 10, 10);
            destinations.add(destination);
        }

        engine = new AnimationEngine(Duration.ofSeconds(10));
        engine.setListener(new AnimationEngine.AnimationEngineListener() {
            @Override
            public void animationEngineTicked(AnimationEngine source, double progress) {
                for (Ping ping : pings) {
                    ping.update(progress);
                }
                repaint();
            }
        });
        engine.start();
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(200, 200);
    }

    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g.create();

        // This is probably overkill, but it will make the output look nicer ;)
        g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
        g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);

        // Lines first, these could be cached
        g2d.setColor(Color.LIGHT_GRAY);
        double fromX = sourceShape.getBounds2D().getCenterX();
        double fromY = sourceShape.getBounds2D().getCenterY();
        for (Shape destination : destinations) {
            double toX = destination.getBounds2D().getCenterX();
            double toY = destination.getBounds2D().getCenterY();
            g2d.draw(new Line2D.Double(fromX, fromY, toX, toY));
        }

        // Pings, so they appear above the line, but under the points
        for (Ping ping : pings) {
            ping.paint(this, g2d);
        }

        // Destination and source
        g2d.setColor(Color.BLACK);
        for (Shape destination : destinations) {
            g2d.fill(destination);
        }

        g2d.fill(sourceShape);

        g2d.dispose();
    }

}

好的,这个“看起来”很复杂,但是确实很简单。

  • 我们创建一个“源”点
  • 然后我们创建一个随机数的“目标”
  • 然后我们创建一个动画引擎并启动它。

然后,动画引擎将遍历所有Ping并根据当前进度值对其进行更新,并触发新的绘制过程,然后绘制源点和目标点之间的线,绘制{{ 1}} s,最后是源和所有目标点。很简单。

如果我希望动画以不同的速度运行怎么办?

嗯,这要复杂得多,并且需要更复杂的动画引擎。

通常来说,您可以建立“可动画”的概念。然后,将由一个中央“引擎”对其进行更新,该引擎会连续“滴答”(本身不受持续时间的限制)。

然后,每个“动画”都需要决定如何更新或报告其状态,并允许其他对象被更新。

在这种情况下,我将寻求一种更现成的解决方案,例如...

可运行的示例。...

Ping me

Ping

没有更简单的东西

正如我所说,好的动画很难。要做好,需要大量的精力和计划。我什至没有谈论过缓动,链接或混合算法,所以当我说这是一个简单,可重用的解决方案时,请相信我

不相信我,请查看JButton hover animation in Java Swing

答案 1 :(得分:-2)

尝试一下:

public class Player extends Thread {
 private  Graphics graphic;
 private Graphics2D g2;
 private int x;
 private int y;
 private DrawPanel panel;
 private numberOfIteration=5;
 private currentNumberOfIteration=0;
    public Player(int x, int y,DrawPanel panel) 
    {
        this.x = x;
        this.y = y;
        this.panel = panel;
        graphic = panel.getGraphics();
        g2 = (Graphics2D) graphic;
        g2.fillOval( column,row, 10, 10);
    }
    public Player(int x, int y,DrawPanel panel,int numberOfIteration) 
    {
        this.x = x;
        this.y = y;
        this.panel = panel;
        this.numberOfIteration=numberOfIterarion;
        graphic = panel.getGraphics();
        g2 = (Graphics2D) graphic;
        g2.fillOval( column,row, 10, 10);
    }
    public void run()
    {
     //startAnimation(this.x,this.y,destination.x,destination.y)
        currentNumberOfIteration=(++currentNumberOfIteration)%numberOfIteration;
        currentX=(int)((destinationX*currentNumberOfIteration+this.x)/(currentNumberOfIteration+1));
        currentY=(int)((destinationY*currentNumberOfIteration+this.y)/(currentNumberOfIteration+1));
        g2.fillOval( currentX,currentY, 10, 10);

    }
}

我看不到destinationXdestinationY的任何声明部分。