2

I need to draw in AWT/Swing rectangles that are moving from frame to frame.

I have a Playground class

public Playground(int sizeX, int sizeY)
{
    frame = new JFrame();
    frame.setVisible(true);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    panel = new JPanel();
    panel.setSize(sizeX, sizeY);
    panel.setDoubleBuffered(true);

    panel.setVisible(true);
    frame.add(panel);
    frame.pack();
    frame.setSize(sizeX, sizeY);
}

public void refresh()
{
    panel.repaint();
}

public Graphics getGraphics()
{
    return panel.getGraphics();
}

This is the class in which objects should be drawn:

public class Star {

    private static int size = 10;

    private int posX;
    private int posY;

    public Star(int posX, int posY)
    {
        this.posX = posX;
        this.posY = posY;
    }

    public void paint(Graphics g)
    {
        g.fillRect(posX, posY, size, size);
    }

    public int getPosX() {
        return posX;
    }

    public int getPosY() {
        return posY;
    }
}

This is the main method:

public static void main(String[] args) {
    Playground playground = new Playground(400, 400);
    Star star = new Star(100, 100);
    Star star2 = new Star(125, 125);
    while(1 == 1)
    {
        playground.refresh();
        star.paint(playground.getGraphics());
        star2.paint(playground.getGraphics());
    }
}

The objects are drawn but are flickering, how can I stop it from flickering?

Edit: I solved the flickering for one element, by changing the refresh method to:

public void refresh()
{
    panel.getGraphics().clearRect(0,0, panel.getWidth(), panel.getHeight());
}

Unfortunately only one Element is not flickering all others are still flickering.

Anima
  • 339
  • 2
  • 16
  • Welcome to SO. "drawing moving rectangles in loop" - before moving it, you need to draw one. Did you manage to do so ? I can't see it in the code. – c0der Oct 13 '18 at 13:28
  • The (filled) rectangles are drawn (in the main loop, which is an endless loop just calling 2 draws for elements right now), they are also moving and everything. The problem is, there is heavy flickering, it doesn't matter if I draw them always in one place or if they are moving :/ – Anima Oct 13 '18 at 13:40
  • 1
    Please post [mcve] so we will not have to guess. End endless loop on the [EDT](https://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html) will freeze the GUI. – c0der Oct 13 '18 at 13:44
  • I can put the loop into another thread, but the problem still persists (even if i put a condition on the loop so I can stop it). There is no GUI that should be interacted with, it's basically only there to visualize an algorithm. – Anima Oct 13 '18 at 14:05
  • i updated the post, its minimal and all the code i execute.. – Anima Oct 13 '18 at 14:29
  • 1
    The posted code is not mcve. To be so it has to be executable after copy past. `Playground` as posted can not compile. See mcve example in my answer. – c0der Oct 13 '18 at 14:45

1 Answers1

2

The following is a one-file mcve that demonstrates moving (rotating for simplicity) a rectangle by custom painting. One-file meaning that you can copy-paste the entire code into one file (AnimateRectangle.java) and execute it.

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

public class AnimateRectangle {

    private JFrame frame;

    public AnimateRectangle(Model model){

        frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JPanel panel = new MyJPanel(model);
        panel.setDoubleBuffered(true);

        frame.add(panel);
        frame.pack();
        frame.setVisible(true);
    }

    void refresh() {
        frame.repaint();
    }

    public static void main(String[] args) throws InterruptedException {
        Controller controller = new Controller(400, 400);
        while (true) {
            Thread.sleep(1000);
            SwingUtilities.invokeLater(()->controller.animate());
        }
    }
}

//"wires" gui and model 
class Controller{

    private Model model;
    private AnimateRectangle view;

    Controller(int sizeX, int sizeY){

         model = new Model(sizeX, sizeY);
         view = new AnimateRectangle(model);
    }

    void animate() {
        int newAngle = (model.getAngle() < 360 ) ? model.getAngle()+1 : 0 ;
        model.setAngle(newAngle);
        view.refresh();
    }
}

//represents the inforamtion the GUI needs 
class Model{

    int sizeX, sizeY, angle = 0;

    public Model(int sizeX, int sizeY) {
        this.sizeX = sizeX;
        this.sizeY = sizeY;
    }

    int getSizeX() { return sizeX; }

    int getSizeY() {return sizeY;}

    int getAngle() {return angle;}

    //degrees
    void setAngle(int angle) {  this.angle = angle; }
}

//a JPanel with custom paint component 
class MyJPanel extends JPanel {

    private Model model;

    public MyJPanel(Model model) {
        this.model = model;
        setPreferredSize(new Dimension(model.getSizeX(), model.getSizeY()));
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D)g;
        g2d.setColor(Color.RED);
        int sizeX = model.getSizeX(), sizeY = model.getSizeY();
        g2d.rotate(Math.toRadians(model.getAngle()), sizeX /2, sizeY/2);
        g2d.fillRect(sizeX/4, sizeY/4, sizeX/2, sizeY/2);
    }
}

A better option (see camickr comment) is to animate using swing Timer. To do so, remove animate() method, and replace it with :

void animateWithTimer(){

    new Timer(1000,new ActionListener() {

        @Override
        public void actionPerformed(ActionEvent e) {
            int newAngle = (model.getAngle() < 360 ) ? model.getAngle()+1 : 0 ;
            model.setAngle(newAngle);
            view.refresh();
        }
    }).start();
}

and change main to use it :

public static void main(String[] args) throws InterruptedException {
    Controller controller = new Controller(400, 400);
    controller.animateWithTimer();
}
c0der
  • 18,467
  • 6
  • 33
  • 65
  • `Another option is to animate using swing Timer.` - actually that is the option that should be used. All updates to the GUI should be done on the Event Dispatch Thread. And the code in the main() method should be invoked from the EDT as well. – camickr Oct 13 '18 at 14:49
  • @camickr Thanks for the feedback. I updated the code with `SwingUtilities.invokeLater(()->controller.animate());` . The option that does not use timer was posted because I thing the OP want's an external control over the animation. – c0der Oct 13 '18 at 14:53
  • Is there no way to draw from the "outside". Here and in other examples I've seen always the paintComponent gets overriden, where some drawing takes place. But I will have an indefinite amount of elements that will be drawn (that's why I'd like to call the draw from them to the graphics object I get from thee JPanel)? – Anima Oct 13 '18 at 15:02
  • When I don't use the refresh (of course the old positions are not cleared and a line is drawn) it doesn't flicker, is there some way to controll the repaint method? – Anima Oct 13 '18 at 15:12
  • Change the drawing attributes (including the number of elements to draw) in the model. Those attributes should be use by [paintComponent](https://docs.oracle.com/javase/tutorial/uiswing/painting/index.html). Number of elements drawn can be dynamic. see examples [1](https://stackoverflow.com/a/52685403/3992939) [2](https://stackoverflow.com/a/43706965/3992939) – c0der Oct 13 '18 at 15:15