我正在编写一个程序,用户可以通过单击并拖动鼠标在JPanel上绘制点。另外,绘图区域被划分为多个扇区,并且旋转这些点使得每个扇区是相同的。例如,内部具有单个点的十二个扇区布置将使该点旋转12次到360/12度。旋转工作正常,但在尝试绘制点时有一些非常奇怪的行为。如果试图在原点周围绘制一个圆圈,那么这些点在短时间内会非常零星地出现,然后才能顺利添加。此图显示了我的意思(在其中一个扇区中绘制四分之一圆的结果):
您可以看到,当接近扇区划分的一侧时,这些点会平滑地添加。但是,最初这些点是分开的并且不能平滑地绘制。代码如下所示(多余的GUI元素和导入已被删除以便于阅读):
public class Doiles extends JPanel implements MouseListener,ActionListener,MouseMotionListener
{
//global variable declarations
JFrame window = new JFrame("Draw");
final int linelength = 340;//length of sector defining lines
int nlines = 12;//store the number of sector defining lines
String numsectors=null;
int currentovalsize = 10;
Color currentcolour = Color.WHITE;
Deque<DoilyPoint> points = new LinkedList<DoilyPoint>();
public Doiles()
{
window.setSize(2000,1000);
//drawing panel + paint method
JPanel drawingPanel = new JPanel()
{
public void paintComponent(Graphics g)
{
super.paintComponent(g);
//calculate angle between sectors
double theta = (2*Math.PI)/nlines;
g.setColor(Color.WHITE);
//calculate line coordinates and draw the sector lines
for(int i=0; i <nlines;i++)
{
g.drawLine(400, 350, 400+(int)Math.round(linelength*Math.cos(theta*i)), 350+(int)Math.round(linelength*Math.sin(theta*i)));
}
for(DoilyPoint j : points)
{
g.fillOval(j.getX(), j.getY(), j.getSize(), j.getSize());
for(int h = 1;h<nlines;h++)
{
double rtheta;
if(j.getX()==400)
rtheta = Math.PI/2;
else
rtheta = Math.atan((j.getY()-350)/(j.getX()-400));
System.out.println(rtheta);
double r = Math.sqrt(Math.pow(j.getX()-400,2)+Math.pow(j.getY()-350,2));
double angle = (h*theta)+rtheta;
double x = r*Math.cos(angle);
double y = r*Math.sin(angle);
g.fillOval((int)Math.round(x)+400,(int)Math.round(y)+350, j.getSize(), j.getSize());
}
}
}
};
}
public static void main(String[] args)
{
new Doiles();
}
public void addPoint(int x, int y)
{
points.addFirst(new DoilyPoint(currentovalsize,x,y,currentcolour));
window.repaint();
}
@Override
public void mouseDragged(MouseEvent e)
{
addPoint(e.getX(),e.getY());
}
}
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;
}
}
我认为它与Java处理拖动鼠标的方式有关,但我想知道如何平滑绘图。谁能告诉我什么是错的?
答案 0 :(得分:4)
为什么它不起作用会带一个拥有更好数学技能的人,然后我必须弄清楚,我会让我的4.5岁的孩子看看她玩完玩偶娃娃之后;)
我已经完成的工作可以追溯到API的可用功能,特别是AffineTransform
,它允许您旋转Graphics
上下文(以及其他内容)。
所以,基本上,对于每个片段,我旋转上下文,并绘制所有点。
我还花了一些时间删除所有“神奇”数字并专注于使用已知值(比如根据宽度和高度计算组件的实际中心)
所以,魔法基本上就在这里......
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();
有许多重要概念需要理解
AffineTransform
。这是非常基本的,我们提供一个锚点,围绕该锚点进行旋转,在这种情况下,组件的中心和要应用的旋转量。AffineTransform
转换复制的上下文。这是一个记住的巧妙技巧,转换是复合的,所以我们只需要知道要改变的增量,而不是实际的角度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;
}
}
}
答案 1 :(得分:2)
Yes, the mouse is refreshing at a very fast rate causing it to lag。在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还有其他因素需要考虑,则下面的Time解决方案会更好:
您真的不需要为此实现MouseMotionListener。在MouseListener实现中,更改类中的布尔标志,表示是否在JPanel上按下了鼠标:
boolean isMousePressed;
@Override
mousePressed(MouseEvent e) {
isMousePressed = true;
}
@Override
mouseReleased(MouseEvent e) {
isMousePressed = false;
}
然后,使用javax.swing.Timer
(针对Swing组件的线程安全)使用MouseListener + PointerInfo来经常更新画布:
// 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);
}
});
就个人而言,我更喜欢第二种解决方案,因为它不那么密集,而且刷新率是恒定的。
编辑:通过每次调用Timer并按下鼠标时重新分配'Point'实例变量,将第一个与第二个结合起来。然后你甚至会有刷新率和一致的点位置。