Создание бесконечного количества объектов в JPanel и рисование их через PaintComponent в Java - PullRequest
0 голосов
/ 12 января 2019

У меня есть одна дилемма, как реализовать приложение. У меня есть JPanel с шириной 288 и высотой 512, затем я создал два объекта (изображения) и нарисовал их через paintComponent, используя координаты

 drawImage (Image1,288,128,this) ;
 drawImage (Image2, 288, 384, this);

. Они уменьшаются одновременно по оси X, и когда она достигает x = 144, новые (те же самые) изображения должны быть нарисованы в координатах '(x = 288, y = (int) Math.random () * 512)' и начать уменьшение как и первые должны все еще уменьшаться. И этот процесс должен быть бесконечным. Каждые новые объекты, достигающие x = 144, должны строить новые. Я пытался создать ArrayList с добавлением в него координат

ArrayList arrayX = new ArrayList(); 
arrayX.add(288)
arrayY.add((int) Math.random()* 512 )

, а затем извлечь значения через

array.get()

Но это было безуспешно. Я видел видео, где человек сделал это, используя JavaScript через массив

var position = []
position = ({
X : 288
Y : 256
 })

А затем реализуется через цикл, подобный этому

 function draw() {

 for (int i = 0; i < position.length; i++ ){
 cvs.drawImage(Image1,position[i].x , position[i].y)
 cvs.drawImage(Image2,position[i].x , position[i].y + 50)

 position [i] .x - -;
 if(position[i].x == 128)
 position.push({
 X : 288
 Y : Math.floor(Math.random()*512 })
 })
 }
 }

Я не знаю, как это сделать на Java. Может быть, я должен использовать массив, чтобы хранить переменные с координатами, или массив, но по-другому. Помоги мне, пожалуйста . Заранее спасибо

Ответы [ 3 ]

0 голосов
/ 12 января 2019

Мой ответ полностью основан на Ответ MadProgrammer (на самом деле это всеобъемлющее руководство).
Из того, что я прочитал в посте: «Каждый новый объект, достигающий x = 144, должен создавать новые», я думаю, что желаемая реализация немного отличается:

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

public class ImageAnimator {

    public ImageAnimator() {
        SwingUtilities.invokeLater(() -> {
            JFrame frame = new JFrame("Testing");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.add(new AnimationPane());
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        });
    }

    public static class Drawable {

        private int x;
        private final int y;
        private static final Image image = image();

        //construct with a random y value
        public Drawable(int x) {
            this(x, -1);
        }

        public Drawable(int x, int y) {
            this.x = x;
            this.y =  y < 0 ? (int) (Math.random() * (512 - 20)) : y;
        }

        public int getX() { return x;  }

        public int getY() { return y; }

        public void update() {  x--; }

        public Image getImage(){  return image; }

        public static Image image() {

            URL url = null;
            try {
                url = new URL("https://dl1.cbsistatic.com/i/r/2017/09/24/b2320b25-27f3-4059-938c-9ee4d4e5cadf/thumbnail/32x32/707de8365496c85e90c975cec8278ff5/iconimg241979.png");
                return ImageIO.read(url);

            } catch ( IOException ex) {
                ex.printStackTrace();
                return null;
            }
        }
    }

    public class AnimationPane extends JPanel {

        private final List<Drawable> drawables;
        private static final int W = 288, H = 512, CYCLE_TIME = 5;

        public AnimationPane() {
            drawables = new ArrayList<>(2);
            drawables.add(new Drawable(W, H/4));
            drawables.add(new Drawable(W, 3*H/4));

            Timer timer = new Timer(CYCLE_TIME, e ->  animate());
            timer.start();
        }

        private void animate() {

          for (Drawable drawable : new ArrayList<>(drawables)) {

              drawable.update();
              if(drawable.getX() == W/2) {
                  drawables.add(new Drawable(W)); //random Y
              }
              if(drawable.getX() <= 0) {
                  drawables.remove(drawable);
              }
          }
          repaint();
        }

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

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            for (Drawable drawable : drawables ) {
                g.drawImage(drawable.getImage(),drawable.getX(), drawable.getY(), null);
            }
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(()->new ImageAnimator());
    }
}

enter image description here

0 голосов
/ 13 января 2019

Следующее решение основано на моем предыдущем ответе .
Я добавляю его в ответ на комментарий MadProgrammer : «Лучшее решение - объединить объекты для повторного использования».
DrawAblesProducer производит drawable объектов по запросу. Он также хранит излишки объектов, чтобы не создавать слишком много таких объектов.
Я публикую его как отдельный ответ, потому что дополнительная функциональность имеет более высокую сложность:

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

public class ImageAnimator {

    private static final int W = 288, H = 512;

