Java:如何在ScrollPane视口上绘制非滚动覆盖层?

8
我想使用ScrollPane在其视口中显示图像,并在图像上叠加网格(或框或任何其他类型的注册/位置标记)。我需要叠加层在滚动时保持固定(意味着图像似乎移动“在”叠加层下)。 我将以固定速率滚动视口中的视图,从而提供平稳的运动,覆盖层是为了提供对视口内某个位置的引用。从概念上讲,可以想象一个大地图在视口中滚动,而视口具有一个矩形,该矩形不会移动(相对于视口本身),它标记了可以根据某些用户操作进行放大的区域。
我认为(但尚未确认)ScrollPane实现有效地处理View的渲染(从备份存储器,不必为每个新的部分暴露重新绘制整个View(甚至ViewPort)),因此希望无需覆盖paint()方法。
我看过LayerPane,虽然还没有掌握它,但似乎这是一种蛮力方法。 ScrollPane是SplitPane的一个组件,并将被移动/调整大小。 我期望必须手动维护ViewPort和LayerPane之间的正确关系,使用绝对定位。 我远非GUI设计和实现的专家,我相信我错过了从显而易见到晦涩优美的各种可能性。
我对任何建议都持开放态度,不仅限于我开始的路径。 我应该(可以)将JPanel作为SplitPane中的一个组件添加,然后添加LayerPane到其中,LayerPane包含我的ScrollPane和覆盖层(另一个JPanel)在LayerPane的不同图层中? ScrollPane是否有直接支持此功能的功能? 我查看了Java Swing教程和API文档,但尚未看到任何内容。
谢谢。
更新:
感谢链接,trashgod。 这比我预想的要容易得多。 不需要LayerPane,GlassPane,xor-based合成...我不知道为什么没有想到尝试重写JViewport.paint()来绘制我的覆盖层 - 但使用下面的代码,我验证了这个概念将给我所需的东西。 只需使用JViewport的子类即可做到我想要的(在本例中,覆盖层矩形位于视口中央,而图像在其下滚动)。 我不是GUI专家,Java API非常广泛; 我不知道你们如何把这些都记在脑海中 - 但是谢谢!
class MyViewport extends JViewport {
    @Override
    public void paint(Graphics g) {
        super.paint(g);
        Graphics2D g2 = (Graphics2D)g;
        g2.setPaint(Color.BLACK);
        g2.drawRect(getWidth()/4,getHeight()/4,getWidth()/2,getHeight()/2);
    }
}

2
另请参见ScrollPanePaint。更新:还请参见玻璃窗格。任何一个都可以帮助您构建sscce - trashgod
1
那个链接正是我需要的正确方向的推动。已经使用解决方案更新了问题。感谢trashgod。 - ags
1个回答

7
通常情况下,如AWT和Swing中的绘图方法所述,“Swing程序应该重写paintComponent()而不是重写paint()”。基于ScrollPanePaint,它在滚动内容之下绘制,以下示例重写了paint()以在滚动内容之上绘制。

ScrollPanePaint

import java.awt.*;
import javax.swing.*;

/**
 * @see https://dev59.com/vGLVa4cB1Zd3GeqPxIim#10097538
 * @see https://stackoverflow.com/a/2846497/230513
 * @see https://dev59.com/_0_Ta4cB1Zd3GeqPEuA_#3518047
 */
public class ScrollPanePaint extends JFrame {

    private static final int TILE = 64;

    public ScrollPanePaint() {
        JViewport viewport = new MyViewport();
        viewport.setView(new MyPanel());
        JScrollPane scrollPane = new JScrollPane();
        scrollPane.setViewport(viewport);
        this.add(scrollPane);
        this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
        this.pack();
        this.setLocationRelativeTo(null);
        this.setVisible(true);
    }

    private static class MyViewport extends JViewport {

        public MyViewport() {
            this.setOpaque(false);
            this.setPreferredSize(new Dimension(6 * TILE, 6 * TILE));
        }

        @Override
        public void paint(Graphics g) {
            super.paint(g);
            g.setColor(Color.blue);
            g.fillRect(TILE, TILE, 3 * TILE, 3 * TILE);
        }
    }

    private static class MyPanel extends JPanel {

        public MyPanel() {
            this.setOpaque(false);
            this.setPreferredSize(new Dimension(9 * TILE, 9 * TILE));
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            g.setColor(Color.lightGray);
            int w = this.getWidth() / TILE + 1;
            int h = this.getHeight() / TILE + 1;
            for (int row = 0; row < h; row++) {
                for (int col = 0; col < w; col++) {
                    if ((row + col) % 2 == 0) {
                        g.fillRect(col * TILE, row * TILE, TILE, TILE);
                    }
                }
            }
        }
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                new ScrollPanePaint();
            }
        });
    }
}

注意:将不透明组件(例如JTable)设置为滚动窗格的视图会导致奇怪的视觉错误,例如在滚动时移动固定的蓝色框。使用setOpaque(false)来修复该问题。


感谢@ags提出这种方法。 - trashgod
@mKorbel:抱歉,如果我没有遵循协议。我错过了什么建议或问题吗? - ags
我看到了一些关于paint()和paintComponent()的模糊提及,但我需要进行一些研究才能理解它们之间的区别以及何时使用每个方法。再次感谢您的指引。 - ags
@ags:抱歉让你等了这么久,我很重视mKorbel的经验。在JComponent中覆盖paint()的一个不好的原因是它会破坏轻量级的从后到前的渲染顺序,但在这种情况下,这是一种特性。 - trashgod

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接