5

我有一个带有 JPanel 的 JSrollPane 作为 ViewPort 组件。在这个 JPanel 上,我使用 paintComponent 绘制一个 64x64px 正方形的网格。JPanel 相当大,28'672 像素 x 14'336 像素,网格仍然是立即绘制的,一切看起来都很好。问题是垂直或水平滚动会导致 CPU 使用率上升相当高,我滚动得越快它就越高。滚动时 CPU 使用率高达 35-50%。滚动相同大小的 JPanel 而不在其上绘制网格,占用的 CPU 很少,因此网格肯定是问题的原因。这个网格是我计划在滚动窗格中做的最基本的部分,如果它现在表现不佳,我担心添加更多“内容”后它将无法使用。

我的问题为什么它使用这么多 CPU 来滚动这个网格,每次滚动条的位置发生变化时,网格都会重新绘制吗?有没有更好或更有效的方法来绘制可滚动网格?

我有一个想法,只绘制可见区域的网格(通过坐标),然后在移动滚动条时重绘该可见区域,但这会大量调用重绘。如果可能的话,我想在启动时绘制整个网格,然后只在命令上重新绘制。

这是我的 JPanel 网格的准系统工作示例。

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ScrollPaneConstants;
import javax.swing.border.EmptyBorder;

public class GridTest extends JFrame
{
    static JScrollPane scrollPane;
    static JPanel contentPane,gridPane;

    public static void main(String[] args) {
        GridTest frame = new GridTest();
        frame.setVisible(true);
    }

    public GridTest(){
        setTitle("Grid Test");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setResizable(false);
        setBounds(300, 100, 531, 483);

        contentPane = new JPanel();
        contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
        setContentPane(contentPane);
        contentPane.setLayout(null);

        scrollPane = new JScrollPane();
        scrollPane.setBounds(0, 0, 526, 452);
        scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
        scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
        contentPane.add(scrollPane);
        gridPane = new JPanel() {
        public void paintComponent( Graphics g ){
            super.paintComponent(g);
            drawGrid(g);
            g.dispose();
         }};
        Dimension gridPaneSize = new Dimension(28672,14336);
        //Dimension gridPaneSize = new Dimension(4096,4096);
    gridPane.setBackground(Color.BLACK);
    gridPane.setPreferredSize(gridPaneSize);
    scrollPane.setViewportView(gridPane);
    }
    public static void drawGrid(Graphics g)
    {
        int width = gridPane.getWidth();
        int height = gridPane.getHeight();

        g.setColor(Color.gray);
        // draw horizontal long lines
        for(int h = 0; h < height; h+=64){
            g.drawLine(0, h, width, h);
        }
        // draw even grid vert lines
        for(int w = 0; w < width; w+=64){
            for(int h = 0; h < height; h+=128){
                g.drawLine(w, h, w, h+64);
            }
        }
        // draw odd grid vert lines
        for(int w = 32; w < width; w+=64){
            for(int h = 64; h < height; h+=128){
                g.drawLine(w, h, w, h+64);
            }
        }
    }
}

编辑:在我对问题的回答中,此代码的更新/固定版本如下。

4

2 回答 2

4

例如

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.*;

public class TilePainter extends JPanel implements Scrollable {

    private static final long serialVersionUID = 1L;

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

            @Override
            public void run() {
                JFrame frame = new JFrame("Tiles");
                frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
                frame.getContentPane().add(new JScrollPane(new TilePainter()));
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }
    private final int TILE_SIZE = 50;
    private final int TILE_COUNT = 100;
    private final int visibleTiles = 10;
    private final boolean[][] loaded;
    private final boolean[][] loading;
    private final Random random;

    public TilePainter() {
        setPreferredSize(new Dimension(TILE_SIZE * TILE_COUNT, TILE_SIZE * TILE_COUNT));
        loaded = new boolean[TILE_COUNT][TILE_COUNT];
        loading = new boolean[TILE_COUNT][TILE_COUNT];
        random = new Random();
    }

    public boolean getTile(final int x, final int y) {
        boolean canPaint = loaded[x][y];
        if (!canPaint && !loading[x][y]) {
            loading[x][y] = true;
            Timer timer = new Timer(random.nextInt(500),
                    new ActionListener() {

                        @Override
                        public void actionPerformed(ActionEvent e) {
                            loaded[x][y] = true;
                            repaint(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
                        }
                    });
            timer.setRepeats(false);
            timer.start();
        }
        return canPaint;
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Rectangle clip = g.getClipBounds();
        int startX = clip.x - (clip.x % TILE_SIZE);
        int startY = clip.y - (clip.y % TILE_SIZE);
        for (int x = startX; x < clip.x + clip.width; x += TILE_SIZE) {
            for (int y = startY; y < clip.y + clip.height; y += TILE_SIZE) {
                if (getTile(x / TILE_SIZE, y / TILE_SIZE)) {
                    g.setColor(Color.GREEN);
                } else {
                    g.setColor(Color.RED);
                }
                g.fillRect(x, y, TILE_SIZE - 1, TILE_SIZE - 1);
            }
        }
    }

    @Override
    public Dimension getPreferredScrollableViewportSize() {
        return new Dimension(visibleTiles * TILE_SIZE, visibleTiles * TILE_SIZE);
    }

    @Override
    public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
        return TILE_SIZE * Math.max(1, visibleTiles - 1);
    }

