Smooth Motion with Java Swing - java

I'm having trouble getting smooth and fluid motion with java Swing. Using this code, which involves using a BufferedImage to draw graphics to before applying it to the panel, gives me results with shaky motion and stuttering. How can I fix this?
public class SmoothMotion extends JPanel {
public static final int size = 800;
public static int pos = 0;
public static void main(String[] args) {
JPanel panel = new SmoothMotion();
JFrame frame = new JFrame("Smooth As");
frame.setSize(size, size);
frame.add(panel);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Timer timer = new Timer(17, new ActionListener() {
public void actionPerformed(ActionEvent e) {
SmoothMotion.updateItems();
panel.repaint();
}
});
timer.start();
}
public static void updateItems() {
pos += 4;
pos = pos > 750 ? 0 : pos;
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLACK);
g.fillRect(0, 0, size, size);
g.setColor(Color.RED);
g.fillRect(pos > 375 ? 750 - pos : pos, 100, 50, 50);
}
}

don't override paint(). Custom painting is done by overriding paintComponent(...)
there is no need for the BufferedImage. Swing is double buffered by default. Just invoke super.paintComponent(graphics) first to clear the background and then do your custom painting.
the painting method should NOT change properties of the class. You should have a method like updatePos(…) to update the position.
The ActionLIstener would invoke the updatePos() which would then invoke repaint().
See: get width and height of JPanel outside of the class for an example that demonstrates many of the above suggestions.

Related

Swing Timer and Painting on Canvas

Hi i wanna ask about Swing timer and Canvas. Im doing simple animation for changing color of objects. I made it with Thread.sleep but JFrame was unresponsive while repainting so i change it to Swing Timer. But now when i start the animation its doing nothing timer is working but objects on canvas dont change color.
Here is my function to animate change of colors im using it in overide paint function of canvas
private void paintSearch(Vector<NodeGraph2D> vector,Graphics graphics2D) {
if (!vector.isEmpty()) {
final int[] k = {0};
Timer timer = new Timer(1000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (repaint) {
if (k[0] == vector.size())
return;
if (vector == pruferTreeNodes) {
vector.elementAt(k[0]).draw((Graphics2D) graphics2D);
} else {
graphics2D.setColor(Color.GREEN);
((Graphics2D) graphics2D).fill(vector.elementAt(k[0]));
graphics2D.setColor(Color.BLACK);
((Graphics2D) graphics2D).drawString(vector.elementAt(k[0]).getNodeGraph().getName(), vector.elementAt(k[0]).getX1() + 15, vector.elementAt(k[0]).getY1() + 25);
}
k[0] += 1;
}
}
});
timer.start();
}
}
Do you think my usage of timers is bad ? Thanks for response. :)
When doing custom painting in Swing it is a good idea to subclass JPanel (it could be an anonymous class) and to store painting-related data in attributes somewhere accessible to the panel.
Your timer would not do any painting but rather manipulate the painting-related data. You should never attempt to do any painting on a graphics object outside of the EventDispatcherThread of Swing or outside of the paintComponent methods of JComponents. (refer to the documentation of these methods for further information)
Here is an example of how custom painting with a timer manipulating color could look like:
public static void main(String[] args) {
EventQueue.invokeLater(Example::new);
}
// this is the painting-related data that is being manipulated by the timer
private int currentColorIndex;
public Example() {
JFrame frame = new JFrame("Custom Painting");
frame.setSize(640, 480);
frame.setLocationRelativeTo(null);
Color[] allColors = {Color.RED, Color.BLUE, Color.GREEN,
Color.YELLOW, Color.ORANGE, Color.MAGENTA};
JPanel myCustomPanel = new JPanel() {
#Override
protected void paintComponent(Graphics g) {
// here the painting related data is being used by the custom JPanel implementation
g.setColor(allColors[currentColorIndex]);
g.fillRect(0, 0, getWidth(), getHeight());
}
};
frame.setContentPane(myCustomPanel);
Timer timer = new Timer(100, e -> {
// the timer does not use any graphics objects, etc, but rather manipulates our painting-related data
currentColorIndex = (currentColorIndex + 1) % allColors.length;
// whenever the painting-related data has changed we need to call repaint() on our custom JPanel implementation
myCustomPanel.repaint();
});
timer.setRepeats(true);
timer.start();
frame.setVisible(true);
}

How to stop JComponent.setLocation() from causing a repaint?

