moving buffered image to specific coordinates

2019-08-08 21:23发布

问题:

I am trying to move the blue square around the grid by pressing the arrow keys. I already set up the KeyListener but when I repaint the frame, I have to move the frame around in order for it to update. How do I make it a smooth animation (moving the square from one coordinate to another)?

Example:

if (keyCode == KeyEvent.VK_LEFT){
    x5=x5-xChange;
    frame.repaint();
}

My images are buffered images and are all set to specific coordinates. Here is my graphics class:

public static class myGraphicsPanel extends JPanel {   
    public void paintComponent(Graphics g) { 
      super.paintComponent(g); 
      g.drawImage(block1,x1,y1,null);
      g.drawImage(block2,x2,y2,null);
      g.drawImage(block3,x3,y3,null);
      g.drawImage(block4,x4,y4,null);
      g.drawImage(block5,x5,y5,null);
      g.drawImage(block6,x6,y6,null);
      g.drawImage(block7,x7,y7,null);
      g.drawImage(block8,x8,y8,null);
      g.drawImage(blue,x9,y9,null);
}

回答1:

You question is lacking a lot of information, how is the screen updated? How is the screen created? What relationship do the objects have with each other?

Many of these questions could be answered with a MCVE

Animation is the illusion of change over time. So you need a few things. You need some way to change the current state and some way to trigger and update to the UI over time.

The simplest (and safest) way to do this within Swing is to use a Swing Timer

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.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.Timer;

public class TestAnimation {

    public static void main(String[] args) {
        new TestAnimation();
    }

    public TestAnimation() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    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);
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
        });
    }

    public class TestPane extends JPanel {

        private BufferedImage img;

        private int xDelta, yDelta;
        private int xPos, yPos;

        public TestPane() throws IOException {
            img = ImageIO.read(new File("/Users/shane/Dropbox/MegaTokyo/thumnails/2.jpg"));
            Random rnd = new Random();
            do {
                xDelta = rnd.nextInt(4);
            } while (xDelta == 0);
            do {
                yDelta = rnd.nextInt(4);
            } while (yDelta == 0);
            if (rnd.nextBoolean()) {
                xDelta *= -1;
            }
            if (rnd.nextBoolean()) {
                yDelta *= -1;
            }

            xPos = (getPreferredSize().width - img.getWidth()) / 2;
            yPos = (getPreferredSize().height - img.getHeight()) / 2;

            Timer timer = new Timer(40, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    xPos += xDelta;
                    yPos += yDelta;
                    if (xPos + img.getWidth() > getWidth()) { 
                        xPos = getWidth() - img.getWidth();
                        xDelta *= -1;
                    } else if (xPos < 0) {
                        xPos = 0;
                        xDelta *= -1;
                    }
                    if (yPos + img.getHeight() > getHeight()) { 
                        yPos = getHeight() - img.getHeight();
                        yDelta *= -1;
                    } else if (yPos < 0) {
                        yPos = 0;
                        yDelta *= -1;
                    }

                    repaint();
                }
            });
            timer.start();
        }

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

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            if (img != null) {
                Graphics2D g2d = (Graphics2D) g.create();
                g2d.drawImage(img, xPos, yPos, this);
                g2d.dispose();
            }
        }

    }

}

Have a closer look at Concurrency in Swing and w to Use Swing Timers for more details

But, how do you do this using the key board? Surprisingly, it's very similar.

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.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class TestAnimation {

    public static void main(String[] args) {
        new TestAnimation();
    }

    public TestAnimation() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    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);
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
        });
    }

    public class TestPane extends JPanel {

        private BufferedImage img;
        private int xPos, yPos;

        public TestPane() throws IOException {
            img = ImageIO.read(new File("/Users/shane/Dropbox/MegaTokyo/thumnails/2.jpg"));

            xPos = (getPreferredSize().width - img.getWidth()) / 2;
            yPos = (getPreferredSize().height - img.getHeight()) / 2;

            addKeyBinding("up", KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), new KeyAction(0, -4));
            addKeyBinding("down", KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), new KeyAction(0, 4));
            addKeyBinding("left", KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), new KeyAction(-4, 0));
            addKeyBinding("right", KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), new KeyAction(4, 0));
        }

        protected void addKeyBinding(String name, KeyStroke keyStroke, Action action) {
            InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
            ActionMap am = getActionMap();

            im.put(keyStroke, name);
            am.put(name, action);
        }

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

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            if (img != null) {
                Graphics2D g2d = (Graphics2D) g.create();
                g2d.drawImage(img, xPos, yPos, this);
                g2d.dispose();
            }
        }

        public class KeyAction extends AbstractAction {

            private int xDelta, yDelta;

            public KeyAction(int xDelta, int yDelta) {
                this.xDelta = xDelta;
                this.yDelta = yDelta;
            }

            @Override
            public void actionPerformed(ActionEvent e) {
                xPos += xDelta;
                yPos += yDelta;
                if (xPos + img.getWidth() > getWidth()) {
                    xPos = getWidth() - img.getWidth();
                } else if (xPos < 0) {
                    xPos = 0;
                }
                if (yPos + img.getHeight() > getHeight()) {
                    yPos = getHeight() - img.getHeight();
                } else if (yPos < 0) {
                    yPos = 0;
                }

                repaint();
            }

        }

    }

}

Have a look at How to use key bindings and How to use actions for more details



回答2:

Try not to repaint the whole panel but only the area on which the square will be drawn, use

repaint(int x, int y , int width , int height);

where x and y will be the coordinates and width and height will width and height of your square.



回答3:

You could give each of your BufferedImages a target variable. You would check to see if the current x position was greater than or less than the target variables, and make changes according the the value. You can do this with y coordinate too.

int x1 = 100;
int x2 = 100;
int xTarget1 = 200; // x target coordinate
int xTarget2 = 200;
// And so on, for as many BufferedImages

int xChange = 1;

// A loop, wherever it may be.
public void loop() {

    if(x1 > xTarget1) {
        x1 = x1 - xChange;
    else if(x1 < xTarget1) {
        x1 = x1 + xChange;
    }
    // Same for the other BufferedImages

}

And then you would draw it. This should create a more smooth move of your images. It will be a constant move unless you change the speed variable through the change. I hopes this helps.