class DrawPane extends JPanel
{
//size is the size of the square, x and y are position coords
double size = 1, x = 0, y = 0;
double start = (-1) * size;
public void paintComponent(Graphics shape)
{
for(x = start; x <= scWidth; x += size)
{
shape.drawRect((int)x, (int)y , (int)size, (int)size);
//When a row is finished drawing, add another
if(x >= scWidth)
{
x = start; y += size;
}
//Redraws the entire grid; makes the for loop infnite
else if(y >= scHeight)
{
x = start; y = start;
}
}
}
}
I'm confused as to why JPanel refuses to work with the loop once I make it infinite. How would I go about allowing it to do so?
When you make the loop "infinite" you effectively tie up and freeze the Swing event thread preventing Swing from doing anything. Instead use a Swing Timer to drive your animation.
e.g.,
class DrawPane extends JPanel {
//size is the size of the square, x and y are position coords
double size = 1, x = 0, y = 0;
double start = (-1) * size;
public DrawPane() {
int timerDelay = 200;
new Timer(timerDelay, new ActionListener(){
public void actionPerformed(ActionEvent e) {
x += size;
if (x >= scWidth) {
x = start;
y += size;
}
if (y >= scHeight) {
x = start;
y = start;
}
repaint();
}
}).start();
}
public void paintComponent(Graphics g) {
super.paintComponent(g); // Don't forget to call this!
g.drawRect((int)x, (int)y , (int)size, (int)size);
}
}
The paint function is supposed to update the Paint and get out of the way. You really shouldn't be putting in complex logic and definitely not infinite loops there.
Just do what you have (except get rid of the reset stuff that makes your loop infinite) and put repaint() in an infinite loop (preferably with some timer logic) somewhere else in your program.
It will never break out of the paintComponent loop and update the GUI. The GUI will only update once the paintComponent method finishes. If you want to make the loop infinite, you need to take the code out of your event handler and be calling repaint() from elsewhere, possibly using a timer to do so.
Related
nothing is showing up at all..
I have tried moving Random rand = new Random() to outside of the loop, but it still doesnt work at all.
Nor does the frame exit on close.
public class myMain {
public static void main(String args[]) {
Frame frame = new Frame();
}
}
public class Frame extends JFrame {
public Frame(){
super("Fancy Triangle");
setSize(1024, 768);
myPanel panel = new myPanel();
add(panel);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setVisible(true);
}
}
public class myPanel extends JPanel {
int x1 = 512;
int y1 = 109;
int x2 = 146;
int y2 = 654;
int x3 = 876;
int y3 = 654;
int x = 512;
int y = 382;
int dx, dy;
Random rand;
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
for (int i = 0; i < 50000; i++) {
g.drawLine(x, y, x, y);
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
rand = new Random();
int random = 1 + rand.nextInt(3);
if (random == 1) {
dx = x - x1;
dy = y - y1;
} else if (random == 2) {
dx = x - x2;
dy = y - y2;
} else {
dx = x - x3;
dy = y - y3;
}
x = x - (dx / 2);
y = y - (dy / 2);
}
}
}
This:
Thread.sleep(300);
is not doing what you intend it to do. I think that you're trying to draw with a delay, but that's not what this does. Instead you're calling sleep on the Swing event thread puts the whole application to sleep, since the thread cannot do what it needs to do, including drawing the application and interacting with the user. Even worse, you're doing this within a painting method, a method that is required to be extremely fast since often the perceived responsiveness of a Swing application is determined by painting speed.
Instead use a Swing Timer (Swing Timer tutorial) to change the state of fields of the class, and then call repaint. Have your paintComponent use those changed fields to decide what to draw and where. Since a Sierpinski triangle is composed of dots, consider creating an ArrayList<Point>, getting rid of the for loop inside your painting method, and using the Swing Timer to replace this for loop. Within the Timer's ActionListener, place the semi-random points into the ArrayList and call repaint. Then within paintComponent, iterate through the ArrayList, drawing each point that it contains.
Alternatively, you could draw the points onto a BufferedImage in your Swing Timer and then simply have your paintComponent display the BufferedImage via g.drawImage(...) method call. This would likely be more efficient.
I'm trying to make a flip effect with java swing, but my flip method doesn't look like a real flip.In my method I change the width with the x var and the xspeed, making the image smaller and then check if the x is >= as half of my image. hope somebody could help me improve it thanks in advance.
Animation Class
public class Animation extends JPanel implements MouseListener {
private static final long serialVersionUID = 3264508834913061718L;
public Timer timer;
public int x = 0;
public int xspeed = 2;
public boolean turning = true;
public String pic = "/images/image.jpg";
public URL url = this.getClass().getResource(pic);
public ImageIcon im = new ImageIcon(url);
public String rev = "/images/image2.jpg";
public URL url2 = this.getClass().getResource(rev);
public ImageIcon reverse = new ImageIcon(url2);
public Animation(){
this.setPreferredSize(new Dimension(128,128));
this.setBackground(Color.BLACK);
this.setFocusable(true);
this.addMouseListener(this);
}
public void paintComponent(Graphics g){
super.paintComponent(g);
g.drawImage(im.getImage(), 0 , 0, im.getIconWidth() - x, im.getIconHeight(), null);
}
public void flipAnimation(){
ActionListener actionListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
//
if (turning) {
x = x + xspeed;
}
repaint();
// x in the middle paint new image & set turning to false to stop animation
if(x >= im.getIconWidth()/2){
turning = false;
x = 0;
im = reverse;
}
}
};
if (turning) {
if (timer != null)timer.stop();
timer = new Timer(30, actionListener);
timer.start();
}
}
#Override
public void mouseClicked(MouseEvent e) {
// TODO Auto-generated method stub
e.getSource();
flipAnimation();
}
First, I’m guessing you want your image to “fold in” from both sides, so you’ll want to increase the image’s left edge along with its right edge:
g.drawImage(im.getImage(), x / 2 , 0, im.getIconWidth() - x, im.getIconHeight(), this);
Notice the first int argument has been changed from 0 to x / 2. Also, it is good practice to pass this as the ImageObserver argument when drawing images in a paintComponent method, since the component itself is the object which is interested in repainting itself when the image has finished loading in the background.
Second, change this:
if (turning) {
x = x + xspeed;
}
to this:
x = x + xspeed;
You don’t need a flag to control the animation. (The correct way to stop the animation is to call timer.stop(), which I’ll get to in a moment.)
Third, change this:
if(x >= im.getIconWidth()/2){
turning = false;
x = 0;
to this:
if(x >= im.getIconWidth()){
xspeed = -xspeed;
As I said, the turning flag is not needed.
The reason for comparing x to the image’s width, instead of half of the width, is that we want to change the image only when the first image has completely “folded in.”
The negation of xspeed reverses the animation.
Finally, at the end of your actionPerformed method, you’ll want to add this:
if (x <= 0) {
timer.stop();
}
This halts the animation when the reverse image reaches its full size, which is why the turning flag is not needed.
I'm working on a program that displays circles colliding with the wall and with themselves.
I'm having trouble with the method that will compensate for collisions.
public class bouncyFX extends Application {
public ArrayList<Ball> arr = new ArrayList<Ball>();
public static void main(String[] args) {
launch(args);
}
static Pane pane;
#Override
public void start(final Stage primaryStage) {
pane = new Pane();
final Scene scene = new Scene(pane, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
pane.setOnMouseClicked(new EventHandler<MouseEvent>() {
public void handle(final MouseEvent event) {
final Ball ball = new Ball(event.getX(), event.getY(), 40, Color.AQUA);
ball.circle.relocate(event.getX(), event.getY());
pane.getChildren().addAll(ball.circle);
arr.add(ball);
final Bounds bounds = pane.getBoundsInLocal();
final Timeline loop = new Timeline(new KeyFrame(Duration.millis(10), new EventHandler<ActionEvent>() {
double deltaX = ball.ballDeltaX;
double deltaY = ball.ballDeltaY;
public void handle(final ActionEvent event) {
ball.circle.setLayoutX(ball.circle.getLayoutX() + deltaX);
ball.circle.setLayoutY(ball.circle.getLayoutY() + deltaY);
final boolean atRightBorder = ball.circle.getLayoutX() >= (bounds.getMaxX()-ball.circle.getRadius());
final boolean atLeftBorder = ball.circle.getLayoutX() <= (bounds.getMinX()+ball.circle.getRadius());
final boolean atBottomBorder = ball.circle.getLayoutY() >= (bounds.getMaxY()-ball.circle.getRadius());
final boolean atTopBorder = ball.circle.getLayoutY() <= (bounds.getMinY()+ball.circle.getRadius());
if(atRightBorder || atLeftBorder)
deltaX *= -1;
if(atBottomBorder || atTopBorder)
deltaY *= -1;
for(int i = 0; i<arr.size(); i++){
for(int j = i+1; j<arr.size()-1; j++){
arr.get(i).collisionMagnitued(arr.get(j));
}
}
}
}));
loop.setCycleCount(Timeline.INDEFINITE);
loop.play();
}
});
}
class Ball{
public Circle circle;
public double ballDeltaX = 3;
public double ballDeltaY = 3;
public void AddBall(Ball b){
arr.add(b);
}
public Ball(double X, double Y, double Rad, Color color) {
circle = new Circle(X, Y, Rad);
circle.setFill(color);
}
private boolean defineCollision(Ball b){
double xd = this.circle.getLayoutX() - b.circle.getLayoutX();
double yd = this.circle.getLayoutY() - b.circle.getLayoutY();
double sumRad = this.circle.getRadius() + b.circle.getRadius();
double squareRad = Math.pow(sumRad, 2);
double distSquare = Math.pow(xd, 2) + Math.pow(yd, 2);
if(distSquare <= squareRad){
return true;
}return false;
}
public void collisionMagnitued(Ball b){
if(this.defineCollision(b)){
double tempDeltaX = ballDeltaX;
double tempDeltaY = ballDeltaY;
if((this.ballDeltaX < 0 && b.ballDeltaX > 0) || (this.ballDeltaX >0 && b.ballDeltaX <0)){
this.ballDeltaX *= -this.ballDeltaX;
b.ballDeltaX *= -b.ballDeltaX;
System.out.println("tredje");
}
if((this.ballDeltaY < 0 && b.ballDeltaY > 0) || (this.ballDeltaY > 0 && b.ballDeltaY < 0)){
this.ballDeltaY *= -this.ballDeltaY;
b.ballDeltaY *= -b.ballDeltaY;
System.out.println("fjärde");
}
else{
System.out.println("Knull");
this.ballDeltaX *= -1;
b.ballDeltaX *= -1;
}
}
}
}
}
The Balls (or circles) are created and are bouncing against the Bounds as expected.
The Collision detection method works as I'm getting print statements inside the last method. However, it seems that there's something wrong with either my ArrayList not being filled with objects or the method trying to compare the parameter Ball and the Ball that calls the method.
Am I way off? Not sure how I'm suppossed to go forth from here.
I see a few issues with your logic:
The first problem is that when the balls "bounce" off the boundaries of the pane, you don't change their ballDeltaX or ballDeltaY values (you just change a local value in the animation loop and use the local value to update the position). So the first time two balls collide, both of their ballDeltaX and ballDeltaY values are equal to +3 (the initial value), which may not represent the actual direction the animation loop is moving them in. In fact, you never actually use any updated values of ballDeltaX or ballDeltaY to compute the new positions; you get the initial values of those variables, copy them into deltaX and deltaY, and then just compute the new positions using deltaX and deltaY. So if you change ballDeltaX and ballDeltaY, the animation loop never sees the change.
The for loops look wrong to me; I don't think they compare the last two elements of the list. (When i = arr.size()-2 in the penultimate iteration of the outer loop, your inner loop is for (int j = arr.size() - 1; j < arr.size() -1; j++) {...} which of course never iterates.) I think you want the bounding conditions to be i < arr.size() - 1 and j < arr.size(), i.e. the other way around.
And then your if/else structure in collisionMagnitued(...) is probably not exactly what you want. I'm not sure what you're trying to implement there, but the else clause only kicks in if the second if is false, and no matter what happens in the first if.
Finally, you are starting a new animation on each mouse click. So, for example, if you have three balls bouncing around, you have three animation loops running, each of which is updating values when the balls collide. You need to start just one loop; it shouldn't do any harm if it refers to an empty list.
kinda new to swing and making guis in general, I have been using the drawLine method to create a "floors" and it has been working for the variables of the coordinates are local the the paintComponent class, however I want to change the coordinates using an actionListerner / Timer, so I need the variables to be accessible to the whole class.
This is probably a real simple fix (??) and I'll look like a fool for asking it here, but I can't work it out.
Here is the code.
class Elevator extends JPanel implements ActionListener {
private int numberOfLines = 0;
private int j = 60;
int yco = j - 125;
final private int HEIGHT = 50;
final private int WIDTH = 80;
final private boolean UP = true;
final private int TOP = 60;
final private int BOTTOM = j - 125;
private int mi = 5;
private Timer timer = new Timer(100, this);
public Elevator(int x) {
this.numberOfLines = x;
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (int i = 0; i < numberOfLines; i++) {
g.drawLine(0, j, getWidth(), j);
j += 75;
}
g.drawRect(getWidth() / 2, yco, WIDTH, HEIGHT);
}
#Override
public void actionPerformed(ActionEvent e) {
}
}
Any help is appreciated thanks in advance.
The reason your lines are not showing now that your variables (specifically j in your example code) are class variables, is that they are "remembering" state between calls to paintComponent(). Specifically, when j was local, it always got set back to its initial value (presumably j=60). Now however, it gets incremented in your for loop in the line
j += 75;
and never reset to a lower value. This means the second or third time that paintComponent() gets called, the value of j is too large, and your lines are being drawn outside the visible area (off screen). A Java Swing component can easily have its paintComponent() method called two, three, or more times before the component is even rendered on the screen, which is why your lines are no longer getting drawn (well, technically they are getting drawn, just not anywhere you can see them).
To fix this, you can add to your paintComponent() method the single line
j = 60;
just before the for loop. But at this point, you might as well just keep j local (unless you need to read its value but not change it with the timer).
Alternatively, if j needs to change over time, just make sure it gets set inside actionPerformed() by the timer, thenwork with a copy of the value inside of paintComponent() instead of directly with the value of j. As an example:
public void paintComponent(Graphics g) {
...
int jCopy = j;
for (int i = 0; i < numberOfLines; i++) {
g.drawLine(0, jCopy, getWidth(), jCopy);
jCopy += 75;
}
...
}
public void actionPerformed(ActionEvent e) {
...
j += 5;
//If you don't cap it at some max,
//it will go off the bottom of the screen again
if (j > 300) {
j = 60;
}
...
}
This will help stop consistency issues that could be caused by modifying j in both paintComponent() and actionPerformed().
What I'm trying to accomplish is adding 1 to the all the numbers in shipX array list, question is, how? I want to do this when the method move() is called, but how would I make this happen, as I'm new with arrays
class Ship
{
public void paint(Graphics g)
{
int shipX[] = {500,485,500,515,500};
int shipY[] = {500,485,455,485,500};
g.setColor(Color.cyan);
g.fillPolygon(shipX, shipY, 5);
}
public void move()
{
}
}
To start, you will have to move your arrays for points outside the local scope of paint() and into the class so that move() has access to the current values. You would increment in the move() method and call whatever routine you use to redraw your component.
class Ship
{
//make your polygon points members of the class
//so that you can have state that changes
//instead of declaring them in the paint method
int shipX[] = {500,485,500,515,500};
int shipY[] = {500,485,455,485,500};
//set these to the amount you want per update. They can even be negative
int velocityX = 1;
int velocityY = 1;
public void paint(Graphics g)
{
g.setColor(Color.cyan);
g.fillPolygon(shipX, shipY, 5);
}
public void move()
{
//add 1 to each value in shipX
for (int i=0; i<shipX.length; i++)
{
shipX[i] += velocityX;
}
//add 1 to each value in shipY
for (int i=0; i<shipY.length;i++)
{
shipY[i] += velocityY;
}
//call whatever you use to force a repaint
//normally I would assume your class extended
//javax.swing.JComponent, but you don't show it in your code
//if so, just uncomment:
//this.repaint();
}
}
Although I should point out that the repaint() method on JComponent does need to be called from the correct Swing thread, as pointed out in this answer.
If you are also trying to animate the movement, you can check out the Java Tutorial on Swing timers to call your move() method on a schedule. You could also use an ActionListener on a button to either control the Timer or on a button to move the object manually once per click.
All you have to do is iterate through the array and modify the value of each index:
for (int i = 0; i < shipX.length; i++)
{
shipX[i]++;
}
Increase the numbers one by one...
for (i=0; i<shipX.length; i++)
{
shipX[i]++; // same as shipX[i] = shipX[i] +1
}
for (i=0; i<shipY.length;i++)
{
shipY[i]++;
}