I'm trying to achieve camera shake for my game by randomly setting the location of the JPanel which everything in the game is drawn on. After a bit of experimentation, I am certain that JPanel.setLocation(Point p) triggers a repaint, which I don't want to happen.
So the way I create screen shake is by specifying the intensity and the frames it should last. However, the effect always wore off far too quickly, so I did some experimentation. I found that the paintComponent(Graphics g) method of the JPanel was triggered multiple times within one frame, but only while there was screen shake (how really does not add much to the point).
This is how the effect is generated:
public void display(){
framesAlive++; //<-- used to track when the effect has worn off
int intensityX = (int) (Math.random() * vals[0] - vals[0] / 2);
int intensityY = (int) (Math.random() * vals[0] - vals[0] / 2);
pane.setLocation(new Point(intensityX, intensityY));
}
And this is the simplified version of the paintComponent method:
#Override
protected void paintComponent(Graphics g){
super.paintComponent(g);
for (int i = 0; i < stockEffects.size(); i++) {
stockEffects.get(i).display(g);
}
}
Again, my guess is that setLocation() causes a repaint, which basically results in an infinite loop in which the paintComponent() method triggers the display() function, which triggers setLocation(), which triggers a repaint that starts the whole cycle again. This results in the framesAlive variable being incremented multiple times per frame, which throws the whole timing system off. Is there an elegant way to solve this?
you can use AffineTransform. this don't have to change objects real location.
it just change how to draw.
you can shake, rotate, flip, scale etc....
public static void main (String[] arg) {
MainFrame mainFrame = new MainFrame();
mainFrame.setVisible(true);
}
public static class MainFrame extends JFrame{
public MainFrame() {
this.setSize(600,600);
this.setLocationRelativeTo(null);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
MainPanel mainPanel = new MainPanel();
this.add(mainPanel);
}
}
public static class MainPanel extends JPanel{
public void paint(Graphics g) {
super.paint(g);
// Panel Size = 400 X 400
g.drawLine(200, 0, 200, 400); // Y Axis
g.drawLine(0, 200, 400, 200); // X Axis
// Create Transform
AffineTransform at = new AffineTransform();
at.translate(200, 200); // Move Center Form (0, 0) To JPanel Center (200, 200)
// Change Transform
at.translate(-200, 0); // Move Center
// Set Transform To Graphics2D
Graphics2D g2d = (Graphics2D) g;
g2d.setTransform(at);
// Draw Rectangle By Graphics2D
g2d.fillRect(100, 100, 100, 100);
}
}

How do I call a paintcomponent method?

Hi I have made an actionlistener and I want to call a paintComponent method when you click the button?
I have googled it but with no luck.
Here is the actionlisetener,
graf.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
graf();
}
And here is the method,
public static void paintComponent (Graphics g) {
int width = Integer.parseInt(xinwindow.getText());
int hight = Integer.parseInt(yinwindow.getText());
g.setColor(Color.black);
g.drawLine((width/2)- 1, 0, (width/2) +1 , hight);
}
How to call it?
Any help would be appreciated.
Override the paintComponent method of a JComponent object you want to paint.
JComponent c = new JComponent() {
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int width = Integer.parseInt(xinwindow.getText());
int hight = Integer.parseInt(yinwindow.getText());
g.setColor(Color.black);
g.drawLine((width/2)- 1, 0, (width/2) +1 , hight);
}
}
And add
c.revalidate();
c.repaint();
after handling the click in actionPerformed.
You should start with some examples to construct a GUI.
static is for a global, a once occuring instance; one per class. Try to remove all, just the program's entry point:
public static void main(String args) {
JFrame appWindow = new MyFrame();
SwingUtilities.invokeLater(() -> appWindow->setVisible(true));
}
public class MyFrame extends JFrame {
private MyPanel panel;
public MyFrame() {
panel = new MyPanel();
add(panel);
panel.addClickListener(evt -> panel.repaint(50L));
}
}
public class MyPanel extends JPanel {
#Override
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.RED);
g2.drawRectangle(40, 40, getWidth() - 80, getHeight() - 80);
}
}
The mechanism is as follows:
A click is handled on the event dispatching thread of swing.
Here it says to repaint the panel in 50 ms.
A bit later repaintComponent is called from the swing framework in the conjunction of erasing the background and painting the child components.
In paintComponent the Graphics parameter for all newer java versions actually is a Graphics2D which has many nice methods.

repaint() doesn't invoke paintComponent()