    @Override
    public boolean getScrollableTracksViewportHeight() {
        return false;
    }

    @Override
    public boolean getScrollableTracksViewportWidth() {
        return false;
    }

    @Override
    public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
        return TILE_SIZE;
    }
}

编辑

关于Rectagle.intersects(Rectagle)by(HFOEhere)的好例子Encephalopathic来自old.sun.forum57

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JViewport;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class IsRectVisible {

    private static void createAndShowUI() {
        JFrame frame = new JFrame("IsRectVisible");
        frame.getContentPane().add(new IsRectVisibleGui());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        java.awt.EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                createAndShowUI();
            }
        });
    }
}

class IsRectVisibleGui extends JPanel {

    public static final Rectangle RECT = new Rectangle(250, 200, 100, 100);
    public static final Dimension INNER_PANEL_SIZE = new Dimension(600, 800);
    private static final Dimension SCROLLPANE_SIZE = new Dimension(250, 300);
    private static final String NOT_VISIBLE = "Not Visible";
    private static final String VISIBLE = "Visible";
    private static final long serialVersionUID = 1L;
    private InnerPanel innerPanel = new InnerPanel();
    private JViewport viewport = new JViewport();
    private JLabel statusLabel = new JLabel(NOT_VISIBLE);

    IsRectVisibleGui() {
        JScrollPane scrollpane = new JScrollPane();
        scrollpane.setViewport(viewport);
        viewport.add(innerPanel);
        scrollpane.setPreferredSize(SCROLLPANE_SIZE);
        viewport.addChangeListener(new ChangeListener() {
            @Override
            public void stateChanged(ChangeEvent e) {
                Rectangle viewRect = viewport.getViewRect();
                if (viewRect.intersects(RECT)) {
                    statusLabel.setText(VISIBLE);
                } else {
                    statusLabel.setText(NOT_VISIBLE);
                }
            }
        });
        setLayout(new BorderLayout());
        add(scrollpane, BorderLayout.CENTER);
        add(statusLabel, BorderLayout.SOUTH);
    }

    class InnerPanel extends JPanel {

        private static final long serialVersionUID = 1L;

        InnerPanel() {
            setPreferredSize(INNER_PANEL_SIZE);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2 = (Graphics2D) g;
            g2.setColor(Color.red);
            g2.setStroke(new BasicStroke(4));
            g2.draw(RECT);
        }
    }
}
于 2013-03-02T18:40:10.157 回答
1

感谢上面 mKorbel 的回答,我能够使用 getViewRect() 修复代码。

对于将来可能想做类似事情的任何人,我得到一个视口的矩形,然后使用 for 循环来检查 viewrect 的 x/y(顶角)是否在一个 tile (1024x1024) 增量内。如果是,那么我从 x/y 增量瓦片开始绘制网格方块,直到视口 +1 瓦片 (1024) 的宽度/高度。一次快速滑动从上到下滚动只使用大约 5% 的 CPU,这是可以接受的。

这是 JPanel 可滚动网格的更新代码:

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

public class GridTest extends JFrame
{
    private static final long serialVersionUID = 6632092242560855625L;
    static JPanel gridPane;
    static JViewport view;

    public static void main(String[] args) {
        GridTest frame = new GridTest();
        frame.setVisible(true);
    }

    public GridTest(){
        setTitle("Grid Test");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setSize(600,600);
        setLocationRelativeTo(null);
        setLayout(new BorderLayout());

        JScrollPane scrollPane = new JScrollPane();
        setContentPane(scrollPane);
        view = scrollPane.getViewport();
        gridPane = new JPanel() {
            private static final long serialVersionUID = 2900962087641689502L;
            public void paintComponent( Graphics g ){
            super.paintComponent(g);
            drawGrid(g, view.getViewRect());
         }};
        Dimension paneSize = new Dimension(28672,14336);
        gridPane.setPreferredSize(paneSize);
        gridPane.setBackground(Color.gray);
        scrollPane.setViewportView(gridPane);
    }

    static void drawGrid(Graphics g, Rectangle view){
        int wMax = gridPane.getWidth();
        int hMax = gridPane.getHeight();

        g.setColor(Color.black);
        Rectangle tile = view;
        // set corner tile x/y to the tile increment.
        for(int w = 0; w < wMax; w+= 1024)
        {
            if(tile.x >= w && tile.x < w+1024) { tile.x = (w); }
            for(int h = 0; h < hMax; h+= 1024)
            {
                if(tile.y >= h && tile.y < h+1024) { tile.y = (h); }
            }
        }
        int xTop = tile.x;
        int yTop = tile.y;
        int width = (int) tile.getWidth();
        int height = (int) tile.getHeight();
        width = xTop + width;
        height = yTop + height;
        // Draw even grid squares within visible tiles, starting at top corner tile.
        for(int w = xTop; w < width+1024; w+=64)
        {
            for(int h = yTop; h < height+1024; h+=128)
            {
                g.fillRect(w+1, h+1, 63, 63);
            }
        }
        // Draw odd grid squares within visible tiles, starting at top corner tile.
        for(int w = xTop-32; w < width+1024; w+=64)
        {
            for(int h = yTop+64; h < height+1024; h+=128)
            {
                g.fillRect(w+1, h+1, 63, 63);
            }
        }
    }
}
于 2013-03-03T18:20:17.380 回答