How to smoothen scrolling of JFrame in Java

前端 未结 3 1196
死守一世寂寞
死守一世寂寞 2020-12-03 16:49

I have a JFrame in my Java application that contains a JPanel where I have some drawing objects created at run-time. The problem is while scrolling the JF

相关标签:
3条回答
  • 2020-12-03 17:01

    this idea maybe can to help you

    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;
        }
    }
    
    0 讨论(0)
  • 2020-12-03 17:13

    Part 1

    There are my little mods on mKorbel's answer, thanks to him and Gilbert Le Blanc :

    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.Graphics;
    import java.awt.LayoutManager;
    import java.awt.Rectangle;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.awt.image.BufferedImage;
    import java.lang.ref.WeakReference;
    import java.util.Random;
    import javax.swing.Scrollable;
    import javax.swing.SwingConstants;
    import javax.swing.Timer;
    
    
    /**
     *
     * @author leBenj
     */
    public class GJPanelBufferedImageTileAdapter extends GJPanelBufferedImageAdapter implements Scrollable
    {
        protected BufferedImage _image = null;
    
        protected GIPanelListener _parent = null;
    
        private int TILE_SIZE_W = -1;
    
        private int TILE_SIZE_H = -1;
    
        private int TILE_COUNT_W = 32;
    
        private int TILE_COUNT_H = 32;
    
        private int visibleTiles = 10;
    
        private boolean[][] loading;
    
        private WeakReference<BufferedImage>[][] subs;
    
        private final Random random;
    
        public GJPanelBufferedImageTileAdapter( final GIPanelListener parent , LayoutManager layout , boolean isDoubleBuffered )
        {
            super( parent , layout , isDoubleBuffered );
            this._parent = parent;
            resetTiling();
            random = new Random();
        }
    
        public void resetTiling()
        {
            loading = new boolean[TILE_COUNT_W][TILE_COUNT_H];
            subs = new WeakReference[TILE_COUNT_W][TILE_COUNT_H];
        }
    
        private BufferedImage getTile( int x , int y )
        {
            BufferedImage retour = null;
            if( x < TILE_COUNT_W )
            {
                if( y < TILE_COUNT_H )
                {
                    if( subs[x][y] != null )
                    {
                        retour = subs[x][y].get();
                    }
                }
            }
            return retour;
        }
    
        private void setTile( BufferedImage sub , int x , int y )
        {
            subs[x][y] = new WeakReference<BufferedImage>( sub );
        }
    
        private boolean loadTile( final int x , final int y )
        {
            boolean canPaint = ( getTile( x , y ) != null );
            if( x < TILE_COUNT_W )
            {
                if( y < TILE_COUNT_H )
                {
                    if( !canPaint && !loading[x][y] )
                    {
                        Timer timer = new Timer( random.nextInt( 500 ) , new ActionListener()
                        {
                            @Override
                            public void actionPerformed( ActionEvent e )
                            {
                                BufferedImage sub = _image.getSubimage( x * TILE_SIZE_W , y * TILE_SIZE_H , TILE_SIZE_W , TILE_SIZE_H );
                                setTile( sub , x , y );
                                repaint( x * TILE_SIZE_W , y * TILE_SIZE_H , TILE_SIZE_W , TILE_SIZE_H );
                            }
                        } );
                        timer.setRepeats( false );
                        timer.start();
                    }
                }
            }
            return canPaint;
            }
    
        // using paint(g) instead of paintComponent(g) to start drawing as soon as the panel is ready
        @Override
        protected void paint( Graphics g )
        {
            super.paint( g );
            Rectangle clip = g.getClipBounds();
            int startX = clip.x - ( clip.x % TILE_SIZE_W );
            int startY = clip.y - ( clip.y % TILE_SIZE_H );
            int endX = clip.x + clip.width /*- TILE_SIZE_W*/;
            int endY = clip.y + clip.height /*- TILE_SIZE_H*/;
            for( int x = startX ; x < endX ; x += TILE_SIZE_W )
            {
                for( int y = startY ; y < endY ; y += TILE_SIZE_H )
                {
                    if( loadTile( x / TILE_SIZE_W , y / TILE_SIZE_H ) )
                    {
                        BufferedImage tile = getTile( x / TILE_SIZE_W , y / TILE_SIZE_H );
                        if( tile != null )
                        {
                            g.drawImage( subs[x / TILE_SIZE_W][y / TILE_SIZE_H].get() , x , y , this );
                        }
                    }
                    else
                    {
                        g.setColor( Color.RED );
                        g.fillRect( x , y , TILE_SIZE_W - 1 , TILE_SIZE_H - 1 );
                    }
                }
            }
                g.dispose(); // Without this, the original view area will never be painted
        }
    
        /**
         * @param image the _image to set
         */
        public void setImage( BufferedImage image )
        {
            this._image = image;
            TILE_SIZE_W = _image.getWidth() / TILE_COUNT_W;
            TILE_SIZE_H = _image.getHeight() / TILE_COUNT_H;
            setPreferredSize( new Dimension( TILE_SIZE_W * TILE_COUNT_W , TILE_SIZE_H * TILE_COUNT_H ) );
        }
    
        @Override
        public Dimension getPreferredScrollableViewportSize()
        {
            return new Dimension( visibleTiles * TILE_SIZE_W , visibleTiles * TILE_SIZE_H );
        }
    
        @Override
        public int getScrollableBlockIncrement( Rectangle visibleRect , int orientation , int direction )
        {
            if( orientation == SwingConstants.HORIZONTAL )
            {
                return TILE_SIZE_W * Math.max( 1 , visibleTiles - 1 );
            }
            else
            {
                return TILE_SIZE_H * 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 )
        {
            if( orientation == SwingConstants.HORIZONTAL )
            {
                return TILE_SIZE_W;
            }
            else
            {
                return TILE_SIZE_H;
            }
        }
    
    }
    

    Explanations :

    There was a little problem on right and bottom scrolls : to avoid ArrayOutOfBoundsException, I implemented tests on x and y.

    I splitted the TILE_SIZE in two parts in order to fit the image proportions.

    As I mentioned in the link in my previous comment, coupling the tiles with an array of WeakReference regulates memory usage : I replaced the boolean[][] loaded by WeakReference[][] and implemented the tileGet(x,y) function to get the tile.

    The setImage() methods initializes the class fields such as the size of tiles.

    The this._image field is inherited from a superclass and is implemented as below :

    protected BufferedImage _image = null;
    

    I hope this could help someone.

    0 讨论(0)
  • 2020-12-03 17:17

    Why not put the Graphics2D drawing in a (large) BufferedImage and display it in a label in a scroll-pane? Something like this (animated, 5000x5000px):

    import java.awt.*;
    import java.awt.event.*;
    import java.awt.image.BufferedImage;
    import java.util.Random;
    import javax.swing.*;
    
    public class BigScrollImage {
    
        BigScrollImage() {
            final int x = 5000;
            final int y = 5000;
            final BufferedImage bi = new BufferedImage(x,y,BufferedImage.TYPE_INT_RGB);
            Graphics2D g1 = bi.createGraphics();
    
            g1.setColor(Color.BLACK);
            g1.fillRect(0, 0, x, y);
    
            g1.dispose();
    
            final JLabel label = new JLabel(new ImageIcon(bi));
    
            ActionListener listener = new ActionListener() {
                Random rand = new Random();
                @Override
                public void actionPerformed(ActionEvent ae) {
                    Graphics2D g2 = bi.createGraphics();
                    int x1 = rand.nextInt(x);
                    int x2 = rand.nextInt(x);
                    int y1 = rand.nextInt(y);
                    int y2 = rand.nextInt(y);
                    int r = rand.nextInt(255);
                    int g = rand.nextInt(255);
                    int b = rand.nextInt(255);
                    g2.setColor(new Color(r,g,b));
                    g2.drawLine(x1,y1,x2,y2);
    
                    g2.dispose();
                    label.repaint();
                }
            };
    
            Timer t = new Timer(5,listener);
    
            JScrollPane scroll = new JScrollPane(label);
            JFrame f = new JFrame("Big Scroll");
            f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
    
            f.add(scroll);
            f.pack();
            f.setSize(800, 600);
    
            f.setLocationByPlatform(true);
            f.setVisible(true);
            t.start();
        }
        public static void main(String[] args) {
            SwingUtilities.invokeLater(new Runnable(){
                @Override
                public void run() {
                    new BigScrollImage();
                }
            });
        }
    }
    

    It tries to draw 200 hundred lines per second, and seems to scroll smoothly here.

    0 讨论(0)
提交回复
热议问题