I'm trying to make a pong game in Java but it doesn't work.
I've done some testing and it seems that the variables are updating but that when I do
repaint(); in the timers actionPerformed(ActionEvent e) doesn't call the paintComponent() method
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class PongGame extends JComponent implements ActionListener, MouseMotionListener{
public int state = 1;
public int paddleX;
public String buttonColor = "blue";
public int mouseX, mouseY;
private int ballX = 400;
private int ballY = 150;
public static void main(String[] args){
JFrame window = new JFrame("Pong");
PongGame game = new PongGame();
window.add(new PongGame());
window.pack();
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setLocationRelativeTo(null);
window.setResizable(false);
window.setVisible(true);
Timer t = new Timer(20, game);
t.start();
}
public Dimension getPreferredSize(){
return new Dimension(800, 600);
}
public void paintComponent(Graphics g){
paddleX = mouseX;
g.setColor(Color.WHITE);
g.fillRect(0,0, 800, 600);
g.setColor(Color.BLACK);
g.fillRect(paddleX, 550, 150, 15);
g.fillOval(ballX, ballY, 30, 30);
}
#Override
public void actionPerformed(ActionEvent e) {
ballX = ballX + 10;
ballY = ballY + 10;
System.out.println(ballX + " " + ballY);
}
#Override
public void mouseDragged(MouseEvent e) {
}
#Override
public void mouseMoved(MouseEvent e) {
mouseX = e.getX();
repaint();
}
}
you haven't registered the implemented MouseMotionListener to any component:
game.addMouseMotionListener(game);
you are not adding your first created instance of PongGame to the frame rather added a new one producing bug:
PongGame game = new PongGame();
window.add(new PongGame()); // <<--- why creating the new instance ?
// it should be window.add(game);
As a good programming practice: try putting the add listener code in the component's own creating context i.e., in their constructor to make your code better readable.
The problem is:
PongGame game = new PongGame();
window.add(new PongGame());
You have two instances of PongGame. One added to the frame (new PongGame()) and the other (game) that actually reacts to the timer. Change this row to:
window.add(game);
To correct the actual problem. Add a constructor (tested locally):
PongGame() {
addMouseMotionListener(this);
}
repaint() does not invoke paint() directly. It schedules a call to an intermediate method, update(). Finally, update() calls paint() (unless you override update).
The reason for this complexity is Java's support for concurrent programming. It does this using threads.
Using repaint() can be tricky for at least three reasons.
the interaction between repaint() and the spontaneous painting done by the GUI thread
the fact that repaint() just asks the thread system to schedule a call to update()/paint() and then exits. The repaint() method is asynchronous.
the problem of preventing your drawing from being erased when being updated.
I suggest you to try the same with update().
Useful link : http://www.scs.ryerson.ca/~mes/courses/cps530/programs/threads/Repaint/index.html

Jframe and Jpanel size won't work

I have a class Skeleton that makes a Surface and sets the size to 400x400
public class Skeleton extends JFrame {
public Skeleton()
{
initUI();
}
private void initUI()
{
setTitle("");
int height = 400;
int width = 400;
add(new Surface());
setPreferredSize(new Dimension(width + getInsets().left + getInsets().right,
height + getInsets().top + getInsets().bottom));
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack();
setLocationRelativeTo(null);
setVisible(true);
//setResizable(false);
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run() {
Skeleton sk = new Skeleton();
sk.setVisible(true);
}
});
}
}
Then in the surface class I draw a line from (0,0) to (400,400) and when I run the code the bottom end of the diagonal ends off the panel.
class Surface extends JPanel
{
private void makediag(Graphics g, int size)
{
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.blue);
g2d.drawLine(0, 0, size, size);
}
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
makediag(g, 400);
}
}
What am I doing wrong? Is the size of the panel wrong or are the drawing coordinates different?
The size of JPanel is wrong because you are setting preferred size for your JFrame.
Best way would be to override JPanels getPreferredSize method and to return your desired dimension.
protected Dimension getPreferredSize() {
return new Dimension(400, 400);
}
Also, be sure just to call pack for your JFrame. Don't call setXXXSize at all.
According to the javadoc for pack:
Causes this Window to be sized to fit the preferred size and layouts
of its subcomponents.
Therefore the window is supposed to ignore its own preferred size and use the preferred size of its subcomponents. There's no reason to expect setPreferredSize to influence the size of the frame. It seems what you really want to do is to set the preferred size of the Surface to (400,400). That also saves you from having to work with insets.

Categories