    public ImageAnimator() {
        SwingUtilities.invokeLater(() -> {
            JFrame frame = new JFrame("Testing");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.add(new AnimationPane(new DrawAblesProducer()));
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        });
    }

    public class AnimationPane extends JPanel {

        private final List<Drawable> drawables;
        private static final int CYCLE_TIME = 5;
        private final DrawAblesProducer producer;

        public AnimationPane(DrawAblesProducer producer) {
            this.producer = producer;
            drawables = new ArrayList<>(2);
            drawables.add(producer.issue(W, H/4));
            drawables.add(producer.issue(W, 3*H/4));

            Timer timer = new Timer(CYCLE_TIME, e ->  animate());
            timer.start();
        }

        private void animate() {

            for (Drawable drawable : new ArrayList<>(drawables)) {
                drawable.update();
                if(drawable.getX() == W/2) {
                    drawables.add(producer.issue(W)); //random Y
                }else if(drawable.getX() <= 0) {
                    drawables.remove(drawable);
                    producer.retrn(drawable);
                }
            }
            repaint();
        }

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

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            for (Drawable drawable : drawables ) {
                g.drawImage(drawable.getImage(),drawable.getX(), drawable.getY(), null);
            }
        }
    }

    //produces `drawable` objects on-demand. stores surplus object, to prevent producing 
    //too many such objects
    public class DrawAblesProducer {

        private final Queue<Drawable> warehouse = new LinkedList<>();

        public Drawable issue(int x){
            return issue(x, -1);
        }

        public Drawable issue(int x, int y){

            Drawable drawable = warehouse.poll();
            if(drawable != null ) {
                drawable.setX(x); drawable.setY(y);
                return  drawable;
            }
            return new Drawable(x, y);
        }

        public void retrn(Drawable drawable){
            warehouse.add(drawable);
        }
    }

    public static class Drawable {

        //made static so image is reused for all instances
        private static final Image image = image();

        private int x, y;

        //construct with a random y value
        public Drawable(int x) {
            this(x, -1);
        }

        public Drawable(int x, int y) {
            setX(x);
            setY(y);
        }

        public int getX() { return x;  }
        public void setX(int x) { this.x = x;}

        public int getY() { return y; }
        public void setY(int y) {
            this.y = y < 0 ? randomY() : y  ;
        }

        private int randomY() {
            int iHeight = image.getHeight(null);
            return iHeight + (int) (Math.random() * (H - iHeight));
        }

        public void update() {  x--; }

        public Image getImage(){  return image; }

        public static Image image() {

            URL url = null;
            try {
                url = new URL("https://dl1.cbsistatic.com/i/r/2017/09/24/b2320b25-27f3-4059-938c-9ee4d4e5cadf/thumbnail/32x32/707de8365496c85e90c975cec8278ff5/iconimg241979.png");
                return ImageIO.read(url);

            } catch ( IOException ex) { ex.printStackTrace();   }
            return null;
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(()->new ImageAnimator());
    }
}
0 голосов
/ 12 января 2019

Концептуально идея достаточно проста, проблема в том, что Swing является сигнальным потоком, а НЕ потокобезопасным.

См. Параллельность в Swing для получения более подробной информации.

Это означает, что вы можете запустить длительную или блокирующую операцию (например, бесконечный цикл) в потоке диспетчеризации событий, но также вы не должны обновлять пользовательский интерфейс (или свойства, от которых зависит пользовательский интерфейс) вне контекста EDT.

Хотя существует ряд возможных решений этой проблемы, простейшим, вероятно, является использование Swing Timer, который предоставляет средство для безопасного планирования задержки (которая не блокирует EDT) и который вызывает ее обновления в контексте EDT, что позволяет обновлять пользовательский интерфейс внутри него.

См. Как использовать Swing Timers для получения более подробной информации.

Теперь, поскольку вы говорите на ОО-языке, вы должны использовать возможности, которые он мне предоставляет, это означает инкапсуляцию.

У вас есть изображение, которое вы хотите нарисовать в определенном месте, но изменение местоположения которого может быть изменено в соответствии с некоторыми правилами, это просто кричит Plain Old Java Old (POJO)

Обычно я бы начал с interface, чтобы описать основные свойства и операции, но для краткости я прыгнул прямо к классу ...

public class Drawable {

    private int x, y;
    private Color color;

    public Drawable(int x, int y, Color color) {
        this.x = x;
        this.y = y;
        this.color = color;
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    public Color getColor() {
        return color;
    }

    public void update() {
        x--;
        if (x <= 144) {
            reset();
        }
    }

    protected void reset() {
        x = 288;
        y = (int) (Math.random() * (512 - 20));
    }

    public void paint(Graphics2D g2d) {
        Graphics2D copy = (Graphics2D) g2d.create();
        copy.translate(getX(), getY());
        copy.setColor(getColor());
        copy.drawOval(0, 0, 20, 20);
        copy.dispose();
    }
}

Но подождите, вы говорите, он использует Color вместо изображения !? Да, у меня не было маленьких картинок под рукой, кроме того, мне нужно оставить вам чем-нибудь заняться;)

