为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

神不在的星期二 2022-12-15 16:01:10
我有一个名为 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 回答

?
ITMISS

TA贡献1871条经验 获得超8个赞

我只想开始,动画并不容易,好的动画也很难。有很多理论可以让动画“看起来”很好,我不打算在这里介绍,有更好的人和资源。

我要讨论的是如何在基本级别上用 Swing 制作“好的”动画。

第一个问题似乎是您对 Swing 中的绘画工作原理没有很好的理解。您应该首先阅读在 Swing 中执行自定义绘画和在 Swing绘画

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

解决此问题的最简单方法是使用 Swing ,有关详细信息Timer,请参阅如何使用 Swing 计时器。

现在,您可以简单地运行 aTimer并进行直线、线性进展,直到您的所有点都达到目标,但这并不总是最好的解决方案,因为它不能很好地扩展,并且会根据个人的不同在不同的 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时间,它会运行。它将tick以固定间隔(不少于 5 毫秒)生成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();

    }


}

好吧,这个“看起来”很复杂,其实很简单。

  • 我们创建一个“源”点

  • 然后我们创建随机数量的“目标”

  • 然后我们创建一个动画引擎并启动它。

然后动画引擎将循环遍历所有Pings 并根据当前进度值更新它们并触发新的绘制通道,然后绘制源点和目标点之间的线,绘制Pings,最后绘制源和所有目标点。简单的。

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

啊,好吧,这要复杂得多,需要更复杂的动画引擎。

一般来说,您可以建立一个“可动画化”的概念。然后,这将由一个中央“引擎”更新,该引擎不断“滴答作响”(本身不受持续时间的限制)。

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

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

可运行的例子....

https://i.stack.imgur.com/MFQt4.gif

import java.awt.Color;

import java.awt.Container;

import java.awt.Dimension;

import java.awt.EventQueue;

import java.awt.Graphics;

import java.awt.Graphics2D;

import java.awt.Point;

import java.awt.Rectangle;

import java.awt.RenderingHints;

import java.awt.Shape;

import java.awt.event.ActionEvent;

import java.awt.event.ActionListener;

import java.awt.geom.Ellipse2D;

import java.awt.geom.Line2D;

import java.time.Duration;

import java.time.Instant;

import java.util.ArrayList;

import java.util.List;

import java.util.Random;

import javax.swing.JFrame;

import javax.swing.JPanel;

import javax.swing.Timer;

import javax.swing.UIManager;

import javax.swing.UnsupportedLookAndFeelException;


public class JavaApplication124 {


    public static void main(String[] args) {

        new JavaApplication124();

    }


    public JavaApplication124() {

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

        }


    }


    // Self contained, duration based, animation engine...

    public static 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);

        }

    }


    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;

        }

    }


}

有没有更简单的😓

正如我所说,好的动画很难。需要付出很多努力和计划才能做好。我什至没有谈到地役权、链式或混合算法,所以请相信我,这实际上是一个简单、可重用的解决方案

不信,看看Java Swing 中的 JButton 悬停动画


查看完整回答
反对 回复 2022-12-15
?
智慧大石

TA贡献1946条经验 获得超3个赞

试试这个:


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


    }

}

我没有看到destinationXand的任何声明部分destinationY。


查看完整回答
反对 回复 2022-12-15
  • 2 回答
  • 0 关注
  • 89 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信