How to smoothen scrolling of JFrame in Java

我是研究僧i 提交于 2019-11-27 14:53:54

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.

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;
    }
}

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!