Теперь анимация представляет собой последовательность обновления и рисования несколько раз, пока не будет достигнуто желаемое состояние.

В этом случае вас не очень заботит конечное состояние, поэтому вы можете просто оставить его включенным.

Цикл «обновления» обрабатывается Swing Timer, который зацикливается на List из Drawable объектов, вызывает их методы update и затем планирует repaint, который вызывает JPanel s paintComponent где Drawable объекты нарисованы, просто ? ...

public class TestPane extends JPanel {

    private List<Drawable> drawables;

    public TestPane() {
        drawables = new ArrayList<>(2);
        drawables.add(new Drawable(288, 128, Color.RED));
        drawables.add(new Drawable(288, 384, Color.RED));

        Timer timer = new Timer(5, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                for (Drawable drawable : drawables) {
                    drawable.update();
                }
                repaint();
            }
        });
        timer.start();
    }

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

    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        for (Drawable drawable : drawables) {
            Graphics2D g2d = (Graphics2D) g.create();
            drawable.paint(g2d);
            g2d.dispose();
        }
    }

}

В целом - работающий пример ...

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.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 Drawable {

        private int x, y;
        private Color color;

        public Drawable(int x, int y, Color color) {
            this.x = x;
            this.y = y;
            this.color = color;
        }

        public int getX() {
            return x;
        }

        public int getY() {
            return y;
        }

        public Color getColor() {
            return color;
        }

        public void update() {
            x--;
            if (x <= 144) {
                reset();
            }
        }

        protected void reset() {
            x = 288;
            y = (int) (Math.random() * (512 - 20));
        }

        public void paint(Graphics2D g2d) {
            Graphics2D copy = (Graphics2D) g2d.create();
            copy.translate(getX(), getY());
            copy.setColor(getColor());
            copy.drawOval(0, 0, 20, 20);
            copy.dispose();
        }
    }

    public class TestPane extends JPanel {

        private List<Drawable> drawables;

        public TestPane() {
            drawables = new ArrayList<>(2);
            drawables.add(new Drawable(288, 128, Color.RED));
            drawables.add(new Drawable(288, 384, Color.RED));

            Timer timer = new Timer(5, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    for (Drawable drawable : drawables) {
                        drawable.update();
                    }
                    repaint();
                }
            });
            timer.start();
        }

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

        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            for (Drawable drawable : drawables) {
                Graphics2D g2d = (Graphics2D) g.create();
                drawable.paint(g2d);
                g2d.dispose();
            }
        }

    }

}

«Есть ли более простое решение» ? Да, конечно, я всегда иду самым сложным способом, чтобы решить проблему в первую очередь ?. Во-первых, хорошая анимация сложна. Шутки в сторону. Я играю с этим искомым предметом более 20 лет, создавая хороший анимационный движок, который будет гибким, чтобы удовлетворить все возможные потребности, которые он может поставить, - почти невыполнимая задача, особенно в среде, которая на самом деле не разработана. за это.

Если вы мне не верите, вы можете взглянуть на

, это всего лишь пара примеров того, насколько сложной может быть анимация

Извините, вы будете удивлены, как часто меня спрашивают "может ли это быть проще", когда дело доходит до анимации;)

"Каждые новые объекты, достигшие x = 144, должны строить новые

Так что, видимо, я могу быть смущен этим конкретным моментом. Если это означает «добавление новых объектов после достижения 144», то это вызывает некоторые новые проблемы. Основная проблема - это одна из проблем, связанная с GC, которая вызывает замедление анимации. Конечно, мы имеем дело только с 4-6 объектами, но это одна из тех вещей, которая может вернуться к вам, если вы не будете осторожны.

Итак, я взял приведенный выше пример и внес некоторые изменения в цикл обновления. Это добавляет reusePool, где старые объекты размещаются и могут быть использованы повторно, уменьшая накладные расходы GC на повторное создание и уничтожение недолговечных объектов.

decaying List просто гарантирует, что, как только объект пройдет swanPoint, он не будет рассматриваться для повторного создания новых объектов. Конечно, вы можете поставить флаг на само POJO, но я не думаю, что это является частью ответственности POJO

