Come ruotare un’immagine gradualmente in Swing?

Ho un’immagine che sto ruotando quando l’utente fa clic su un pulsante. Ma non sta funzionando.

Mi piacerebbe vedere l’immagine ruotare gradualmente fino a 90 gradi finché non si arresta, ma non lo fa. L’immagine deve ruotare di 90 gradi gradualmente quando si fa clic sul pulsante.

Ho creato un SSCCE per dimostrare il problema. Si prega di sostituire l’immagine nella class CrossingPanelSSCE con qualsiasi immagine di vostra scelta. Basta inserire l’immagine nella cartella delle images e denominarla images/railCrossing.JPG .

RotateButtonSSCE

 import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JPanel; public class RotateButtonSSCE extends JPanel implements ActionListener{ private JButton rotate = new JButton("Rotate"); private VisualizationPanelSSCE vis = new VisualizationPanelSSCE(); public RotateButtonSSCE() { this.setBorder(BorderFactory.createTitledBorder("Rotate Button ")); this.rotate.addActionListener(this); this.add(rotate); } public void actionPerformsd(ActionEvent ev) { vis.rotatetheCrossing(); } } 

CrossingPanelSSCE

 import java.awt.Color; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.geom.AffineTransform; import javax.swing.BorderFactory; import javax.swing.JPanel; import javax.swing.border.TitledBorder; public class CrossingPanelSSCE extends JPanel{ private static final long serialVersionUID = 1L; // private data members private Image crossingImage; private int currentRotationAngle; private int imageWidth; private int imageHeight; private AffineTransform affineTransform; private boolean clockwise; private static int ROTATE_ANGLE_OFFSET = 2; private int xCoordinate; private int yCoordinate; private static javax.swing.Timer timer; private void initialize(){ this.crossingImage = Toolkit.getDefaultToolkit().getImage("images/railCrossing.JPG"); this.imageWidth = this.getCrossingImage().getWidth(this); this.imageHeight = this.getCrossingImage().getHeight(this); this.affineTransform = new AffineTransform(); currentRotationAngle = 90; timer = new javax.swing.Timer(20, new MoveListener()); } public CrossingPanelSSCE(int x, int y) { this.setxCoordinate(x); this.setyCoordinate(y); this.setPreferredSize(new Dimension(50, 50)); this.setBackground(Color.red); TitledBorder border = BorderFactory.createTitledBorder("image"); this.setLayout(new FlowLayout()); this.initialize(); } public void paintComponent(Graphics grp){ Rectangle rect = this.getBounds(); Graphics2D g2d = (Graphics2D)grp; g2d.setColor(Color.BLACK); this.getAffineTransform().setToTranslation(this.getxCoordinate(), this.getyCoordinate()); //rotate with the rotation point as the mid of the image this.getAffineTransform().rotate(Math.toRadians(this.getCurrentRotationAngle()), this.getCrossingImage().getWidth(this) /2, this.getCrossingImage().getHeight(this)/2); //draw the image using the AffineTransform g2d.drawImage(this.getCrossingImage(), this.getAffineTransform(), this); } public void rotateCrossing(){ System.out.println("CurrentRotationAngle: " + currentRotationAngle); this.currentRotationAngle += ROTATE_ANGLE_OFFSET; //int test = currentRotationAngle % 90; if(currentRotationAngle % 90 == 0){ setCurrentRotationAngle(currentRotationAngle); timer.stop(); } //repaint the image panel repaint(); } void start() { if (timer != null) { timer.start(); } } private class MoveListener implements ActionListener { public void actionPerformsd(ActionEvent e) { rotateCrossing(); } } public Image getCrossingImage() { return crossingImage; } public void setCrossingImage(Image crossingImage) { this.crossingImage = crossingImage; } public int getCurrentRotationAngle() { return currentRotationAngle; } public void setCurrentRotationAngle(int currentRotationAngle) { this.currentRotationAngle = currentRotationAngle; } public int getImageWidth() { return imageWidth; } public void setImageWidth(int imageWidth) { this.imageWidth = imageWidth; } public int getImageHeight() { return imageHeight; } public void setImageHeight(int imageHeight) { this.imageHeight = imageHeight; } public AffineTransform getAffineTransform() { return affineTransform; } public void setAffineTransform(AffineTransform affineTransform) { this.affineTransform = affineTransform; } public boolean isClockwise() { return clockwise; } public void setClockwise(boolean clockwise) { this.clockwise = clockwise; } public int getxCoordinate() { return xCoordinate; } public void setxCoordinate(int xCoordinate) { this.xCoordinate = xCoordinate; } public int getyCoordinate() { return yCoordinate; } public void setyCoordinate(int yCoordinate) { this.yCoordinate = yCoordinate; } public javax.swing.Timer getTimer() { return timer; } public void setTimer(javax.swing.Timer timer) { this.timer = timer; } } 

VisualizationPanelSSCE

 import gui.CrossingPanel; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.geom.GeneralPath; import javax.swing.BorderFactory; import javax.swing.JPanel; import javax.swing.SwingUtilities; import javax.swing.border.TitledBorder; import application.Robot2; public class VisualizationPanelSSCE extends JPanel{ //private data members private GeneralPath path; private Shape horizontalRail; private Shape verticalRail; private static int LENGTH = 350; private CrossingPanelSSCE crossingP; private void initializeComponents(){ this.path = new GeneralPath(); this.horizontalRail = this.createHorizontalRail(); this.verticalRail = this.createVerticalRail(); this.crossingP = new CrossingPanelSSCE(328,334); } public VisualizationPanelSSCE(){ this.initializeComponents(); this.setPreferredSize(new Dimension(400,400)); TitledBorder border = BorderFactory.createTitledBorder("Rotation"); this.setBorder(border); } public GeneralPath getPath() { return path; } public void setPath(GeneralPath path) { this.path = path; } private Shape createHorizontalRail(){ this.getPath().moveTo(5, LENGTH); this.getPath().lineTo(330, 350); this.getPath().closePath(); return this.getPath(); } private Shape createVerticalRail(){ this.getPath().moveTo(350, 330); this.getPath().lineTo(350,10); this.getPath().closePath(); return this.getPath(); } public void paintComponent(Graphics comp){ super.paintComponent(comp); Graphics2D comp2D = (Graphics2D)comp; BasicStroke pen = new BasicStroke(15.0F, BasicStroke.CAP_BUTT,BasicStroke.JOIN_ROUND); comp2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); comp2D.setPaint(Color.black); comp2D.setBackground(Color.WHITE); comp2D.draw(this.horizontalRail); this.crossingP.paintComponent(comp2D); } public CrossingPanelSSCE getCrossingP() { return crossingP; } public void setCrossingP(CrossingPanelSSCE crossingP) { this.crossingP = crossingP; } public void rotatetheCrossing(){ Runnable rotateCrossing1 = new Runnable(){ public void run() { crossingP.start(); } }; SwingUtilities.invokeLater(rotateCrossing1); } } 

TestGUISSCE contiene il metodo principale.

 import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.Random; import javax.swing.*; public class TestGUISSCE{ private RotateButtonSSCE rotate = new RotateButtonSSCE(); private VisualizationPanelSSCE vision = new VisualizationPanelSSCE(); public void createGui(){ JFrame frame = new JFrame("Example"); frame.setSize(new Dimension(500, 500)); JPanel pane = new JPanel(); pane.add(this.vision); pane.add(rotate); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.add(pane); frame.setVisible(true); } public static void main(String[] args) { new TestGUISSCE().createGui(); } } 

Oltre alle utili osservazioni di @tulskiy, aggiungerei due punti:

  1. Costruisci sempre la tua GUI sul thread di invio dell’evento , come mostrato di seguito.

  2. Un sscce dovrebbe essere un esempio breve, autonomo, corretto (compilabile) . Per comodità, non richiedere ad altri di ricreare più classi pubbliche; utilizzare le classi di livello superiore (pacchetto-privato) o nidificate. Poiché si tratta di un problema grafico, utilizza un’immagine pubblica o sintetica che rifletta il tuo problema.

Nell’esempio seguente, paintComponent() altera la trasformazione del contesto grafico per effettuare la rotazione. Si noti che le operazioni vengono eseguite nell’ordine inverso (apparente) dell’ordine di dichiarazione: in primo luogo, il centro dell’immagine viene tradotto all’origine; in secondo luogo, l’immagine viene ruotata; terzo, il centro dell’immagine è tradotto al centro del pannello. Puoi vedere l’effetto ridimensionando il pannello.

Addendum: vedere anche questo approccio alternativo utilizzando AffineTransform .

Immagine

 package overflow; import java.awt.*; import java.awt.event.*; import java.awt.image.BufferedImage; import java.util.Random; import javax.swing.*; /** * @see https://stackoverflow.com/questions/3371227 * @see https://stackoverflow.com/questions/3405799 */ public class RotateApp { private static final int N = 3; public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { @Override public void run() { JFrame frame = new JFrame(); frame.setLayout(new GridLayout(N, N, N, N)); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); for (int i = 0; i < N * N; i++) { frame.add(new RotatePanel()); } frame.pack(); frame.setVisible(true); } }); } } class RotatePanel extends JPanel implements ActionListener { private static final int SIZE = 256; private static double DELTA_THETA = Math.PI / 90; private final Timer timer = new Timer(25, this); private Image image = RotatableImage.getImage(SIZE); private double dt = DELTA_THETA; private double theta; public RotatePanel() { this.setBackground(Color.lightGray); this.setPreferredSize(new Dimension( image.getWidth(null), image.getHeight(null))); this.addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { image = RotatableImage.getImage(SIZE); dt = -dt; } }); timer.start(); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D) g; g2d.translate(this.getWidth() / 2, this.getHeight() / 2); g2d.rotate(theta); g2d.translate(-image.getWidth(this) / 2, -image.getHeight(this) / 2); g2d.drawImage(image, 0, 0, null); } @Override public void actionPerformed(ActionEvent e) { theta += dt; repaint(); } @Override public Dimension getPreferredSize() { return new Dimension(SIZE, SIZE); } } class RotatableImage { private static final Random r = new Random(); static public Image getImage(int size) { BufferedImage bi = new BufferedImage( size, size, BufferedImage.TYPE_INT_ARGB); Graphics2D g2d = bi.createGraphics(); g2d.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.setPaint(Color.getHSBColor(r.nextFloat(), 1, 1)); g2d.setStroke(new BasicStroke(size / 8)); g2d.drawLine(0, size / 2, size, size / 2); g2d.drawLine(size / 2, 0, size / 2, size); g2d.dispose(); return bi; } } 

Il codice per l’ icona ruotata usa AffineTransform per ruotare attorno al suo centro.

 this.crossingP.paintComponent(comp2D); 

Non farlo mai! Il tuo CrossingPane non viene aggiunto a nessun componente, quindi il repaint () non ha alcun effetto. Puoi controllarlo aggiungendo stampe nel metodo paintComponent (). Quindi è necessario aggiungere CrossingPane a VisualizationPane:

 setLayout(new BorderLayout()); add(crossingP, BorderLayout.CENTER); 

Ci sono alcuni problemi con il centraggio dell’immagine, ma questo non dovrebbe essere difficile da risolvere.

PS. Leggi di nuovo su layout e pittura.