簡體   English   中英

如何從擴展線程的類開始在JPanel中創建動畫(從一個點移動一個圓圈到另一個點)?

[英]How do I start an animation (move a circle from one point to another) in a JPanel from a class that extends thread?

我有一個名為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)
    }
}

我只想開始,動畫並不容易,好動畫很難。 有很多理論可以讓動畫“看起來很好”,我不會在這里介紹,有更好的人和資源。

我將要討論的是如何在基本級別上進行Swing中的“好”動畫。

第一個問題似乎是你沒有很好地理解繪畫在Swing中是如何工作的。 你應該首先閱讀Swing中的SwingPainting中的 Performing Custom Painting

接下來,您似乎沒有意識到Swing實際上不是線程安全的(並且是單線程的)。 這意味着您永遠不應該在事件調度線程的上下文之外更新UI或UI所依賴的任何狀態。 有關更多詳細信息,請參閱Swing中的並發

解決此問題的最簡單方法是使用Swing Timer ,有關詳細信息,請參閱如何使用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 ,它將運行。 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();
    }

}

好吧,這看起來很“復雜”,但它非常簡單。

  • 我們創造了一個“源”點
  • 然后我們創建一個隨機數的“目標”
  • 然后我們創建一個動畫引擎並啟動它。

然后,動畫引擎將循環遍歷所有Ping並根據當前進度值更新它們並觸發新的繪制過程,然后繪制源點和目標點之間的線,繪制Ping ,然后最后繪制源和所有目標點。 簡單。

如果我希望動畫以不同的速度運行怎么辦?

啊,嗯,這要復雜得多,需要更復雜的動畫引擎。

一般來說,你可以建立一個“動畫”的概念。 然后,這將由一個連續“勾選”的中央“引擎”更新(本身並不局限於持續時間)。

然后,每個“動畫”都需要決定如何更新或報告其狀態並允許更新其他對象。

在這種情況下,我會尋求更現成的解決方案,例如......

可運行的例子....

平我

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懸停動畫

試試這個:

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任何聲明部分。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM