I'm working with JFrame, and I have a while loop. Inside that while loop I change the background of the frame to black then white, and have it do it again. However, I need it to pause for a second or two in between changing so you can actually see it. Thread.sleep(), and Timer don't seem to work. Can anyone help?
If you want to use a timer from swing this is the proper way to do it:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.Timer;
public class Animation extends JFrame implements ActionListener {
private Timer t;
private Color myColor;
private int howManyTimesIwantThis;
private int count = 0;
public Animation() {
t = new Timer(1000, this); // actionPerformed will be called every 1 sec
t.start();
this.howManyTimesIwantThis = 10;
this.setVisible(true);
this.setSize(500, 500);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLocationRelativeTo(null);
myColor = Color.blue;
}
public void actionPerformed(ActionEvent e) {
if (count < howManyTimesIwantThis) {
count++;
if (myColor.equals(Color.blue)) {
myColor = Color.red;
} else {
myColor = Color.blue;
}
repaint(); //calls the paint method
}
}
public void paint(Graphics g) {
super.paint(g);
g.setColor(myColor);
g.fillRect(0, 0, this.getWidth(), this.getHeight());
g.dispose();
}
}
And if you want to use Thread.sleep(), you can do something like this:
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JFrame;
public class Animation extends JFrame{
private Color myColor;
private int howManyTimesIwantThis;
private int count = 0;
public Animation() {
this.howManyTimesIwantThis = 10;
this.setVisible(true);
this.setSize(500, 500);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLocationRelativeTo(null);
myColor = Color.blue;
}
public void paint(Graphics g) {
super.paint(g);
while (count < howManyTimesIwantThis) {
count++;
if (myColor.equals(Color.blue)) {
myColor = Color.red;
} else {
myColor = Color.blue;
}
g.setColor(myColor);
g.fillRect(0, 0, this.getWidth(), this.getHeight());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
g.dispose();
}
}
If you have any questions about the code please feel free to ask.
Related
I try to do my first graphical program in java - bouncing ball. Actually it should be brick breaker according to tutorial. But for now it is bouncing ball. The ball should bounce from the frames of the window. So far I programmed it. The issue is that the ball is laggy, it does not move fluently at all. If I press and hold a keyboard key the ball moves fluently. Firstly I though it might by problem with my graphical card and linux system(AMD Ryzen 5 integrated graphic, Ubuntu 20.04)? But why it works correctly when I press and hold a keyboard button. Any ideas?
package com.company;
import javax.swing.JFrame;
public class Main {
public static void main(String[] args) {
JFrame obj=new JFrame();
Gameplay gamePlay = new Gameplay();
obj.setBounds(10,10,700,600);
obj.setTitle("Breakout Ball");
obj.setResizable(false);
obj.setVisible(true);
obj.setDefaultCloseOperation(obj.EXIT_ON_CLOSE);
obj.add(gamePlay);
}
}
package com.company;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class Gameplay extends JPanel implements ActionListener {
private boolean play=false;
private int totalBrics=21;
private Timer timer;
private int delay=8;
private int playerX=310;
private int ballposX=120;
private int ballposY=350;
private int ballXdir=-1;
private int ballYdir=-2;
public Gameplay(){
setFocusable(true);
setFocusTraversalKeysEnabled(false);
timer=new Timer(delay, this);
timer.start();
}
public void paint (Graphics g){
//background
g.setColor(Color.black);
g.fillRect(1,1,692,592);
//borders
g.setColor(Color.yellow);
g.fillRect(0,0,3,592);
g.fillRect(0,0,692,3);
g.fillRect(691,0,3,592);
// paddle
g.setColor(Color.green);
g.fillRect(playerX, 550,100,8);
//ball
g.setColor(Color.yellow);
g.fillOval(ballposX, ballposY,20,20);
//g.dispose();
}
public void actionPerformed(ActionEvent f) {
//timer.start();
play=true;
if (play){
if(new Rectangle(ballposX, ballposY, 20,20).intersects(new Rectangle(playerX,550,100,8))){
ballYdir=-ballYdir;
}
ballposX+=ballXdir;
ballposY+=ballYdir;
if(ballposX<0){
ballXdir=-ballXdir;
}
if(ballposY<0){
ballYdir=-ballYdir;
}
if(ballposY>570){
ballYdir=-ballYdir;
}
if(ballposX>670){
ballXdir=-ballXdir;
}
}
repaint();
}
}
I ran your exact code and it worked fine. There was no real lag or some better behavior while pressing a key. Hence there is a problem with you hardware maybe?
I did not notice any lag but you were doing several things incorrectly which I have fixed.
You should be overriding paintComponent and not paint for a JPanel
You should invoke super.paintComponent as the first statement. When you do that it will then set the background color for you and clear the screen each time. It also does some other processing in the parent paintComponent method.
I also set the size for the JPanel which made the yellow borders appear (although a little off on the right). And the paddle was raised off the bottom edge a tad.
I then packed the frame and centered it on the screen.
I used RenderingHints via Graphics2D to smooth out the edges of the graphics.
All in all you did a pretty nice job. I am not certain why you would be having a delay. You might want to try increasing your x and y increment values and slowing down the timer. But it worked fine for me as is.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
class Gameplay extends JPanel implements ActionListener {
private boolean play = false;
private int totalBrics = 21;
private Timer timer;
private int delay = 8;
private int playerX = 310;
private int ballposX = 120;
private int ballposY = 350;
private int ballXdir = -1;
private int ballYdir = -2;
public static void main(String[] args) {
JFrame obj = new JFrame();
Gameplay gamePlay = new Gameplay();
gamePlay.setPreferredSize(new Dimension(700,600));
obj.setTitle("Breakout Ball");
obj.setResizable(false);
obj.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
obj.add(gamePlay);
gamePlay.setBackground(Color.black);
obj.pack();
// center on screen
obj.setLocationRelativeTo(null);
obj.setVisible(true);
}
public Gameplay() {
setFocusable(true);
setFocusTraversalKeysEnabled(false);
timer = new Timer(delay, this);
timer.start();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
// borders
g2d.setColor(Color.yellow);
g2d.fillRect(0, 0, 3, 592);
g2d.fillRect(0, 0, 692, 3);
g2d.fillRect(691, 0, 3, 592);
// paddle
g2d.setColor(Color.green);
g2d.fillRect(playerX, 550, 100, 8);
// ball
g2d.setColor(Color.yellow);
g2d.fillOval(ballposX, ballposY, 20, 20);
g2d.dispose();
}
public void actionPerformed(ActionEvent f) {
// timer.start();
play = true;
if (play) {
if (new Rectangle(ballposX, ballposY, 20, 20).intersects(
new Rectangle(playerX, 550, 100, 8))) {
ballYdir = -ballYdir;
}
ballposX += ballXdir;
ballposY += ballYdir;
if (ballposX < 0) {
ballXdir = -ballXdir;
}
if (ballposY < 0) {
ballYdir = -ballYdir;
}
if (ballposY > 570) {
ballYdir = -ballYdir;
}
if (ballposX > 670) {
ballXdir = -ballXdir;
}
}
repaint();
}
}
Problem solved,
just found answer here:Java window lagging on Ubuntu but not windows when code isn't lagging
Problem is with Linux's graphics scheduling, adding
Toolkit.getDefaultToolkit().sync();
after
repaint();
caused, that everything works fluently now.
I'm trying to draw over a vlcj (java binding of the VLC library) panel so that I can play a video and draw over it. And I have encounter some issues. Here is the full base code:
Code-listing 1: AppOverlay.java
package app;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.HeadlessException;
import java.awt.Window;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
import com.sun.jna.platform.WindowUtils;
#SuppressWarnings("serial")
public class AppOverlay extends Window implements Runnable {
private final boolean isRunning;
private final int fps;
private BufferedImage graphics;
private BufferedImage img;
private int x, y;
private boolean ltr;
public AppOverlay(Window owner) {
super(owner, WindowUtils.getAlphaCompatibleGraphicsConfiguration());
setBackground(new Color(0,0,0,0));
graphics = new BufferedImage(1280,800, BufferedImage.TYPE_INT_ARGB);
isRunning = true;
img = null;
ltr = true;
fps = 60;
x = 0;
y = 0;
}
#Override
public void run(){
while(isRunning){
try{
Thread.sleep(1000/fps);
} catch(InterruptedException e){
e.printStackTrace();
}
if(ltr) {
if(x < 1280) x++;
else ltr = false;
} else {
if(x < 0) ltr = true;
else x--;
}
repaint();
}
}
public void createAndShowGUI() {
setVisible(true);
Thread thread = new Thread(this);
thread.start();
String path = "Drive:\\path\\to\\image.png";
try {
img = ImageIO.read(new java.io.FileInputStream(path));
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D)g;
Graphics2D gfx = graphics.createGraphics();
gfx.setColor(new Color(255,255,255,0));
gfx.clearRect(0, 0, 1280, 800);
if(img != null) gfx.drawImage(img, x, y, null);
gfx.dispose();
g2d.drawImage(graphics, 0, 0, null);
}
}
Code-listing 2: AppPlayer.java
package app;
import uk.co.caprica.vlcj.player.component.EmbeddedMediaPlayerComponent;
#SuppressWarnings("serial")
public class AppPlayer extends EmbeddedMediaPlayerComponent {
}
Code-listing 3: AppFrame.java
package app;
import java.awt.Dimension;
import javax.swing.JFrame;
#SuppressWarnings("serial")
public class AppFrame extends JFrame {
private AppPlayer appPlayer;
private AppOverlay overlay;
public AppFrame(){
super();
}
public void createAndShowGUI() {
appPlayer = new AppPlayer();
appPlayer.setPreferredSize(new Dimension(1280,800));
getContentPane().add(appPlayer);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setTitle("App");
setVisible(true);
pack();
overlay = new AppOverlay(this);
appPlayer.mediaPlayer().overlay().set(overlay);
appPlayer.mediaPlayer().overlay().enable(true);
overlay.createAndShowGUI();
}
}
Code-listing 4: Main.java
package main;
import javax.swing.SwingUtilities;
import app.AppFrame;
public class Main {
public static void main(String[] args) {
final AppFrame app = new AppFrame();
SwingUtilities.invokeLater( new Runnable() {
#Override
public void run() {
app.createAndShowGUI();
}
});
}
}
with that and the vlcj-4 library you should be able to test my code yourself. My issue is that the Overlay (AppOverlay class that extends the Window class) doesn't display or refresh the animation unless I deselect the window (I click on another window or on the desktop or the OS toolbar) so that the window (application) is inactive then select the window (the application) again. It will only load one frame and that's it. I have to deselect and reselect the window again for it to load another frame (this is only the case for the Overlay i.e. if I play a video in the AppPlayer class the video will be playing just fine.
What I want is to be able to draw some animated graphics on the overlay. I know that with the JPanel class there is the paintComponent() method but the Window class doesn't have that method (only the paint() and repaint() methods are available).
What should I do to fix this?
EDIT:
I tried adding a JPanel on which I draw instead of drawing directly on the AppOverlay
Code-listing 5: AppPanel.java
package app;
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JPanel;
public class AppPanel extends JPanel implements Runnable {
private int x, y;
private boolean ltr;
public AppPanel() {
x = 0;
y = 0;
ltr = true;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(new Color(0,0,0,0));
g.clearRect(0, 0, 1280, 800);
g.setColor(Color.RED);
g.fillRect(x, y, 100, 100);
}
#Override
public void run() {
while(true){
try{
Thread.sleep(1000/60);
} catch(InterruptedException e){
e.printStackTrace();
}
if(ltr) {
if(x < 1280) x++;
else ltr = false;
} else {
if(x < 0) ltr = true;
else x--;
}
repaint();
}
}
}
then adding it to the AppOverlay.
Code-listing 6: AppOverlay.java with partial modification
public class AppOverlay extends Window implements Runnable {
//previous field declaration above ...
AppPanel panel;
AppPlayer player = null;
public AppOverlay(Window owner) {
//previous constructor instructions above...
panel = new AppPanel();
add(panel);
}
public void createAndShowGUI(AppPlayer player) {
setVisible(true);
/*
Thread thread = new Thread(this);
thread.start();
String path = "Drive:\\path\\to\\image.png";
try {
img = ImageIO.read(new java.io.FileInputStream(path));
} catch (IOException e) {
e.printStackTrace();
}
*/
Thread panelThread = new Thread(panel);
panelThread.start();
}
}
Doing this will display the graphics of the JPanel and animate them as needed.
If you know a way to make the JPanel background transparent (so that we can see through it) while still letting it display its graphics. That would solve the issue for sure.
I played around a bit with your example and came up with something working, but I wouldn't call it a nice solution.
The main issue seems to be that there is no way to tell the overlay to refresh (or I just have not found it). Just repainting the overlay does not update it on screen, so the workaround I used is to hide and show it again.
For the timeing of the update interval I used a javax.swing.Timer.
(In a real version you probably want to start and stop the timer via the MediaPlayerEventListener).
As a side effect the repaint method is called and the x coordinate is adjusted to move the image around the screen.
In the simplified example below (use your main to run it), I moved a red rectangle with the x coordinate instead of some unknown image.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.HeadlessException;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.Timer;
import com.sun.jna.platform.WindowUtils;
import uk.co.caprica.vlcj.player.component.EmbeddedMediaPlayerComponent;
import uk.co.caprica.vlcj.player.embedded.OverlayApi;
public class AppFrame extends JFrame {
private static final long serialVersionUID = -1569823648323129877L;
public class Overlay extends Window {
private static final long serialVersionUID = 8337750467830040964L;
private int x, y;
private boolean ltr = true;
public Overlay(Window owner) throws HeadlessException {
super(owner, WindowUtils.getAlphaCompatibleGraphicsConfiguration());
setBackground(new Color(0,0,0,0));
}
#Override
public void paint(Graphics g) {
super.paint(g);
if (ltr) {
if (x < 1180)
x += 1;
else
ltr = false;
} else {
if (x < 0)
ltr = true;
else
x -= 1;
}
g.setColor(Color.RED);
g.fillRect(x, y, 100, 100);
String s = Integer.toString(x);
g.setColor(Color.WHITE);
g.drawChars(s.toCharArray(), 0, s.length(), x+10, y+50);
}
}
private EmbeddedMediaPlayerComponent appPlayer;
public void createAndShowGUI() {
appPlayer = new EmbeddedMediaPlayerComponent();
appPlayer.setPreferredSize(new Dimension(1280, 800));
getContentPane().add(appPlayer);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setTitle("App");
setVisible(true);
pack();
Overlay overlay = new Overlay(this);
OverlayApi api = appPlayer.mediaPlayer().overlay();
api.set(overlay);
api.enable(true);
//appPlayer.mediaPlayer().media().play(" ... ");
Timer timer = new Timer(0, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
api.enable(false);
api.enable(true);
}
});
timer.setRepeats(true);
timer.setDelay(200);
timer.start();
}
}
If that is an option for you, it might be far easier to use an animated gif instead. At least that is working on its own (no need for the Timer).
Update:
As you figured out using a JPanel seems to work better.
Just use setOpaque(false) to make it transparent.
Here an adjusted example.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.HeadlessException;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import uk.co.caprica.vlcj.player.component.EmbeddedMediaPlayerComponent;
import uk.co.caprica.vlcj.player.embedded.OverlayApi;
public class AppFrame2 extends JFrame {
private static final long serialVersionUID = -1569823648323129877L;
public class OverlayPanel extends JPanel {
private static final long serialVersionUID = 8070414617530302145L;
private int x, y;
private boolean ltr = true;
public OverlayPanel() {
this.setOpaque(false);
}
#Override
public void paint(Graphics g) {
super.paint(g);
if (ltr) {
if (x < 1180)
x += 1;
else
ltr = false;
} else {
if (x < 0)
ltr = true;
else
x -= 1;
}
g.setColor(Color.RED);
g.fillRect(x, y, 100, 100);
String s = Integer.toString(x);
g.setColor(Color.WHITE);
g.drawChars(s.toCharArray(), 0, s.length(), x+10, y+50);
}
}
public class Overlay extends Window {
private static final long serialVersionUID = 8337750467830040964L;
OverlayPanel panel;
public Overlay(Window owner) throws HeadlessException {
super(owner);
setBackground(new Color(0,0,0,0));
panel = new OverlayPanel();
this.add(panel);
}
}
private EmbeddedMediaPlayerComponent appPlayer;
public void createAndShowGUI() {
appPlayer = new EmbeddedMediaPlayerComponent();
appPlayer.setPreferredSize(new Dimension(1280, 800));
getContentPane().add(appPlayer);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setTitle("App");
setVisible(true);
pack();
Overlay overlay = new Overlay(this);
OverlayApi api = appPlayer.mediaPlayer().overlay();
api.set(overlay);
api.enable(true);
//appPlayer.mediaPlayer().media().play(" ... ");
Timer timer = new Timer(0, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
overlay.panel.repaint();
}
});
timer.setRepeats(true);
timer.setDelay(17);
timer.start();
}
}
You have already done the bulk of the work. Simply repaint the frame every time you draw over it by calling app.repaint();
You can use the following methods from JComponent: ( http://download.oracle.com/javase/6/docs/api/javax/swing/JComponent.html )
void repaint(long tm, int x, int y, int width, int height)
//**Adds the specified region to the dirty region list if the component is showing.*//
void repaint(Rectangle r)
/**Adds the specified region to the dirty region list if the component is showing.*//
You can call those before redraw()
This is just a simple red ball going up and down and i see it flickering. I already saw few subjects about that but did not find any answer that helped me.
Thank you :)
The Window class with the go method that makes the ball goes up and down.
The panel that also contains the ball positions and that just repaints.
Window.java
import java.awt.Dimension;
import javax.swing.JFrame;
public class Window extends JFrame
{
public static void main(String[] args)
{
new Window();
}
public Panel pan = new Panel();
public Window()
{
this.setSize(600, 600);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLocationRelativeTo(null);
this.setContentPane(pan);
this.setVisible(true);
go();
}
private void go()
{
int vecY = 1;
while (true)
{
if (pan.y <= 100)
{
vecY = 1;
}
else if (pan.y >= 400)
{
vecY = -1;
}
pan.y += vecY;
pan.repaint();
try
{
Thread.sleep(10);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
Panel.java
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JPanel;
public class Panel extends JPanel
{
public int x = 300;
public int y = 300;
public void paintComponent(Graphics g)
{
g.setColor(Color.white);
g.fillRect(0, 0, this.getWidth(), this.getHeight());
g.setColor(Color.red);
g.fillOval(x, y, 50, 50);
}
}
There are a number of possible issues. The primary issue is likely to be a thread race condition between your while-loop and the paintComponent method.
Your while-loop is capable of change the state of the y position before the paintComponent has a chance to paint it's state. Painting is done at the leisure of the paint sub system, so calling repaint simply makes a request to the RepaintManager which decides what and when an actual paint cycle might take place, this means that you could be dropping frames.
For most animations in Swing, a Swing Timer is more the capable. It's safe to update the UI from within, as the ActionListener is called within the context of the EDT but won't block the EDT
For example:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class Window extends JFrame {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Window();
}
});
}
public Panel pan = new Panel();
public Window() {
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setContentPane(pan);
pack();
this.setLocationRelativeTo(null);
this.setVisible(true);
go();
}
private void go() {
Timer timer = new Timer(10, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
pan.updateAnmationState();
}
});
timer.start();
}
public class Panel extends JPanel {
private int x = 300;
private int y = 300;
private int vecY = 1;
public void updateAnmationState() {
if (y <= 100) {
vecY = 1;
} else if (y >= 400) {
vecY = -1;
}
y += vecY;
repaint();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.white);
g.fillRect(0, 0, this.getWidth(), this.getHeight());
g.setColor(Color.red);
g.fillOval(x, y, 50, 50);
}
}
}
This example worked fine for me on MiniMac
While using Swing in java, I am trying to move a circle slowly from a starting position to an end position when clicking a button. However, I can't see the circle moving. It just moves from start to end in an instant.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class MyApp {
private int x = 10;
private int y = 10;
private JFrame f;
private MyDraw m;
private JButton b;
public void go() {
f = new JFrame("Moving circle");
b = new JButton("click me to move circle");
m = new MyDraw();
f.add(BorderLayout.SOUTH, b);
f.add(BorderLayout.CENTER, m);
f.setSize(500, 500);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
b.addActionListener(new Bute());
}
public static void main(String[] args) {
MyApp m = new MyApp();
m.go();
}
private class Bute implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
for (int i = 0; i < 150; i++) {
++x;
++y;
m.repaint();
Thread.sleep(50);
}
}
}
private class MyDraw extends JPanel {
#Override
public void paintComponent(Graphics g) {
g.setColor(Color.white);
g.fillRect(0, 0, 500, 500);
g.setColor(Color.red);
g.fillOval(x, y, 40, 40);
}
}
}
I think the problem is with the action listener because when I'm doing it without using button it is working. Any suggestions?
As Andrew Thompson said, calling Thread.sleep() without defining a second thread freezes everything, so the solution is to define and run another thread like so:
class Bute implements ActionListener, Runnable {
//let class implement Runnable interface
Thread t; // define 2nd thread
public void actionPerformed(ActionEvent e) {
t = new Thread(this); //start a new thread
t.start();
}
#Override //override our thread's run() method to do what we want
public void run() { //this is after some java-internal init stuff called by start()
//b.setEnabled(false);
for (int i = 0; i < 150; i++) {
x++;
y++;
m.repaint();
try {
Thread.sleep(50); //let the 2nd thread sleep
} catch (InterruptedException iEx) {
iEx.printStackTrace();
}
}
//b.setEnabled(true);
}
}
The only problem with this solution is that pressing the button multiple times will speed up the circle, but this can be fixed by making the button unclickable during the animation via b.setEnabled(true/false). Not the best solution but it works.
As said in the comments and another answer, don't block the EDT. Thead.sleep(...) will block it, so you have two options:
Create and manage your own (new) thread.
Use a Swing Timer
In this answer I'll be using a Swing Timer, since it's easier to use. I also changed the paintComponent method to use the Shape API and change the button text to start and stop accordingly as well as reusing the same ActionListener for the button and the timer:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class MovingCircle {
private JFrame frame;
private CustomCircle circle;
private Timer timer;
private JButton button;
public static void main(String[] args) {
SwingUtilities.invokeLater(new MovingCircle()::createAndShowGui);
}
private void createAndShowGui() {
frame = new JFrame(this.getClass().getSimpleName());
circle = new CustomCircle(Color.RED);
timer = new Timer(100, listener);
button = new JButton("Start");
button.addActionListener(listener);
circle.setBackground(Color.WHITE);
frame.add(circle);
frame.add(button, BorderLayout.SOUTH);
frame.pack();
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
private ActionListener listener = (e -> {
if (!timer.isRunning()) {
timer.start();
button.setText("Stop");
} else {
if (e.getSource().equals(button)) {
timer.stop();
button.setText("Start");
}
}
circle.move(1, 1);
});
#SuppressWarnings("serial")
class CustomCircle extends JPanel {
private Color color;
private int circleX;
private int circleY;
public CustomCircle(Color color) {
this.color = color;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(color);
g2d.fill(new Ellipse2D.Double(circleX, circleY, 50, 50));
}
#Override
public Dimension preferredSize() {
return new Dimension(100, 100);
}
public void move(int xGap, int yGap) {
circleX += xGap;
circleY += yGap;
revalidate();
repaint();
}
public int getCircleX() {
return circleX;
}
public void setCircleX(int circleX) {
this.circleX = circleX;
}
public int getCircleY() {
return circleY;
}
public void setCircleY(int circleY) {
this.circleY = circleY;
}
}
}
I'm sorry, I can't post a GIF as I wanted but this example runs as expected.
I have a problem with JApplet. The code was working just fine, but when I converted it from JFrame to JApplet, the render part stopped working properly. Basicly what I'm trying to do is simplistic draw app. When launching applet, half of time repaint() is not working (There is no gray background; you have to put mouse over button for it to update its color etc), furtheremore the pixel rendering part is not shown up at all. Here's the code:
The Frame class (JApplet)
package painter;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import javax.swing.JApplet;
import javax.swing.JButton;
import javax.swing.JColorChooser;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Frame extends JApplet {
public JPanel panel;
private JButton plus, minus, buttonColor;
private int scaleSize;
private JLabel labelScale;
private final Timer updateTimer;
private static boolean painting = false;
public static Color currentColor;
public static int mode = 0;
// 0 = draw; 1 = setcolor; 2 = erase
private ArrayList<Pixel> pixelArray;
public Frame() {
pixelArray = new ArrayList<>();
for (int i = 1; i <= 8; i++) {
for (int j = 1; j <= 8; j++) {
pixelArray.add(new Pixel(i, j));
}
}
setLayout(new BorderLayout());
panel = new JPanel() {
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
//g.fillRect(10, 10, 100, 100); <- test if fillRect works at all. Yus it does.
for (int i = 0; i < pixelArray.size(); i++) {
pixelArray.get(i).render(g);
}
Toolkit.getDefaultToolkit().sync();
g.dispose();
}
};
//panel.setBounds(0, 0, 800, 800);
//add(panel);
getContentPane().add(panel);
//panel.setLayout(null);
//panel.setOpaque(true);
//panel.setDoubleBuffered(true);
currentColor = Color.yellow;
buttonColor = new JButton("Choose color");
buttonColor.setBounds(10, 10, 128, 64);
buttonColor.setBackground(currentColor);
buttonColor.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
currentColor = JColorChooser.showDialog(null, "JColorChooser Sample", Color.gray);
}
});
updateTimer = new Timer(20, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
buttonColor.setBackground(currentColor);
repaint();
}
});
updateTimer.start();
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
painting = true;
}
});
addMouseListener(new MouseAdapter() {
#Override
public void mouseReleased(MouseEvent e) {
painting = false;
}
});
panel.add(buttonColor);
repaint();
}
public static boolean getPaint() {
return painting;
}
public static void main(String[] args) {
new Frame();
}
}
And here is the Pixel class:
package painter;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.MouseInfo;
import java.awt.Point;
public class Pixel {
private Color color;
private int size;
private int x, y, relativex, relativey;
public Pixel(int relx, int rely) {
color = new Color(0x999999, false);
size = 32;
x = relx * size + 64;
y = rely * size + 64;
}
public boolean mouseOver() {
Point pos, mousepos;
pos = new Point(x, y);
mousepos = MouseInfo.getPointerInfo().getLocation();
if ((mousepos.x > pos.x)
&& (mousepos.x < pos.x + size)
&& (mousepos.y > pos.y)
&& (mousepos.y < pos.y + size)) {
return true;
} else {
return false;
}
}
public void render(Graphics g) {
g.setColor(color);
if (mouseOver() && Frame.getPaint()) {
if (Frame.mode == 0) {
color = Frame.currentColor;
}
if (Frame.mode == 1) {
Frame.currentColor = color;
}
if (Frame.mode == 2) {
color = new Color(0xffffffff, true);
}
}
g.fillRect(x, y, size, size);
if (mouseOver()) {
g.setColor(Color.black);
g.drawRect(x, y, size - 1, size - 1);
g.setColor(Color.yellow);
g.drawRect(x + 1, y + 1, size - 3, size - 3);
}
//g.fillRect(10, 10, 250, 250);
}
}
As a stab in the dark, don't call Graphics#dipose on a Graphics context you did not create yourself explicitly
Apart from the fact the the Graphics context is a shared resource, used by all the components that might need to be painted within a given paint cycle, it can also prevent what ever was painted to it to be displayed on some platforms
In 15 years of professional development, I've never had reason to call Toolkit.getDefaultToolkit().sync();. I doubt it'll make that big a difference, I'm just saying
Java applets provide us with these methods
[here] http://docs.oracle.com/javase/tutorial/deployment/applet/appletMethods.html
the method
public void paint(Graphics g){}
is used as alternative of
public void paintComponent(Graphics g){}
of swing.
Check out http://docs.oracle.com/javase/tutorial/uiswing/painting/index.html for the recommended way to perform custom painting of swing components