Okay. I was writing my Ball class and the ball is not displayed. I tried adding other components to my container and they are displayed, so I think it is safe to assume that the problem is my ball. The class code:
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JComponent;
public class Ball extends JComponent {
public Ball() {
ballX = (Window.WINDOW_WIDTH - BALL_DIAMETER) / 2;
ballY = (Window.WINDOW_HEIGHT - BALL_DIAMETER) / 2;
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawRect(5, 5, 50, 50);
g.setColor(Color.GREEN);
g.fillOval(ballX, ballY, BALL_DIAMETER, BALL_DIAMETER);
g.dispose();
}
public void setX(int x) {
ballX = x;
}
public void setY(int y) {
ballY = y;
}
private int ballX;
private int ballY;
public static final int BALL_DIAMETER = 30;
}
The first rect was used for testing. It doesn't appear neither....
Make sure your component has a preferred size larger than (0, 0):
#Override
public Dimension getPreferredSize() {
return new Dimension(500, 500);
}
Not seeing the code that adds the ball to a container, it's a little hard to answer your question.
However, there are a few problems with your code:
You're setting the location of the ball in the constructor, using some constants - Assuming you want to paint the ball in the middle of the component, you should calculate the location in the paintComponent method by calling getSize and doing the math on the spot
You dispose of the Graphics object at the end of the paintComponent method even though you didn't create it, don't do that, remove that method call (that's probably the culprit)
You're not keeping the state of the Graphics object. Either create a copy (using the create method of the Graphics object) and disposing of it at the end, or by restoring the state you changed at the end of the method (the Color that was set before you changed it to Color.GREEN)
Related
I teach a course in Java and am encountering the following problem. The project this issue is occurring on is a "paint" application. Relevant code at the bottom:
For most students, calling Graphics.fillRect and passing in a negative height and width will result in a rectangle NOT showing up on the screen. This is EXPECTED behavior based on java's documentation and the behavior I've seen across hundreds of students' machines for the past 4 years. In other words, without additional code, you can only draw a rectangle by clicking and dragging from top left to bottom right.
Suddenly, I'm seeing a few students who are passing in negative height and width into Graphics.fillRect and the rectangle draws on the screen correctly (as if a positive height and width were passed in). In other words, they can draw a rectangle by clicking and dragging from any direction on the screen without adding code to correctly calculate the top left-most corner and then getting the height and width based on that.
This is happening with people using different IDEs (Eclipse and IntelliJ). I've checked the following:
Using Oracle JDK
Using JDK 10.0.2 (also attempted to update my own JDK to 14.0.1 in case there was some compat level issue but was unable to reproduce)
(In IntelliJ) Project structure > SDK and language level are correct (10)
(In IntelliJ) Project structure > Platform SDK pointing to correct JDK version (10.0.2)
(In IntelliJ) Run/Debug configuration point to the correct JRE version (10.0.2)
Debugging to make sure the height and width were indeed being passed in as negative numbers
One student completely uninstalled and reinstalled everything and is getting the same issue.
This is the relevant code (keep in mind this is a course for learning design patterns taught by a .NET dev, I know the code isn't perfect):
public class MyMouseListener extends MouseAdapter {
private Point startPoint;
private PaintCanvasBase paintCanvas;
public MyMouseListener(PaintCanvasBase paintCanvas){
this.paintCanvas = paintCanvas;
}
#Override
public void mousePressed(MouseEvent e){
startPoint = new Point(e.getX(), e.getY());
}
#Override
public void mouseReleased(MouseEvent e){
// Filled in rectangle
Graphics2D graphics2d = paintCanvas.getGraphics2D();
graphics2d.setColor(Color.GREEN);
int width = e.getX() - startPoint.getX();
int height = e.getY() - startPoint.getY();
graphics2d.fillRect(startPoint.getX(), startPoint.getY(), width, height);
}
}
public class Point {
private final int x;
private final int y;
public Point(int x, int y){
this.x = x;
this.y = y;
}
public int getX(){
return x;
}
public int getY(){
return y;
}
}
public class PaintCanvas extends PaintCanvasBase {
public Graphics2D getGraphics2D() {
return (Graphics2D)getGraphics();
}
}
public abstract class PaintCanvasBase extends JComponent {
public abstract Graphics2D getGraphics2D();
}
// Following lines in the main method:
// PaintCanvasBase paintCanvas = new PaintCanvas();
// paintCanvas.addMouseListener(new MyMouseListener(paintCanvas));
What is the reason for this behaviour?
I created a simple, runnable example of a Java Swing application that draws a rectangle based on a start point and an end point.
I compiled this application to a Java 8 standard and ran it on a Java 13.0.2 JDK. I'm using Windows 10. The rectangle drew correctly no matter which direction I drew the rectangle. I know that's not helpful to you, but I wanted to discuss some things I found in your code snippet.
All Swing painting must be done in the paintComponent method of a JPanel. The MouseAdapter performs the Rectangle calculations. The paintComponent method paints the Rectangle.
Here's my example code.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class SimpleDrawingArea implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new SimpleDrawingArea());
}
private Rectangle rectangle;
#Override
public void run() {
JFrame frame = new JFrame("Simple Drawing Panel");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new DrawingPanel());
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public class DrawingPanel extends JPanel {
private static final long serialVersionUID = 1L;
public DrawingPanel() {
this.setPreferredSize(new Dimension(400, 300));
this.addMouseListener(new MyMouseListener(this));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
if (rectangle != null) {
g2d.setColor(Color.GREEN);
g2d.fillRect(rectangle.x, rectangle.y,
rectangle.width, rectangle.height);
}
}
}
public class MyMouseListener extends MouseAdapter {
private DrawingPanel drawingPanel;
private Point startPoint;
public MyMouseListener(DrawingPanel drawingPanel) {
this.drawingPanel = drawingPanel;
}
#Override
public void mousePressed(MouseEvent e){
startPoint = e.getPoint();
}
#Override
public void mouseReleased(MouseEvent e){
Point endPoint = e.getPoint();
int width = endPoint.x - startPoint.x;
int height = endPoint.y - startPoint.y;
rectangle = new Rectangle(startPoint.x, startPoint.y,
width, height);
drawingPanel.repaint();
}
}
}
as part of a school project we have to create a little game using Applets. I'm working on some tests right now but there's one thing I can't quite figure out:
I want to have multiple objects flying on my screen at the same time on my Applet screen. The animation effect is created by drawing the object, deleting it then moving it after a while.
Here's my code:
Robotworld class
package core;
import items.Obstacle;
import java.applet.Applet;
import java.awt.*;
import java.util.ArrayList;
public class Roboterwelt extends Applet {
private ArrayList<Obstacle> obstacles = new ArrayList<>();
#Override
public void init() {
setSize(600, 600);
Graphics g = getGraphics();
g.setColor(Color.BLACK);
for(int x = 0; x < 5; x++) {
Obstacle h = new Obstacle((x+1)*100, 100, g, this);
obstacles.add(h);
Thread t = new Thread(h);
t.start();
}
}
#Override
public void paint(Graphics g) {
for(Obstacle o : obstacles) {
o.draw();
}
}
}
Obstacle class
package items;
import java.applet.Applet;
import java.awt.*;
public class Obstacle implements Runnable {
private int x;
private int y;
private Graphics g;
public Hindernis(int x, int y, Graphics g) {
this.x = x;
this.y = y;
this.g = g;
}
public void draw() {
g.drawOval(x, y, 50, 50); //Draw obstacle
}
//Deleting the obstacle by covering it with a white circle
public void delete() {
g.setColor(Color.WHITE); //Change the color to white
g.fillOval(x-5,y-5,60,60); //Making it a bit bigger than the obstacle to fully cover it
g.setColor(Color.BLACK); //Reset the color to black
}
#Override
public void run() {
try {
while(y < 600) {
delete();
y += 10;
draw();
Thread.sleep(1000);
}
} catch(InterruptedException e) {
e.printStackTrace();
}
}
}
The problem is the part where I change the color of the Graphics object to cover the circle in white. When I have multiple threads running to represent the multiple obstacles on my screen and redrawing AND deleting happens concurrently, a thread gets interrupted after changing the color to white and draws a filled oval with the Graphics object which color was set to black by another thread that ran the delete() method to the end.
How can I force the program to not interrupt the delete() method between the color change to white and the drawing of the filled oval shape?
Disclaimer
Applet is deprecated, it is no longer supported by browsers, Oracle or the community. It would be unprofessional of me to try and encourage you to keep using them.
I appreciate that this is a "school" assignment, but perhaps it's time your instructor caught up with the rest of the world and started using something which doesn't actual cause more issues then it solves (hint JavaFX) - IMHO
Answer...
Don't use getGraphics, this is not how custom painting should be done. Painting should be done within the confines of the paint methods. Take a look at Painting in AWT and Swing for details. Apart from solving your immediate issue, your current approach risks been "wiped" clean when the applet repaints itself.
Overriding paint of the top level containers like Applet is a bad idea. Apart from locking you into a single use case, they aren't double buffered, which will cause flickering when painting occurs. The simplest solution is to start with a JPanel, which is double buffered and which can be added to what ever container you want to use.
You don't need multiple threads. Thread is a bit of an art form. More threads doesn't always mean more work gets done and can actually degrade the performance of the system. In your case you want to "update" the state in a single pass and then schedule a paint pass, so that the operations are synchronised in a single step and you don't end up with "dirty" updates
The following example simple makes use of Swing, which is based on AWT. It uses a JFrame instead of an Applet, but the concept is easily transferable, because the core functionality is based on a JPanel, so you can add it to what ever you want.
It makes use of a Swing Timer, which basically schedules a callback on a regular bases, but does it in away which makes it safe to update the state of the UI from (this replaces your Thread).
By using paintComponent to paint the Obstacles, we get two things for free.
Double buffering, so no more flickering
The Graphics context is automatically prepared for us, we don't need to "delete" the objects first, we simply paint the current state
The example also removes the Obstacle once it passes the edge of the panel, so you don't waste time trying to move/paint it when it's no longer visible.
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 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 TestPane extends JPanel {
private List<Obstacle> obstacles;
public TestPane() {
Color[] colors = new Color[]{Color.RED, Color.GREEN, Color.BLUE, Color.MAGENTA, Color.YELLOW};
obstacles = new ArrayList<>(10);
int y = 0;
for (int index = 0; index < 5; index++) {
y += 55;
Obstacle obstacle = new Obstacle(y, 0, colors[index]);
obstacles.add(obstacle);
}
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
Iterator<Obstacle> it = obstacles.iterator();
while (it.hasNext()) {
Obstacle ob = it.next();
if (ob.move(getSize())) {
it.remove();
}
}
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
Iterator<Obstacle> it = obstacles.iterator();
while (it.hasNext()) {
Obstacle ob = it.next();
ob.paint(g2d);
}
g2d.dispose();
}
}
public class Obstacle {
private int x, y;
private Color color;
public Obstacle(int x, int y, Color color) {
this.x = x;
this.y = y;
this.color = color;
}
public void paint(Graphics2D g2d) {
g2d.setColor(color);
g2d.fillRect(x, y, 50, 50);
}
public boolean move(Dimension size) {
y += 1;
return y > size.height;
}
}
}
But all the Obstacles move at the same rate!
Yeah, that's because you used a single delta. If you want the Obstacles to move at different rates, then change the deltas, for example...
public static class Obstacle {
private static Random RND = new Random();
private int x, y;
private Color color;
private int yDelta;
public Obstacle(int x, int y, Color color) {
this.x = x;
this.y = y;
this.color = color;
yDelta = RND.nextInt(5) + 1;
}
public void paint(Graphics2D g2d) {
g2d.setColor(color);
g2d.fillRect(x, y, 50, 50);
}
public boolean move(Dimension size) {
y += yDelta;
return y > size.height;
}
}
I'm not sure why but in my paintComponent method, the fillOval function is not letting me pass in my other classes coordinates. It comes up with :
'Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException'
all the rectangles draw fine but just not the oval. Here is the paintComponent() method.
public void paintComponent(Graphics graphics){
graphics.setColor(Color.BLACK);
graphics.fillRect(0,0,600,450);
graphics.setColor(Color.WHITE);
graphics.fillRect(290,0,15,450);
graphics.fillRect(leftPaddle.getXPos(),leftPaddle.getYPos(),10,85);
graphics.fillRect(rightPaddle.getXPos(),rightPaddle.getYPos(),10,85);
graphics.fillOval(ball.getxPos(),ball.getyPos(),ball.getWidth(),ball.getHeight());
}
And here is my Ball class (the class which bears the coordinates).
public class Ball {
int xPos = 140;
int yPos = 50;
int width = 15;
int height = 15;
public int getWidth() {
return width;
}
public int getxPos() {
return xPos;
}
public int getyPos() {
return yPos;
}
public int getHeight() {
return height;
}
}
This is probably quite an easy fix, but I am relatively new to java, so excuse any formatting mistakes, etc.
Here is where I get the object from the MainClass :
public class PaintComponents extends JPanel{
Paddle leftPaddle;
Paddle rightPaddle;
Ball ball;
public PaintComponents(Paddle leftPaddle, Paddle rightPaddle, Ball ball) {
this.leftPaddle = leftPaddle;
this.rightPaddle = rightPaddle;
this.ball = ball;
}
As its showing NullPointerException
you might not have created the object "ball" of class Ball
try adding:
Ball ball=new Ball();
in public void paintComponent(Graphics graphics)
before using "ball".
I didn't see where did you get the ball. So,
You can instantiate the Ball class like Ball ball = new ball() before the fillOval() happen. Or
You can mark all the fields and methods as static then use Ball.getSth() instead.
I'm new to Java and I want to try some graphic things with it. I want to generate two circles with two different colors and different positions. My code:
Paint Class:
package de.test.pkg;
import javax.swing.*;
public class paint {
public static void main(String[] args) throws Exception{
JFrame frame = new JFrame("Titel");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Circle d = new Circle();
Circle r = new CircleRed();
frame.add(d);
frame.add(r);
frame.setSize(600,200);
frame.setVisible(true);
}
}
Circle class
package de.test.pkg;
import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;
public class Circle extends JPanel {
private double iconRadius = 100;
private Color defaultColor = new Color(89,104,99);
private int positionX = 0;
private int positionY = 0;
private Ellipse2D iconBody = new Ellipse2D.Double(getPositionX(),getPositionY(),iconRadius,iconRadius);
public Icon(){
}
public Color getDefaultColor() {
return defaultColor;
}
public void setDefaultColor(Color defaultColor) {
this.defaultColor = defaultColor;
}
public int getPositionX() {
return positionX;
}
public void setPositionX(int positionX) {
this.positionX = positionX;
}
public int getPositionY() {
return positionY;
}
public void setPositionY(int positionY) {
this.positionY = positionY;
}
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D)g;
super.paintComponent(g2d);
this.setBackground(new Color(255,255,255));
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setPaint(getDefaultColor());
g2d.draw(iconBody);
g2d.fill(iconBody);
}
}
CircleRed class
package de.design.pkg;
import java.awt.Color;
public class CircleRed extends Circle {
private Color defaultColor = Color.RED;
private int positionX = 120;
private int positionY = 0;
public CircleRed() {
}
public Color getDefaultColor() {
return defaultColor;
}
public void setDefaultColor(Color defaultColor) {
this.defaultColor = defaultColor;
}
public int getPositionX() {
return positionX;
}
public void setPositionX(int positionX) {
this.positionX = positionX;
}
public int getPositionY() {
return positionY;
}
public void setPositionY(int positionY) {
this.positionY = positionY;
}
}
The Result is that the Circles have the same positions.
My questions are:
Why do they have the same positions?
Is this a good way to do that or are there better solutions? I want to use a class so please don't gave me answers with do all that paint thing in the Main.
Is there a better way to hold the position. Maybe in an array? But how should the setter and getter look like if I want to return array[position]?
If I want to set the Position from the Main function. How can I do this?
The Result is that the Circles have the same positions.
(1) Why do they have the same positions?
Not really. The result is that only CircleRed is displayed. Your problem here is not with painting, it's with adding components to a container with a suitable layout manager. The lines
Circle d = new Circle();
Circle r = new CircleRed();
frame.add(d);
frame.add(r);
add r instead of d. This is because JFrame uses BorderLayout by default and you are replacing the center component d with r the line after. Just to show the point, add the line
frame.setLayout(new GridLayout(1, 2));
(2) Is this a good way to do that or are there better solutions? I want to use a class so please don't gave me answers with do all that paint thing in the Main.
It depends on what you are aiming to do. I would venture a guess that if you want to practice inheritance, it would be better for you to create an abstract class for a general circle with shared code and then subclass it with concrete implementation and specific code for the various types. Otherwise, you can just create a single customizable circle class and instantiate that one with different parameters.
It's not a good practical approach in any case because the placement of the circles (which are JPanels) will be determined by the layout manager. The painting only determines the location of the painted shape in the panel. It would be better to just paint the shapes on a single big panel and not with using multiple panels.
There are a few very good answers on the site about moving components around.
(3) Is there a better way to hold the position. Maybe in an array? But how should the setter and getter look like if i want to return array[position]?
There are effectively 2 positions in your design. One is for the panels in the frame, the other is for the shapes in the panels.
For the latter, I would use a Point or just an int x, y fields in the class itself. Getters and setters are the standard ones, the setters will control the position (you will need to call repaint() though).
For the first, it is determined by the layout manager and you don't (shouldn't) control it in a pixel-prefect way. You just instruct the layout manager with "guidelines" and it does the calculations for you.
(4) If I want to set the Position from the Main function. How can i do this?
Again, depends on which position you are talking about. See above.
What your doing is very overkill for just creating two colored circles. You can just use the paint method in java.awt
public void paint(Graphics g){
g.setColor(Color.YELLOW);
g.fillOval(20,20,160,160);
g.setColor(Color.RED);
g.fillOval(60,60,80,80);
}
fillOval takes the following parameters (int x, int y, int width, int height)
You can use g.setColor(Color.NAME) to change the color of your circle. Just call this method before your draw calls.
They're in the same position because the paintComponent() method in Circle is being used for both - and it's using the position variables defined in Circle. When you have CircleRed extend Circle, you don't need to define the position variables again - you inherit them from Circle. Instead, call the setPosition() methods on CircleRed. (You don't need to define these either, they're also inherited.)
There are a variety better solutions, I think the biggest improvement would be to improve your use of inheritance.
That's a fine way to hold the position. If anything else, you could also use a Point object. (Already in Java)
To set the position from the main function, you just call for example
Circle c = new Circle();
c.setPositionX(120);
c.setPositionY(40);
You are going wrong way. You can simply draw two circles by overriding paint method of JFrame class.
Here is a sample demo program.
import java.awt.*;
/**
*
* #author Pankaj
*/
public class TestFrame extends javax.swing.JFrame {
/**
* Creates new form NewJFrame
*/
public TestFrame() {
initComponents();
setTitle("Graphics Demo");
setSize(200,200);
}
/**
* You need to override this method.
*/
#Override
public void paint(Graphics g) {
int X1 = 10; //X coordinate of first circle
int X2 = 60; //X coordinate of second circle
int Y1 = 100; //Y coordinate of first circle
int Y2 = 100; //Y coordinate of second circle
int width = 50; //Width of the circle
int height = 50; //Height of the circle
Color color1 = Color.RED; //Color of first circle
Color color2 = Color.BLUE; //Color of second circle
g.setColor(color1);
g.fillOval(X1, Y1, width, height);
g.setColor(color2);
g.fillOval(X2, Y2, width, height);
}
private void initComponents() {
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
setResizable(false);
pack();
}
public static void main(String args[]) {
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new TestFrame().setVisible(true);
}
});
}
}
And here's the output
I have a simple applet that animates a rectangle along the x-axis of the canvas. The problem is that it flickers. I have tried to Google this problem, but I didn't come up with anything useful or anything that I understood.
I am relatively new to Java.
Thanks!
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.*;
import javax.swing.*;
public class simpleAnimation extends JApplet implements ActionListener {
Timer tm = new Timer(10, this);
int x = 0, velX = 2;
public void actionPerformed(ActionEvent event) {
if (x < 0 || x > 550){
velX = -velX;
}
x = x + velX;
repaint();
}
public void paint ( Graphics g ) {
super.paint(g);
g.setColor(Color.RED);
g.fillRect(x, 30, 50, 30);
tm.start();
}
}
**********UPDATED CODE WITHOUT FLICKER**********
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;
public class simpleAnimation extends JApplet implements ActionListener
{
Graphics bufferGraphics;
Image offscreen;
Dimension dim;
int x = 3, velX = 2;
Timer tm = new Timer(10, this);
public void init()
{
dim = getSize();
offscreen = createImage(dim.width,dim.height);
bufferGraphics = offscreen.getGraphics();
}
public void paint(Graphics g)
{
bufferGraphics.clearRect(0,0,dim.width,dim.width);
bufferGraphics.setColor(Color.red);
bufferGraphics.fillRect(x,50,50,20);
g.drawImage(offscreen,0,0,this);
tm.start();
}
public void update(Graphics g)
{
paint(g);
}
public void actionPerformed(ActionEvent evt)
{
if ( x < 0 || x > 550){
velX = -velX;
}
x = x + velX;
repaint();
}
}
I used this applet as a template.
I always struggle with this concept of double buffering.
Here is my example that overrides paint() of the JApplet and a paintComponent() of a JPanel, which uses double buffering by default.
I don't see any difference in the apparent flickering.
//<applet code="SimpleAnimation.class" width="600" height="300"></applet>
import java.awt.*;
import java.awt.Graphics;
import java.awt.event.*;
import javax.swing.*;
public class SimpleAnimation extends JApplet implements ActionListener {
Timer tm = new Timer(10, this);
int x = 0, velX = 2;
JPanel panel;
public void init()
{
panel = new JPanel()
{
#Override
public Dimension getPreferredSize()
{
return new Dimension(50, 100);
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
g.setColor(Color.RED);
g.fillRect(x, 30, 50, 30);
}
};
add(panel, BorderLayout.SOUTH);
tm.start();
}
public void actionPerformed(ActionEvent event) {
if (x < 0 || x > 550){
velX = -velX;
}
x = x + velX;
repaint();
// panel.repaint();
}
public void paint ( Graphics g ) {
super.paint(g);
g.setColor(Color.RED);
g.fillRect(x, 30, 50, 30);
}
}
I test the code using: appletviewer SimpleAnimation.java
Is my concept of double buffering flawed, or my implementation, or both?
The problem is, top level containers like JApplet aren't double buffered. This means that when it's updated, the screen flickers as each action is done directly onto the screen.
Instead, you should create a custom component, using something like a JPanel, and override its paintComponent method and perform your custom painting actions there.
Because Swing components are double buffered the results of the paint action are buffered before they are painted to the screen, making it a single action
Take a look at Performing Custom Painting for more details
What you're doing now works like this:
public void paint ( Graphics g ) {
// draw the entire area white
super.paint(g);
g.setColor(Color.RED);
// draw a rectangle at the new position
g.fillRect(x, 30, 50, 30);
}
So with every step, you first wipe out your rectangle, and then draw it fresh. Thus the flickering - the pixels under the rectangle keep changing from white to red to white to red to white to red...
Now observe that the smallest amount of painting you need to do is (supposing rectangle moves to the right) this:
draw velx pixels on the left WHITE
draw velx pixes on the right RED
If you do that, your animation will be smooth.
Computing that can be quite challenging though, especially when your shape is more complicated than just a square / your movement is more complex. That's where double buffering comes in.
With double buffering, you create an in-memory image that is the same size as your screen. You paint your entire theme there. Then you paint that image on your screen all at once.
When doing that, there won't be an intermediate step of "entire screen is WHITE"; thus no flickering. But note that you end up re-painting the entire screen rather than just the area that changed, which isn't ideal. Consider using clipping - a technique where you repaint a pre-defined area of the image and not the entire thing.