In very basic terms I have a panel that is drawing a line pixel by pixel and is being updated in real time. On top of this I want another panel that draws a box around the current pixel but clears itself later. Both are in real time
My current situation is a wrapper JPanel that has an OverlayLayout. The bottom panel has the line that is drawn with the Graphics2D object fetched from its JPanel. The top panel the box that follows around that is also drawn with the Graphics2D object fetched from its JPanel.
The issues in this system are many. Since I'm just drawing on the Graphics2D object separately and not overriding the JPanel's paint() not only is all the lines lost when the panel needs to repaint, I think I'm going against Swing's threading model by only having a single thread update the screen. I have also not gotten the top panel to work correctly, it just keeps clearing the screen and won't let the bottom panel draw a line.
What would be the best way to approach this situation? I have little experience with image processing and the low-level displaying of images in Swing, I just know the basics. I've heard of BufferedImage, but not knowing where to put that image, if it will be updated when changed later, the efficiency of doing that, and the fact that its buffered scare me off. I'm not sure what to use
Can someone point me in the right direction of what I need to use to accomplish this?
You can accumulate your points in a suitable Shape, such as GeneralPath, as shown in LissajousPanel. Use a javax.swing.Timer to cause periodic updates, and highlight the most recently added point with a surrounding rectangle.
I suggest
that you do not get your JPanel's Graphics or Graphics2D object via getGraphics as this object is not meant to be stable and will not work once the component has been repainted for any reason.
that you instead do all your drawing in a single JPanel's paintComponent method, but first in that method call the super's method.
that you again draw in only one JPanel, not two,
that you draw your line in a BufferedImage, since that is a more permanent part of your image.
That you get the BufferedImage's Graphics2D object via its createGraphics method
That you properly dispose of the BufferedImage's Graphics object when done using it so as to conserve resources.
that you display your BufferedImage in the paintComponent method of your JPanel (after calling the super method first)
that you draw your box, the more fleeting portion of the image, directly in the paintComponent using int class variables to tell paintComponent where to draw and perhaps a class boolean variable to tell if to draw.
and that most important, you review the tutorials on Swing graphics as to do this properly, you'll have to throw all old assumptions out and learn the correct way from the ground up (as we all had to do).
For example:
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
#SuppressWarnings("serial")
public class BufferedImageEg extends JPanel {
private static final int BI_WIDTH = 700;
private static final int BI_HEIGHT = 500;
private static final Color BACKGROUND = new Color(255, 255, 240);
private static final int THIS_PT_WIDTH = 12;
private static final int THIS_PT_HEIGHT = THIS_PT_WIDTH;
private static final float THIS_PT_STROKE_WIDTH = 2f;
private static final Color THIS_PT_BORDER_COLOR = Color.red;
private static final Color THIS_PT_FILL_COLOR = new Color(250, 250, 0, 125);
private static final int TIMER_DELAY = 30;
private static final int X_MIN = 0;
private static final int X_MAX = 100;
private static final double X_STEP = 0.1;
private static final double X_SCALE = (double) BI_WIDTH
/ ((double) X_MAX - X_MIN);
private static final double Y_SCALE = 8;
private static final float LINE_WIDTH = 4;
private static final Color LINE_COLOR = Color.blue;
private Point lastPoint = null;
private Point thisPoint = null;
private BufferedImage bImage = new BufferedImage(BI_WIDTH, BI_HEIGHT,
BufferedImage.TYPE_INT_RGB);
private double xValue = X_MIN;
public BufferedImageEg() {
Graphics biG = bImage.getGraphics();
biG.setColor(BACKGROUND);
biG.fillRect(0, 0, BI_WIDTH, BI_HEIGHT);
setBackground(BACKGROUND);
new Timer(TIMER_DELAY, new ActionListener() {
public void actionPerformed(ActionEvent e) {
timerActionPerformed(e);
}
}).start();
}
private void timerActionPerformed(ActionEvent e) {
if (xValue <= X_MAX) {
lastPoint = thisPoint;
double tempX = xValue;
double yValue = function(xValue);
tempX *= X_SCALE;
yValue *= Y_SCALE;
yValue = BI_HEIGHT / 2.0 - yValue;
thisPoint = new Point((int) tempX, (int) yValue);
if (lastPoint != null) {
drawInBufferedImage();
}
xValue += X_STEP;
} else {
((Timer) e.getSource()).stop();
thisPoint = null;
}
repaint();
}
private void drawInBufferedImage() {
Graphics2D g2 = bImage.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setStroke(new BasicStroke(LINE_WIDTH, BasicStroke.CAP_ROUND,
BasicStroke.JOIN_ROUND));
g2.setColor(LINE_COLOR);
int x1 = lastPoint.x;
int y1 = lastPoint.y;
int x2 = thisPoint.x;
int y2 = thisPoint.y;
g2.drawLine(x1, y1, x2, y2);
g2.dispose();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(BI_WIDTH, BI_HEIGHT);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(bImage, 0, 0, null);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
if (thisPoint != null) {
drawThisPoint(g2);
}
}
private void drawThisPoint(Graphics2D g2) {
int x = thisPoint.x - THIS_PT_WIDTH / 2;
int y = thisPoint.y - THIS_PT_HEIGHT / 2;
Graphics2D g2b = (Graphics2D) g2.create();
g2b.setStroke(new BasicStroke(THIS_PT_STROKE_WIDTH));
g2b.setColor(THIS_PT_FILL_COLOR);
g2b.fillOval(x, y, THIS_PT_WIDTH, THIS_PT_HEIGHT);
g2b.setColor(THIS_PT_BORDER_COLOR);
g2b.drawOval(x, y, THIS_PT_WIDTH, THIS_PT_HEIGHT);
g2b.dispose();
}
private double function(double x) {
return 24 * Math.sin(x / 12.0) * Math.sin(x);
}
private static void createAndShowGui() {
JFrame frame = new JFrame("BufferedImage Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new BufferedImageEg());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
Related
I am working on a lab to practice inheritance, in which we are to create a horizontal ellipse as "Shape1" and then create a "Shape2" which extends Shape1 which draws it's superclass Shape1, and then draws a vertical ellipse over top to create a new looking shape. The shape is displaying fine in terms of inheritance and looks (color/location etc) however when running the program, the frame width is set to 1000, and the height is set to 700, but If I drag the frame by the corner to enlarge it, the shape is drawn over and over again as I keep dragging the frame larger. Ideally the shape should just stay where it is relative to the frame size. I think this is happening because while I drag the frame larger, the draw method is being called over and over again by the system, but I am not sure where this is happening or how to fix it. Any suggestions?
All classes are displayed below:
Shape1:
public class Shape1 {
private double x, y, r;
protected Color col;
private Random randGen = new Random();
public Shape1(double x, double y, double r) {
this.x = x;
this.y = y;
this.r = r;
this.col = new Color(randGen.nextFloat(), randGen.nextFloat(), randGen.nextFloat());
}
public double getX() {
return this.x;
}
public double getY() {
return this.y;
}
public double getR() {
return this.r;
}
public void draw(Graphics2D g2){
//Create a horizontal ellipse
Ellipse2D horizontalEllipse = new Ellipse2D.Double(x - 2*r, y - r, 4 * r, 2 * r);
g2.setPaint(col);
g2.fill(horizontalEllipse);
}
}
Shape2:
public class Shape2 extends Shape1 {
public Shape2(double x, double y, double r) {
super(x, y, r);
}
public void draw(Graphics2D g2) {
//Create a horizontal ellipse
Ellipse2D verticalEllipse = new Ellipse2D.Double(super.getX() - super.getR(),
super.getY() - 2*super.getR(),
2 * super.getR(), 4 * super.getR());
super.draw(g2);
g2.fill(verticalEllipse);
}
}
ShapeComponent:
public class ShapeComponent extends JComponent {
//Instance variables here
private Random coordGen = new Random();
private final int FRAME_WIDTH = 1000;
private final int FRAME_HEIGHT = 700;
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
Shape2 myShape = new Shape2(1 + coordGen.nextInt(FRAME_WIDTH), 1 + coordGen.nextInt(FRAME_HEIGHT), 20);
//Draw shape here
myShape.draw(g2);
}
}
ShapeViewer(Where the JFrame is created):
public class ShapeViewer {
public static void main(String[] args) {
final int FRAME_WIDTH = 1000;
final int FRAME_HEIGHT = 700;
//A new frame
JFrame frame = new JFrame();
frame.setSize(FRAME_WIDTH, FRAME_HEIGHT);
frame.setTitle("Lab 5");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
ShapeComponent component = new ShapeComponent();
frame.add(component);
//We can see it!
frame.setVisible(true);
}
}
because while I drag the frame larger, the draw method is being called over and over again by the system,
Correct, all components are repainted when the frame is resized.
Any suggestions?
Painting code should be based on properties of your class. If you want the painting to be a fixed size then you define the properties that control the painting and set these properties outside the painting method.
For example, you would never invoke Random.nextInt(...) in the painting method. This means the value will change every time the component is repainted.
So the Shape should be created in the constructor of your class and its size would be defined there, not each time you paint it.
Class one:
public class Bar extends JComponent {
// instance variables - replace the example below with your own
private static boolean litUp = false;
private static boolean vertical = true;
private static int positionX;
private static int positionY;
private static int sizeX;
private static int sizeY;
private static Color color;
public void paintComponent(Graphics g) {
System.out.println("I am being called");
positionX = 50;
positionY = 30;
vertical = true;
if(vertical == true) {
sizeX = 10;
sizeY = 30;
if(litUp == true)
{
color = Color.red;
}
else
{
color = Color.black;
}
} else{
sizeX = 30;
sizeY = 10;
if(litUp == true)
{
color = Color.red;
}
else
{
color = Color.black;
}
}
g.fillRect(positionX, positionY, sizeX, sizeY);
g.setColor(color);
super.paintComponent(g);
}
}
Class Two:
public class TestingBar {
public static void main(String[] args) {
final int FRAME_WIDTH = 317;
final int FRAME_HEIGHT = 415;
//created frame and panel. Panel layout is taken away from the flow layout.
final JFrame myFrame = new JFrame();
myFrame.setTitle("Tester Window (v2)");
myFrame.setSize(FRAME_WIDTH,FRAME_HEIGHT);
final JPanel myPanel = new JPanel();
myPanel.setLayout(null);
final Bar testBar = new Bar();
//myPanel.add(testBar);
myFrame.getContentPane().add(testBar);
myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
myFrame.setVisible(true);
myPanel.add(testBar);
}
}
This is just giving me an empty JFrame. And the print statement is never being utilized in the paintComponent method. I am seriously stumped.
PS: They won't let me post this without typing more, so I will talk about turtles. I don't understand how they survive out in the wild. They can't run, their shells really aren't that hard, they can only attack from the front, and if they are flipped over they are crippled for an indefinite amount of time. You think that natural selection would have worn them out of the pool by now.
Maybe they survived by being so darn adorable. I mean, just watch youtube videos of a baby turtle trying to eat a cherry tomato. It is the epitome of cute.
I think you should call super.paintComponent(g); at the beggining of the function. It probably paints over your rectangle.
You are adding testBar to your frame then again to your myPanel which removes it from the frame (a component can only have one parent). But since myPanel is not added to anything, it never gets called.
Commenting out this line will fix it, but you probably are intending to do something meaningful with your myPanel, so check your design.
//myPanel.add(testBar);
I turned your Bar class into a JPanel and ovrrode the paint method instead and it worked but the other comments are correct, you're not adding the panel to the frame, just the bar object:
//created frame and panel. Panel layout is taken away from the flow layout.
final JFrame myFrame = new JFrame();
myFrame.setTitle("Tester Window (v2)");
myFrame.setSize(FRAME_WIDTH,FRAME_HEIGHT);
final Bar testBar = new Bar();
myFrame.getContentPane().add(testBar);
myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
myFrame.setVisible(true);
and in bar:
public class Bar extends JPanel
{
//....
public void paint(Graphics g)
{
g.setColor(Color.black);
g.fillRect(20,20,20,20);
}
}
Call super.paintComponents first thing. You probably want to cast g to a Graphics2D object, too.
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
Wrong order -- set the color, then draw the rectangle (using g2):
g2.setColor(color);
g2.fillRect(positionX, positionY, sizeX, sizeY);
I am learning the basics of java games programming and I am confused about a few things.
I know you use the "canvas" class to create a blank canvas and then use the paint method to create stuff.
But what does Graphics2D? I have seen people using the grahpics2d class to create a canvas for example
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.BLACK);
Now why did they use the grahpics2d and not the canvas?
Also I have seen people creating shapes like a rectangle by using:
Rectangle r = new Rectangle();
but some people have created them like:
Shape shape = new Rectangle2D.Double(value1,valu2,valu3,valu4);
What's the difference between these two?
Thanks in advance.
regards,
First, no I wouldn't use a Canvas object but rather a JPanel, and I'd draw in the paintComponent method override, not the paint method. Think of the JPanel as if it were a paint canvas and the Graphics or Graphics2D as if it were the brush that you were using to paint with. So in other words, you would need them both to create your drawing.
As for Rectangle vs. Rectangle2D, the 2D shapes are part of a newer addition to Graphics, when Graphics2D came about. These are based on the Shape interface, one that allows you a little more flexibility and OOPs to your drawing.
For greater detail, please have a look at:
Lesson: Performing Custom Painting
Trail: 2D Graphics
Painting in AWT and Swing
Edit
Re your questions:
Q: So you would use JPanel as my empty canvas and the Graphics2D g2d = (Graphics2D) g; create a brush kind of thing that you can use to change the JPanel. Hence this g2d.setColor(Color.BLACK); changes the background colour of our JPanel canvas. Is this right?
Yes. And you can even change the Graphics2D object's Stroke via
g2d.setStroke(new BasicStroke(...));
Q: Also can you explain to me what is "Shape" and what do you use it for?
Please look at the 2nd tutorial that I've linked to above as it will go into a fair bit of detail on what Shape represents and how to use it. It is in sum an interface used by all of the Xxxxx2D classes such as Rectangle2D and Ellipse2D. And it allows them all to share certain properties including being fillable, drawable, transormable, and more.
Edit 2
For example:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
import javax.swing.*;
#SuppressWarnings("serial")
public class RotateFoo extends JPanel {
private static final int PREF_WIDTH = 800;
private static final int PREF_HEIGHT = 600;
private static final Color STAR_COLOR = Color.red;
private static final int ROTATE_TIMER_DELAY = 20;
private static final int POINTS = 5;
private static final int RADIUS = 50;
private static final String TITLE = "Press \"r\" to rotate";
private static final float TITLE_POINTS = 52f;
private Path2D star = new Path2D.Double();
private Timer rotateTimer = new Timer(ROTATE_TIMER_DELAY, new RotateTimerListener());
public RotateFoo() {
double x = 0.0;
double y = 0.0;
double theta = 0.0;
for (int i = 0; i <= POINTS; i++) {
x = RADIUS + RADIUS * Math.cos(theta);
y = RADIUS + RADIUS * Math.sin(theta);
if (i == 0) {
star.moveTo(x, y);
} else {
star.lineTo(x, y);
}
theta += 4 * Math.PI / POINTS;
}
double tx = (getPreferredSize().getWidth() - star.getBounds().getWidth()) / 2;
double ty = (getPreferredSize().getHeight() - star.getBounds().getHeight()) / 2;
AffineTransform at = AffineTransform.getTranslateInstance(tx, ty);
star.transform(at );
int condition = JComponent.WHEN_IN_FOCUSED_WINDOW;
InputMap inputMap = getInputMap(condition);
ActionMap actionMap = getActionMap();
String rotateOn = "rotate on";
String rotateOff = "rotate off";
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_R, 0, false), rotateOn);
actionMap.put(rotateOn, new AbstractAction() {
public void actionPerformed(ActionEvent arg0) {
if (rotateTimer != null && !rotateTimer.isRunning()) {
rotateTimer.start();
}
}
});
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_R, 0, true), rotateOff);
actionMap.put(rotateOff, new AbstractAction() {
public void actionPerformed(ActionEvent arg0) {
if (rotateTimer != null && rotateTimer.isRunning()) {
rotateTimer.stop();
}
}
});
//rotateTimer.start();
JLabel titleLabel = new JLabel(TITLE, SwingConstants.CENTER);
titleLabel.setFont(titleLabel.getFont().deriveFont(Font.BOLD, TITLE_POINTS));
add(titleLabel);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_WIDTH, PREF_HEIGHT);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(STAR_COLOR);
if (star != null) {
g2.draw(star);
}
}
private class RotateTimerListener implements ActionListener {
private static final double BASE_THETA = Math.PI / 90;
#Override
public void actionPerformed(ActionEvent e) {
double anchorx = getPreferredSize().getWidth() / 2;
double anchory = getPreferredSize().getHeight() / 2;
AffineTransform at = AffineTransform.getRotateInstance(BASE_THETA, anchorx, anchory);
star.transform(at);
repaint();
}
}
private static void createAndShowGui() {
RotateFoo mainPanel = new RotateFoo();
JFrame frame = new JFrame("RotateFoo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
you have to make a certain difference:
a Canvas (or maybe a Panel or a JPanel or a Frame) are Objects that represent an GUI-Object! such an object is required to capture Input Events, or maybe used to be layouted. it can be set active and disabled, all that stuff.
a Graphics Objects is that thing, that is inside of the canvas. it is responsible for the pure drawing! it can use special drawing features, having strokes and fonts and colors...
so - you have two different classes for different purpose! it took me a while to understand that...
excuse my puny english...
Background
Im total Java newbie, today I started learning it (with thenewboston.org). I already know how to make simple windows/forms/gui, how to draw lines etc.
My goal is to create in Java gauge like this:
This is gauge which I created in .NET C# WPF, and now I want to rewrite this to Java.
Main question:
How to create triangle or other shape with some transparency and rotate it?
I tried to draw something by using Graphics object like this:
public void paint(Graphics g){
g.drawLine(0, 0, 100, 100);
}
But I think this is wrong direction, because when I put something on graphics - it just stays there, I can't move or rotate it.
I have to clear whole graphics and draw it again to make kind of "animation", or there is easier way?
Edit:
I already know how to antialias (Hovercraft Full Of Eels already helped me in this - thanks).
Edit2:
My code actually looks like this:
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class MainWindow extends JPanel {
private Point p1 = new Point(100, 100);
private Point p2 = new Point(740, 450);
public MainWindow() {
this.setPreferredSize(new Dimension(800, 600));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
drawLines(g);
}
private void drawLines(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.DARK_GRAY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setStroke(new BasicStroke(4, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
g.drawLine(p1.x, p1.y, p2.x, p2.y);
}
private void display() {
JFrame f = new JFrame("Main Window");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
new MainWindow().display();
}
}
You state:
I tried to draw something by using Graphics object like this:
public void paint(Graphics g){
g.drawLine(0, 0, 100, 100);
}
But I think this is wrong direction, because when I put something on graphics - it just stays there, I can't move or rotate it.
I have to clear whole graphics and draw it again to make kind of "animation", or there is easier way?
Suggestions:
Don't hard-code your numbers. Use class fields (variables) instead so that your program can change the position of items drawn easily.
Don't override a component's paint(...) method. Instead override the paintComponent(Graphics g) method of an object that derives from JComponent or one of its children such as JPanel. This will give you the benefit of automatic double-buffering for smoother animation, and also will reduce the likelihood of erroneous drawing of a component's children or borders.
Cast your Graphics object to a Graphics2D object so that you can do more advanced drawing using classes that implement the Shape interface, including Rectangle2D, Ellipse2D, Line2D, Path2D, and many more.
Draw the background image as a BufferedImage using Graphics#drawImage(...) method, and then draw your moving images on top of this, again using the Graphics2D object and again changing the images drawn based on the state of the object (the values held by its fields).
Be careful when doing animations that you obey Swing threading rules, that you don't have any animation or game loops that tie up the Swing thread. A Swing Timer can allow you to create a quick and easy (albeit somewhat primitive) game loop.
For example:
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import javax.swing.*;
public class DailAnimation extends JPanel {
private static final int PREF_W = 400;
private static final int PREF_H = 350;
private static final Point2D CENTER = new Point2D.Double(PREF_W / 2.0,
PREF_W / 2.0);
private static final double RADIUS = PREF_W / 2.0;
private static final Color LARGE_TICK_COLOR = Color.green;
private static final Color CENTER_HUB_COLOR = Color.LIGHT_GRAY;
private static final Stroke LARGE_TICK_STROKE = new BasicStroke(3f);
private static final int LRG_TICK_COUNT = 9;
private static final double TOTAL_LRG_TICKS = 12;
private static final double LRG_TICK_OUTER_RAD = 0.9;
private static final double LRG_TICK_INNER_RAD = 0.8;
private static final int START_TICK = 10;
private static final double CENTER_HUB_RADIUS = 10;
public static final int MAX_SPEED = 100;
private static final double INIT_SPEED = 0;
private static final double DIAL_INNER_RAD = 0.02;
private static final double DIAL_OUTER_RAD = 0.75;
private static final Color DIAL_COLOR = Color.DARK_GRAY;
private BufferedImage backgroundImg;
private double speed;
private double theta;
private double cosTheta;
private double sinTheta;
public DailAnimation() {
setBackground(Color.white);
backgroundImg = createBackgroundImg();
setSpeed(INIT_SPEED);
}
public void setSpeed(double speed) {
if (speed < 0) {
speed = 0;
} else if (speed > MAX_SPEED) {
speed = MAX_SPEED;
}
this.speed = speed;
this.theta = ((speed / MAX_SPEED) * LRG_TICK_COUNT * 2.0 + START_TICK)
* Math.PI / TOTAL_LRG_TICKS;
cosTheta = Math.cos(theta);
sinTheta = Math.sin(theta);
repaint();
}
private BufferedImage createBackgroundImg() {
BufferedImage img = new BufferedImage(PREF_W, PREF_H,
BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = img.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(LARGE_TICK_COLOR);
g2.setStroke(LARGE_TICK_STROKE);
for (int i = 0; i < LRG_TICK_COUNT; i++) {
double theta = (i * 2.0 + START_TICK) * Math.PI / TOTAL_LRG_TICKS;
double cosTheta = Math.cos(theta);
double sinTheta = Math.sin(theta);
int x1 = (int) (LRG_TICK_INNER_RAD * RADIUS * cosTheta + CENTER.getX());
int y1 = (int) (LRG_TICK_INNER_RAD * RADIUS * sinTheta + CENTER.getY());
int x2 = (int) (LRG_TICK_OUTER_RAD * RADIUS * cosTheta + CENTER.getX());
int y2 = (int) (LRG_TICK_OUTER_RAD * RADIUS * sinTheta + CENTER.getY());
g2.drawLine(x1, y1, x2, y2);
}
g2.setColor(CENTER_HUB_COLOR);
int x = (int) (CENTER.getX() - CENTER_HUB_RADIUS);
int y = (int) (CENTER.getY() - CENTER_HUB_RADIUS);
int width = (int) (2 * CENTER_HUB_RADIUS);
int height = width;
g2.fillOval(x, y, width, height);
// g2.draw(ellipse);
g2.dispose();
return img;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (backgroundImg != null) {
g.drawImage(backgroundImg, 0, 0, this);
}
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(DIAL_COLOR);
int x1 = (int) (DIAL_INNER_RAD * RADIUS * cosTheta + CENTER.getX());
int y1 = (int) (DIAL_INNER_RAD * RADIUS * sinTheta + CENTER.getY());
int x2 = (int) (DIAL_OUTER_RAD * RADIUS * cosTheta + CENTER.getX());
int y2 = (int) (DIAL_OUTER_RAD * RADIUS * sinTheta + CENTER.getY());
g.drawLine(x1, y1, x2, y2);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
private static void createAndShowGui() {
final DailAnimation mainPanel = new DailAnimation();
JFrame frame = new JFrame("DailAnimation");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
int delay = 100;
new Timer(delay, new ActionListener() {
int speed = 0;
#Override
public void actionPerformed(ActionEvent evt) {
speed ++;
if (speed > DailAnimation.MAX_SPEED) {
((Timer)evt.getSource()).stop();
}
mainPanel.setSpeed(speed);
}
}).start();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
Drawing a line via Graphics.drawLine() writes pixels directly to whatever is backing the Graphics instance. If you want to rotate the line, you must calculate what its coordinates should be when rotated. This is the only way to draw things in AWT and Swing.
You could write a needle class that maintained its angle, and then have it handle its rendering every frame.
This must be rather trivial and straight forward, but I cannot figure it out.
This is what my JPanel looks like, it is added to a JFrame:
private class RadarPanel extends JPanel {
public RadarPanel() {
super();
this.repaint();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
//painting logic here
//repaint in 500 ms
this.repaint(500);
}
}
Now, when I resize the JFrame this JPanel starts getting redrawn all the time. However, when I do not resize the JFrame the JPanel's paintComponent method does not seem to get called, even though I call repaint in the constructor.
Any advice? Thanks.
UPDATE:
more complete code (everything except drawing logic):
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class PlayerRadar extends JFrame {
private static final long serialVersionUID = 230324190;
//settings
private static final int windowWidth = 300;
private static final int windowHeight = 300;
private static final int maxDistance = 250;
//components
private PlayerRadar radarWindow;
private JPanel radarPanel;
public PlayerRadar(String title) {
super(title);
//set reference
radarWindow = this;
//create radar window
Dimension screenSize = java.awt.Toolkit.getDefaultToolkit().getScreenSize();
this.setAlwaysOnTop(true);
this.setBackground(new Color(0xFFFFFF));
this.setBounds(screenSize.width - windowWidth, 0, windowWidth, windowHeight);
this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
radarWindow.setVisible(false);
}
});
this.setVisible(true);
//create a JPanel for drawing
radarPanel = new RadarPanel();
radarPanel.setBounds(0, 0, windowWidth, windowHeight);
radarPanel.setBackground(new Color(0xFFFFFF));
//add to frame
this.getContentPane().add(radarPanel);
}
private class RadarPanel extends JPanel {
private static final long serialVersionUID = 230324191;
private static final int repaintInterval = 500;
public RadarPanel() {
super();
}
#Override
public void paint(Graphics g) {
super.paint(g);
//draw player oval (center of the frame)
g.setColor(Color.BLUE); //blue
int ovalWidth = (int) Math.round(this.getWidth() / 30);
int ovalHeight = (int) Math.round(this.getHeight() / 30);
int playerLocalX = (int) Math.round(this.getWidth() / 2);
int playerLocalY = (int) Math.round(this.getHeight() / 2);
int ovalX = playerLocalX - ovalWidth / 2;
int ovalY = playerLocalY - ovalHeight / 2;
g.fillOval(ovalX, ovalY, ovalWidth, ovalHeight);
g.setColor(Color.BLACK); //black
g.drawOval(ovalX, ovalY, ovalWidth, ovalHeight);
//get info of the player itself
PlayerInfo thisPlayer = GameUtil.getPlayerInfo();
float playerPosZ = thisPlayer.position[0];
float playerPosX = thisPlayer.position[2];
//float playerRotRad = thisPlayer.rotation;
//set rectangle specs
int rectWidth = this.getWidth() / 40;
int rectHeight = this.getWidth() / 40;
//only continue if we have information about our player
if (thisPlayer != null) {
//get nearby players
ArrayList<PlayerInfo> playersInfo = GameUtil.getNearbyPlayers();
//for each other player, draw a rectangle
for (PlayerInfo playerInfo : playersInfo) {
//get data
float posZ = playerInfo.position[0];
float posX = playerInfo.position[2];
//float rotRad = playerInfo.rotation;
//calculate relative x and y
int rectX = playerLocalX + Math.round((posX - playerPosX) / maxDistance * this.getWidth() / 2) - rectWidth / 2;
int rectY = playerLocalY + ovalHeight / 2 + Math.round((playerPosZ - posZ) / maxDistance * this.getHeight() / 2) - rectHeight / 2;
//draw rectangle
g.setColor(Color.RED);
g.fillRect(rectX, rectY, rectWidth, rectHeight);
g.setColor(Color.BLACK);
g.drawRect(rectX, rectY, rectWidth, rectHeight);
}
}
//repaint soon
this.repaint(repaintInterval);
}
}
}
You where correct the first time. Custom painting is done in the paintComponent() method, NOT the paint() method.
You should NEVER invoke repaint() from within the paintComponent() method, since that will result in an infinite loop.
If you want to animate the painting, then you should be using a Swing Timer to schedule the animation.
You should not be using use setSize(). That is the job of the layout manager. Instead you can override the getPreferredSize() method of the panel (or use setPreferredSize()) and then you can pack() the frame, instead of setting its size.
The panel should be added to the frame BEFORE the frame is made visible otherwise it has a size of (0, 0) which means there is nothing to paint.
It won't repaint until your form is shown and graphics is initialized. I don't think calling repaint in constructor is a good idea. It will repaint once the component is visible.