Java JFrame点绘制的奇怪行为
我正在编写一个程序,用户可以通过点击并拖动鼠标在JPanel上绘制点。此外,绘图区域被划分为多个扇区,并旋转这些点,以使每个扇区都相同。例如,内部有一个点的十二扇区排列将在360/12度内旋转该点十二次。旋转效果很好,但在尝试绘制点时会出现一些非常奇怪的行为。如果试图在原点周围画一个圆,在平滑添加之前,这些点将在短时间内偶尔出现。此图显示了我的意思(在其中一个扇区中绘制四分之一圆的结果): 您可以看到,当接近扇形分区的一侧时,点会平滑地添加。但是,最初,这些点是分开的,绘制起来并不平滑。代码如下所示(为了便于阅读,多余的GUI元素和导入已被删除):Java JFrame点绘制的奇怪行为,java,swing,graphics,jpanel,Java,Swing,Graphics,Jpanel,我正在编写一个程序,用户可以通过点击并拖动鼠标在JPanel上绘制点。此外,绘图区域被划分为多个扇区,并旋转这些点,以使每个扇区都相同。例如,内部有一个点的十二扇区排列将在360/12度内旋转该点十二次。旋转效果很好,但在尝试绘制点时会出现一些非常奇怪的行为。如果试图在原点周围画一个圆,在平滑添加之前,这些点将在短时间内偶尔出现。此图显示了我的意思(在其中一个扇区中绘制四分之一圆的结果): 您可以看到,当接近扇形分区的一侧时,点会平滑地添加。但是,最初,这些点是分开的,绘制起来并不平滑。代码如
公共类Doiles扩展了JPanel实现了MouseListener、ActionListener、MouseMotionListener
{
//全局变量声明
JFrame窗口=新JFrame(“绘图”);
final int linelength=340;//扇区定义线的长度
int nlines=12;//存储扇区定义行的数量
字符串numsectors=null;
int currentovalsize=10;
Color currentcolour=Color.WHITE;
Deque points=new LinkedList();
公共卫生用品
{
设置窗口大小(20001000);
//画板+画法
JPanel drawingPanel=新JPanel()
{
公共组件(图形g)
{
超级组件(g);
//计算扇区之间的角度
双θ=(2*Math.PI)/nlines;
g、 setColor(Color.WHITE);
//计算线坐标并绘制扇形线
对于(int i=0;i。在1080p屏幕上移动的每个鼠标上调用repaint()将刷新它的次数远远超过所需次数。有两种方法可以解决此问题。通过以下方式限制对addPoint()的调用:
空间
时间
我将提供一个示例,说明如何同时使用这两种方法
空间
保存在Doiles类的实例变量中更新的最后一个点的位置:
int previousX, previousY
设置在绘制和重新绘制屏幕之前必须满足的偏移值(移动的距离):
static final in MINIMUM_OFFSET = 10; //mess around with this and use whatever looks good and performs well
然后,修改mouseDragged实现以考虑其移动的距离:
@Override
public void mouseDragged(MouseEvent e)
{
//you can add some trig to this to calculate the hypotenuse, but with pixels I wouldn't bother
int distance = Math.abs(e.getY() - previousY) + Math.abs(e.getX - previousX);
if(distance > OFFSET_VALUE){
//update the previous x,y values
this.previousX = e.getX();
this.previousY = e.getY();
//add point
addPoint(e.getX(),e.getY());
}
}
这将降低刷新率,具体取决于鼠标移动的距离。这适用于您所描述的内容,但如果此JPanel还需要考虑其他因素,那么下面的时间解决方案会更好:
时间
您实际上不需要为此实现MouseMotionListener。在MouseStener实现中,更改类中表示是否在JPanel上按下鼠标的布尔标志:
boolean isMousePressed;
@Override
mousePressed(MouseEvent e) {
isMousePressed = true;
}
@Override
mouseReleased(MouseEvent e) {
isMousePressed = false;
}
然后,使用(Swing组件的线程安全)每隔一段时间使用MouseListener+在画布上更新它:
就我个人而言,我更喜欢第二种解决方案,因为它不太密集,而且刷新率是恒定的
编辑:每次调用计时器并按下鼠标时,通过重新分配一个“点”实例变量,将第一个实例与第二个实例结合起来。这样,您就可以获得更高的刷新率和一致的点位置。为什么它不起作用需要一个数学技能更好的人,然后我必须弄清楚,我会问我的4.5岁的她玩完洋娃娃后去看一看;)
不过,我所做的是回到API的可用功能上,特别是仿射变换
,它允许您旋转图形
上下文(以及其他内容)
因此,基本上,对于每个片段,我旋转上下文,并绘制所有的点
我还花了一些时间删除了所有的“神奇”数字,并专注于处理已知值(比如根据构件的宽度和高度计算构件的实际中心)
魔法
所以,魔法基本上发生在这里
double delta = 360.0 / (double) nlines;
Graphics2D gCopy = (Graphics2D) g.create();
AffineTransform at = AffineTransform.getRotateInstance(
Math.toRadians(delta),
centerPoint.x,
centerPoint.x);
for (int h = 0; h < nlines; h++) {
for (DoilyPoint j : points) {
gCopy.fillOval(j.getX(), j.getY(), j.getSize(), j.getSize());
}
gCopy.transform(at);
}
gCopy.dispose();
建议。。。
- 当我测试这个时,为了使它更简单,我将段的数量减少到2和3
- 我使用鼠标点击而不是鼠标拖动,这样我可以更好地控制点的创建,并查看实际情况
- 我为每个片段设置了一个单独的颜色,这样我就可以看到这些点实际上是在哪里画的
- 考虑到与此问题和类似问题相关的问题出现的频率,请与班上其他学生分享此信息,因为重复一些基本的解决方案会变得令人厌烦
谢谢您的帮助,但只有一个问题:我应该在代码中的什么位置放置计时器代码块?@imruction我将计时器任务设置为JPanel类中的实例/类变量。然后,在构造函数中声明它(“新建”声明和实现),并在您将JPanel可见性设置为true后立即启动计时器。如果您在可能出现NPE或其他恶劣行为之前启动计时器。另外,在处理JPanel对象时,不要忘记停止计时器!我已经有一段时间没有使用Swing了,但是如果处理不当,混合了大量GUI组件的线程会导致奇怪的事情。edit-如果它解决了您的问题,请将此标记为正确:)我担心它不起作用-我的问题与以前相同,只是现在图形的坐标与鼠标有一点偏移。不过我会在早上尝试您的第一个解决方案。谢谢您的回答:)我很确定您所述的原因不是实际原因但是,只要@imruction没有发布问题,人们只能猜测…我想你的意思是说,而不是TimerTask
。请尝试在问题c的地方发布问题
// this is set to 60Hz I, mess around with it to get the best results
Timer timer=new Timer(1000/60, e -> {
if(isMousePressed) {
Point p = MouseInfo.getPointerInfo().getLocation();
addPoint(p.x,p.y);
}
});
double delta = 360.0 / (double) nlines;
Graphics2D gCopy = (Graphics2D) g.create();
AffineTransform at = AffineTransform.getRotateInstance(
Math.toRadians(delta),
centerPoint.x,
centerPoint.x);
for (int h = 0; h < nlines; h++) {
for (DoilyPoint j : points) {
gCopy.fillOval(j.getX(), j.getY(), j.getSize(), j.getSize());
}
gCopy.transform(at);
}
gCopy.dispose();
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.AffineTransform;
import java.util.Deque;
import java.util.LinkedList;
import javax.swing.JFrame;
import javax.swing.JPanel;
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 Doiles());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class Doiles extends JPanel implements MouseListener, ActionListener, MouseMotionListener {
//global variable declarations
int nlines = 12;//store the number of sector defining lines
int currentovalsize = 10;
Color currentcolour = Color.WHITE;
Deque<DoilyPoint> points = new LinkedList<DoilyPoint>();
Color test[] = {Color.RED,
Color.GREEN,
Color.BLUE, Color.MAGENTA, Color.CYAN};
public Doiles() {
//drawing panel + paint method
JPanel drawingPanel = new JPanel() {
public void paintComponent(Graphics g) {
super.paintComponent(g);
int lineLength = Math.max(getWidth(), getHeight());
Point centerPoint = new Point(getWidth() / 2, getHeight() / 2);
//calculate angle between sectors
double theta = Math.toRadians(360.0 / nlines);
g.setColor(Color.WHITE);
//calculate line coordinates and draw the sector lines
for (int i = 0; i < nlines; i++) {
g.drawLine(centerPoint.x, centerPoint.y,
centerPoint.x + (int) Math.round(lineLength * Math.cos(theta * i)),
centerPoint.y + (int) Math.round(lineLength * Math.sin(theta * i)));
}
double delta = 360.0 / (double) nlines;
Graphics2D gCopy = (Graphics2D) g.create();
AffineTransform at = AffineTransform.getRotateInstance(
Math.toRadians(delta),
centerPoint.x,
centerPoint.x);
for (int h = 0; h < nlines; h++) {
for (DoilyPoint j : points) {
gCopy.fillOval(j.getX(), j.getY(), j.getSize(), j.getSize());
}
gCopy.transform(at);
}
gCopy.dispose();
}
};
drawingPanel.setBackground(Color.BLACK);
drawingPanel.addMouseMotionListener(this);
drawingPanel.addMouseListener(this);
setLayout(new BorderLayout());
add(drawingPanel);
}
@Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
public void addPoint(int x, int y) {
points.addFirst(new DoilyPoint(currentovalsize, x, y, currentcolour));
repaint();
}
@Override
public void mouseDragged(MouseEvent e) {
addPoint(e.getX(), e.getY());
}
@Override
public void mouseClicked(MouseEvent e) {
// addPoint(e.getX(), e.getY());
}
@Override
public void mousePressed(MouseEvent e) {
}
@Override
public void mouseReleased(MouseEvent e) {
}
@Override
public void mouseEntered(MouseEvent e) {
}
@Override
public void mouseExited(MouseEvent e) {
}
@Override
public void actionPerformed(ActionEvent e) {
}
@Override
public void mouseMoved(MouseEvent e) {
}
}
class DoilyPoint {
private int size;
private int x;
private int y;
private Color colour;
void setSize(int a) {
this.size = a;
}
int getSize() {
return size;
}
void setX(int a) {
this.x = a;
}
int getX() {
return x;
}
void setY(int a) {
this.y = a;
}
int getY() {
return y;
}
void setColor(Color r) {
this.colour = r;
}
Color getColor() {
return colour;
}
public DoilyPoint(int size, int x, int y, Color colour) {
this.size = size;
this.x = x;
this.y = y;
this.colour = colour;
}
}
}