Creating endless number of objects in JPanel and draw them through PaintComponent in Java

六月ゝ 毕业季﹏ 提交于 2019-12-12 13:53:17

问题


I have one dilemma , how to realize application. I have JPanel with width 288 and height 512, then I created two objects ( images ) and drew them through paintComponent using coordinates

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

. They are decrementing simultaneously in the X axis and when it reaches x = 144 , new (same) images should be drawn at the coordinates ‘( x = 288 , y = (int)Math.random()* 512 )’ and begin decrement as well as first ones should still decrements. And this process should be endless. Every new objects reaching x = 144 should build new ones . I tried to create ArrayList with adding coordinates in it

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

and then extract values through

array.get()

But that was unsuccessfully. I saw video where man did it using JavaScript through the array

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

And then implemented through the loop like this

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

I don’t know how to do this in Java. May be I should use array too to keep variables with coordinates , or arraylist but in different way. Help me please . Thanks in advance


回答1:


My answer is complitely based on MadProgrammer's answer (A comprehensive tutorial actually).
From what I read in the post : "Every new objects reaching x = 144 should build new ones", I think the desired implementation is slightly different:

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




回答2:


Conceptually the idea is simple enough, the problem is, Swing is signal thread and NOT thread safe.

See Concurrency in Swing for more details.

This means you can run a long running or blocking operation (like a never ending loop) inside the Event Dispatching Thread, but also, you shouldn't update the UI (or properties the UI depends on) from outside the context of the EDT.

While there are a number of possible solutions to the problem, the simplest is probably to use a Swing Timer, which provides a means to schedule a delay safely (that won't block the EDT) and which will trigger it's updates within the context of the EDT, allowing you to update the UI from within it.

See How to Use Swing Timers for more details.

Now, because you're in a OO language, you should leverage the power it provides, to me, this means encapsulation.

You have a image, you want drawn at a specific location, but whose location change be changed based on some rules, this just screams Plain Old Java Old (POJO)

Normally, I'd start with a interface to describe the basic properties and operations, but for brevity, I've jumped straight for a class...

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

But wait, you say, it's using Color instead of image!? Yes, I didn't have any small images at hand, besides, I need to leave you something to do ;)

Now, the animation is a sequence of updating and painting repeatedly until a desired state is reached.

In this case, you don't care about the end state so much, so you can just keep it running.

The "update" cycle is handled by a Swing Timer, which loops over a List of Drawable objects, calls their update methods and then schedules a repaint, which triggers the JPanels paintComponent where by the Drawable objects are painted, simple 😝...

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

}

Putting it altogether - runnable example...

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

    }

}

"Is there a simpler solution"? Yes, of course, I always go to the hardest possible way to solve a problem first 🤪. First, good animation is hard. Seriously. I've been playing around with this sought of thing more nearly 20 years, making a good animation engine which is flexible to meet all the possible needs it might be put to is near impossible mission, especially in a framework which isn't really designed for it.

If you don't belief me, you could have a look at

  • How do I change Swing Timer Delay inside actionPerformed()
  • How can I fade out or fade in by command JPanel, its components and its color

which are just a couple of examples how complicated animation can be

Sorry, you'd be amazed how often I get asked "can it be simpler" when it comes to animation ;)

"Every new objects reaching x = 144 should build new ones

So, apparently I may be confused about this particular point. If this means "adding new objects after reaching 144" then this raises some new issues. The primary issue is one over GC overhead, which cause slow downs in the animation. Sure, we're only dealing with about 4-6 objects, but it's one of those things which can come back to byte you if you're not careful.

So I took the above example and made some modifications to the update cycle. This adds a reusePool where old objects are placed and can be re-used, reducing the GC overhead of repeatedly creating and destroying short lived objects.

The decaying List simply ensures that once an object passes the swanPoint, it won't be consider for re-spawning new objects. Sure you could put a flag on the POJO itself, but I don't think this is part of the POJOs responsibility

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

This will now allow objects to travel the whole width of the width, spawning new objects as they pass the half way point. Once they pass beyond the visual range of the view, they will be placed into the reuse List so they can be reused again when new objects are required.

Runnable example...

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

    }

}



回答3:


The following solution is based on my previous answer.
I add it in response to MadProgrammer's comment: "A better solution is to pool the objects for re-use".
DrawAblesProducer produces drawable objects on-demand. It also stores surplus object, to prevent producing too many such objects.
I post it as a separate answer because the additional functionality comes with somewhat higher complexity:

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


来源:https://stackoverflow.com/questions/54156781/creating-endless-number-of-objects-in-jpanel-and-draw-them-through-paintcomponen

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!