public TestPane() {
    drawables = new ArrayList<>(2);
    reusePool = new ArrayList<>(2);
    decaying = new ArrayList<>(2);

    timer = new Timer(5, new ActionListener() {
        private List<Drawable> spawned = new ArrayList<>(5);

        @Override
        public void actionPerformed(ActionEvent e) {
            spawned.clear();
            Iterator<Drawable> it = drawables.iterator();
            int swapnPoint = getWidth() / 2;
            while (it.hasNext()) {
                Drawable drawable = it.next();
                drawable.update();

                if (drawable.getX() > 0 && drawable.getX() < swapnPoint) {
                    if (!decaying.contains(drawable)) {
                        decaying.add(drawable);
                        Drawable newDrawable = null;
                        if (reusePool.isEmpty()) {
                            newDrawable = new Drawable(
                                            getWidth() - 20,
                                            randomVerticalPosition(),
                                            randomColor());
                        } else {
                            newDrawable = reusePool.remove(0);
                            newDrawable.reset(getWidth() - 20,
                                            randomVerticalPosition(),
                                            randomColor());
                        }
                        spawned.add(newDrawable);
                    }
                } else if (drawable.getX() <= -20) {
                    System.out.println("Pop");
                    it.remove();
                    decaying.remove(drawable);
                    reusePool.add(drawable);
                }
            }
            drawables.addAll(spawned);

            repaint();
        }
    });
}

Теперь это позволит объектам перемещаться по всей ширине, создавая новые объекты, когда они проходят половину пути. Как только они выйдут за пределы визуального диапазона обзора, они будут помещены в reuse List, чтобы их можно было повторно использовать, когда потребуются новые объекты.

Пример выполнения ...

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.util.ArrayList;
import java.util.Iterator;
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 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();
                }

                TestPane testPane = new TestPane();
                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(testPane);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);

                EventQueue.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        testPane.start();
                    }
                });
            }
        });
    }

    public class Drawable {

        private int x, y;
        private Color color;

        public Drawable(int x, int y, Color color) {
            this.x = x;
            this.y = y;
            this.color = color;
        }

        public int getX() {
            return x;
        }

        public int getY() {
            return y;
        }

        public Color getColor() {
            return color;
        }

        public void update() {
            x--;
        }

        protected void reset(int x, int y, Color color) {
            this.x = x;
            this.y = y;
            this.color = color;
        }

        public void paint(Graphics2D g2d) {
            Graphics2D copy = (Graphics2D) g2d.create();
            copy.translate(getX(), getY());
            copy.setColor(getColor());
            copy.fillOval(0, 0, 20, 20);
            copy.setColor(Color.BLACK);
            copy.drawOval(0, 0, 20, 20);
            copy.dispose();
        }
    }

    public class TestPane extends JPanel {

        private List<Drawable> drawables;
        private List<Drawable> decaying;
        private List<Drawable> reusePool;

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

        private Timer timer;

        public TestPane() {
            drawables = new ArrayList<>(2);
            reusePool = new ArrayList<>(2);
            decaying = new ArrayList<>(2);

            timer = new Timer(40, new ActionListener() {
                private List<Drawable> spawned = new ArrayList<>(5);

                @Override
                public void actionPerformed(ActionEvent e) {
                    spawned.clear();
                    Iterator<Drawable> it = drawables.iterator();
                    int swapnPoint = getWidth() / 2;
                    while (it.hasNext()) {
                        Drawable drawable = it.next();
                        drawable.update();

                        if (drawable.getX() > 0 && drawable.getX() < swapnPoint) {
                            if (!decaying.contains(drawable)) {
                                decaying.add(drawable);
                                Drawable newDrawable = null;
                                if (reusePool.isEmpty()) {
                                    System.out.println("New");
                                    newDrawable = new Drawable(
                                                    getWidth() - 20,
                                                    randomVerticalPosition(),
                                                    randomColor());
                                } else {
                                    System.out.println("Reuse");
                                    newDrawable = reusePool.remove(0);
                                    newDrawable.reset(getWidth() - 20,
                                                    randomVerticalPosition(),
                                                    randomColor());
                                }
                                spawned.add(newDrawable);
                            }
                        } else if (drawable.getX() <= -20) {
                            System.out.println("Pop");
                            it.remove();
                            decaying.remove(drawable);
                            reusePool.add(drawable);
                        }
                    }
                    drawables.addAll(spawned);

                    repaint();
                }
            });
        }

        public void start() {
            drawables.add(new Drawable(getWidth(), 128, randomColor()));
            drawables.add(new Drawable(getWidth(), 384, randomColor()));
            timer.start();
        }

        protected int randomVerticalPosition() {
            return rnd.nextInt(getHeight() - 20);
        }

        protected Color randomColor() {
            return colors[rnd.nextInt(colors.length - 1)];
        }

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

        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            for (Drawable drawable : drawables) {
                Graphics2D g2d = (Graphics2D) g.create();
                drawable.paint(g2d);
                g2d.dispose();
            }
        }

    }

}
...