【问题标题】:Layered Canvas in Java SwingJava Swing 中的分层画布
【发布时间】:2013-05-22 01:05:52
【问题描述】:

我正在模拟 在连接节点图上行走。为了直观地显示它,我使用了画布对象。我首先通过它的节点和它们之间的链接来渲染图形,然后我开始绘制 person,它由一个在图形中移动的小方块表示。

我的问题是,在我绘制地图(或图表)后,人的动画会删除标签,有时还会删除图表的线条。我知道这是因为我渲染了人的动作 在包含地图的同一画布上(并且由于 clearRect() 方法调用)。

如何避免清除图表?起初我查看了JLayeredPane,但画布重叠并且在最上面的画布上没有透明度(没有透明的人)。我想到的第二个选项是在绘制人之前复制该区域,然后在人移动时恢复该区域,但我不确定如何实现这一点,因此我没有使用过swingawt 这么多,我认为这可能是一个常见问题。

我附上了一张图片来显示我的问题以及我为每个

的渲染代码
public class Person extends Thread {

    public Person(String name, Spot location, World world, Graphics g) {
    this.name= name;
    this.location= location;
    this.world= world;
    this.g= g;
}

private void move() {
    Set<Link> links= world.getLinksFrom(location.getId());  
Link route= CollectionUtil.getRandomElement(links);  
Spot destination= route.getOriginX() == location.getX() &&   
    route.getOriginY() == location.getY() ?  
    route.getTheTarget(): route.getTheOrigin();  

try {
    double deltaX= (destination.getX() - location.getX()) / route.distance();
    double deltaY= (destination.getY() - location.getY()) / route.distance();
    double w2= (PERSON_WIDTH / 2);

    for(double i=location.getX(), j=location.getY(), d= route.distance(); 
    d > 5;
    i+=deltaX, j+= deltaY, 
    d=Point2D.distance(i, j, destination.getX(), destination.getY())) {
            g.clearRect((int)(i - w2 - deltaX), (int)(j - w2 - deltaY), 
        PERSON_WIDTH, PERSON_WIDTH);
        g.drawRect((int)(i-w2), (int)(j-w2), 
        PERSON_WIDTH-1, PERSON_WIDTH-1);
        Thread.sleep(50);
    }

    this.location= destination;

    // Stay ath the new location for a while
    Thread.sleep(new Random(System.currentTimeMillis()).nextInt(Person.MAX_SPOT_MILLIS));
    } catch(InterruptedException e) {
        throw new RuntimeException(e);
    }

@Override
public void run() {
    while(!isInterrupted()) {
        this.move();            
    }
}
}

【问题讨论】:

  • 如需尽快获得更好的帮助,请发帖SSCCE
  • 没有足够的信息继续下去,但是,看起来您存储了对图形对象的引用,可能是通过使用 getGraphics。这不是一个好主意。 getGraohics 返回的图形上下文可以为空,并在重绘之间更改。您似乎也在事件 Dispathing 线程之外更新 UI。这也是一个坏主意,因为您实际上无法控制重绘发生的时间,并且可能会产生绘画伪影。更多详情请关注Custom Painting
  • 您对您假设的一切都是正确的(getGraphics 参考,每个人都是Thread),感谢您提供的链接我会看看我是否可以设置一个SSCCE 作为@AndreThompson 点出来。
  • 使用 JLayer/GlassPane 在上面绘画
  • 不要通过 Thread.sleep(int) 阻止 EDT,也不要阻止 OpenGL/CL

标签: java swing user-interface awt graphics2d


【解决方案1】:

两种最常见的解决方案是:

  1. 每次都画图。使用double buffering 避免闪烁。除非您的图表有很多节点并且您的计算机速度很慢,否则这应该可以正常工作。

  2. 将图形渲染成图像。在开始绘制人物之前,先绘制一次图像。这会清除整个画布。

【讨论】:

  • 我不确定 OP 是否使用 java.awt.Canvas。如果是,那么问题就变成了为什么?默认情况下,Swing 组件是双缓冲的。无论如何,第二点是一个很好的建议
【解决方案2】:
  1. 除非您自己创建它,否则您不应维护对Graphics 上下文的引用。图形上下文可以在重绘之间更改,重绘将覆盖其间绘制的任何内容。您应该覆盖自定义组件(如 JPanel)的 paintComponet 方法并在每次调用时绘制组件的当前状态
  2. 听起来您正在使用java.awt.Canvas,您应该避免使用 AWT 并改用 Swing。这是一个更新的框架,默认情况下是双缓冲的。您应该避免混合 AWT 和 Swing 组件,因为它们往往不能很好地结合在一起
  3. Swing 是单线程环境,这意味着您应该只在事件调度线程的上下文中与 UI 交互吗?查看Concurrency in Swing了解更多详情。
  4. 有 3 种方法可以制作 Swing 动画。您可以使用Threadjavax.swing.Timer 或其中一种动画框架。

使用线程更复杂,您需要负责确保更新正确地重新同步到客户端。这对于复杂的动画作业很有用,其中每个周期计算状态之间的时间会有所不同。

javax.swing.Timer 更简单,因为它的回调是在 EDT 的上下文中定期执行的。这对于更新状态所需的时间很短和/或合理恒定的简单动画非常有用,因此它们不会有可能导致惊人的帧

【讨论】:

    【解决方案3】:

    如果您想尝试拥有两个画布,那么我想我可能有一个解决方案。

    创建你的第一个画布来绘制你的图表,并在上面创建一个画布来绘制你的人。

    您可以将顶部画布的背景颜色设置为透明色,然后也可以使用相同的透明色清除画布。

    例如:

    g.setBackground(new Color(0,0,0,0));
    g.clearRect(0, 0, width, height);
    

    将清除顶部画布,留下完全透明的颜色

    canvas.setBackground(new Color(0,0,0,0);
    

    将使画布组件透明。 只需要在创建画布时调用一次。

    现在顶部画布在您使用 0 alpha 颜色清除后将完全透明,只有您在顶部画布上绘制的内容才会遮挡底部画布。

    希望对你有帮助

    【讨论】:

    • 如果 OP 使用 AWT 组件,这可能会导致绘制伪影,因为如果我没记错的话,AWT 组件不支持透明度并且重绘管理器不会知道更新它下面的组件。如果 OP 发布 Swing 组件,那么他们应该通过 setOpaque 方法将组件设置为透明,这会告诉重绘管理器它下面的组件也需要更新
    • 我想我想的是一个比我意识到的更复杂的答案。我正在考虑让 2 个类扩展摇摆 JPanel 并运行他们自己的计时器。通过这种方式,它们可以通过透明度设置单独管理。我尝试使用从单独线程创建的多个面板。尽管性能影响太大,但它的工作方式与我想的一样。来自不同线程的多个图形上下文意味着我需要大量同步,而对于一个更简单的问题,这确实是一个过于复杂的解决方案。
    猜你喜欢
    • 2023-03-11
    • 2015-02-15
    • 2011-11-29
    • 1970-01-01
    • 2015-02-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-08
    相关资源
    最近更新 更多