Painting in AWT and Swing

惨痛背景

在模仿网上做贪吃蛇小游戏时,由于网上源码用的是AWT,而我用的是Swing,对于painting我也一知半解,导致绘图出现各种奇怪的现象。我用Swing重写了update()和paint()(像AWT那样),update()白写没调用是一回事,paint()少写了super.paint()导致了一系列bug。

于是我在官网上找到文章Paint in AWT and Swing ,有了以下总结。

重量级组件和轻量级组件

  • heavyweight” means that the component has it’s own opaque native window

  • a “lightweight” component is one that reuses the native window of its closest heavyweight ancestor

重量级组件有自己不透明的本地窗口,而轻量级组件是重用其最接近的重量级祖先的本机窗口的组件。

翻译过来有点难懂,其实就是重量级组件是调用本地操作系统的方法画出来的组件,而轻量级组件不依赖于本地系统,而是将一个其最接近重量级容器组件作为载体,是用Java代码来实现的组件。

当JDK1.0开发原始AWT API时,只有重量级组件存在,而JDK1.1引入了轻量级组件,Swing组件就是轻量级组件

以AWT和Swing为例,已经有了AWT,引入Swing的目的是什么呢?

前面说到AWT是作为重量级组件,调用的是本地系统的方法来进行构建GUI的,而各个操作系统内部的方法存在一定的差异,使得Java程序开发后无法跨平台使用,这与Java的“一次编译,到处运行”的概念相违背。因此AWT存在一定的局限性,而随后由AWT发展出来的Swing,不仅提供了AWT的所有功能,而且用纯Java代码对AWT的功能进行大幅度的扩展。

AWT绘图

AWT Painting Guidelines

  1. For most programs, all client paint code should be placed within the scope of the component’s paint() method.

  2. Programs may trigger a future call to paint() by invoking repaint(), but shouldn’t call paint() directly.

  3. On components with complex output, repaint() should be invoked with arguments which define only the rectangle that needs updating, rather than the no-arg version, which causes the entire component to be repainted.

  4. Since a call to repaint() results first in a call to update(), which is forwarded to paint() by default, heavyweight components may override update() to do incremental drawing if desired (lightweights do not support incremental drawing)

  5. Extensions of java.awt.Container which override paint() should always invoke super.paint() to ensure children are painted.

  6. Components which render complex output should make smart use of the clip rectangle to narrow the drawing operations to those which intersects with the clip area.

简单而言就是,在AWT中,组件中都有一个paint() 方法,当需要绘图时,应该将绘图代码写在paint() 方法中,当需要刷新或者说重新渲染时(也就是你需要重新执行自己写的paint() 方法时),调用的是repaint() 方法而不是paint() 。调用repaint()时先调用的是update() ,再调用paint()update() 也是组件中的一个方法,我们需要重写update()增量绘图 。当继承了java.awt.Container的时候,在重写paint() 的时候需要调用super.paint() 确保子组件被重绘。(上面的1. 2. 4. 5点)

这样基本就可以完成绘图,但是注意一下细节可以使得程序执行效率更高。当调用repaint() 的时候,如果使用的是无参调用,重画的将是整个组件,而很多程序每次重画只是一小部分,所以应当带上参数,表明需要重画的矩形。

Swing绘图

Swing Painting Guidelines

  1. For Swing components, paint() is always invoked as a result of both system-triggered and app-triggered paint requests; update() is never invoked on Swing components.

  2. Programs may trigger a future call to paint() by invoking repaint(), but shouldn’t call paint() directly.

  3. On components with complex output, repaint() should be invoked with arguments which define only the rectangle that needs updating, rather than the no-arg version, which causes the entire component to be repainted.

  4. Swing’s implementation of paint() factors the call into 3 separate callbacks: paintComponent()
    , paintBorder(), paintChildren(). Extensions of Swing components which wish to implement their own paint code should place this code within the scope of the paintComponent() method ( not within paint()).

  5. Swing introduces two properties to maximize painting efficiency: 1. opaque: will the component paint all its bits or not? 2. optimizedDrawingEnabled: may any of this component’s children overlap?

  6. If a Swing component’s opaque property is set to true, then it is agreeing to paint all of the bits contained within its bounds (this includes clearing it’s own background within paintComponent()), otherwise screen garbage may result.

  7. Setting either the opaque or optimizedDrawingEnabled properties to false on a component will cause more processing on each paint operation, therefore we recommend judicious use of both transparency and overlapping components.

  8. Extensions of Swing components which have UI delegates (including JPanel), should typically invoke super.paintComponent() within their own paintComponent() implementation. Since the UI delegate will take responsibility for clearing the background on opaque components, this will take care of #5.

  9. Swing supports built-in double-buffering via the JComponent doubleBuffered property, and it defaults to true for all Swing components, however setting it to true on a Swing container has the general effect of turning it on for all lightweight descendents of that container, regardless of their individual property settings.

  10. It is strongly recommended that double-buffering be enabled for all Swing components.

  11. Components which render complex output should make smart use of the clip rectangle to narrow the drawing operations to those which intersect with the clip area.

既然了解了AWT的绘图,那我们只需要知道Swing在绘图方面与AWT的区别即可。

首先,AWT调用repaint()的时候需要调用update(),而Swing不需要。且Swing的paint() 分成三个独立的回调函数,分别是paintComponent(), paintBorder(), paintChildren(). 我们重画的代码一般应该写在paintComponent()

其次,Swing包含了两个特性,opaqueoptimizedDrawingEnabled,文中说到不管运用两个特性哪一个,都会导致绘制操作的更多处理,所以建议明智使用这两个特性。(没理解错的话,也就是说它好用,但是不会用的开发者会导致很多bug,真委婉!)

Swing还支持了双缓冲,也就是其内部进行了优化,使我们开发者不需要手动优化。

双缓冲

paint()是在屏幕上绘图,一边绘图一边展示,一旦绘图工作量大,容易出现闪烁,因此有了双缓冲技术。双缓冲就是先生成一张图像,程序对图像进行操作,在该图像显示到屏幕上,解决了闪烁问题。(在AWT中绘图需要手动实现双缓冲,不然也会闪烁)

AWT重写update()和paint(), draw()部分就是你自己想要画的

public void paint(Graphics g) {
        super.paint(g);
        this.draw(g);
}

public void update(Graphics g) {
    if(iBuffer == null)
    {
        iBuffer = createImage(this.getSize().width, this.getSize().height);
        gBuffer = iBuffer.getGraphics();
    }
    gBuffer.setColor(getBackground());
    gBuffer.fillRect(0, 0, this.getSize().width,  this.getSize().height);
    paint(gBuffer);
    g.drawImage(iBuffer, 0, 0, this);
}

Swing重写paintComponent(), draw()部分就是你自己想要画的

public void paintComponent(Graphics g){
    super.paintComponent(g);
    this.draw(g);
}

但是,两种方法绘图还是有点不一样,使用AWT绘图(图2)感觉很奇怪,只能说还是没学透,之后再努力吧!

双缓冲参考于https://blog.csdn.net/kai_wei_zhang/article/details/8120382
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值