如何在 Java 中平滑 JFrame 的滚动

Posted

技术标签:

【中文标题】如何在 Java 中平滑 JFrame 的滚动【英文标题】:How to smoothen scrolling of JFrame in Java 【发布时间】:2012-07-05 00:39:35 【问题描述】:

我的 Java 应用程序中有一个 JFrame,其中包含一个 JPanel,其中我有一些在运行时创建的绘图对象。问题是在滚动JFrame 以获取大数字时,滚动速度变慢并且滚动条移动不顺畅。请注意我正在使用Graphics 2D 对象并在滚动操作上执行repaint

有什么办法可以平滑JFrame的滚动动作。

这是部分代码

public class DiagramPanel implements MouseListener

    int click=0;
    Point p1;
    Point p2;
    private Dimension panelDimension;
    .... // variables

    public void go() 
        p1 = new Point();
        p2 = new Point();

        JFrame f = new JFrame();
        f.setVisible(true);
        f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        f.setSize(1200,500);
        panelx = new DiaPanel();
        panelx.setOpaque(true);
        panelx.setBackground(Color.white);
        panelx.setAutoscrolls(true);
        panelx.addMouseListener(this);


        JScrollPane scrollPane = new JScrollPane();

        // scrollPane.add(panelx);

        ClassRectangle tempRect = null;
        for (ClassRectangle rect : this.classRectangles) 
            tempRect = rect;
        


        Rectangle rect = new Rectangle();
        rect.setBounds(tempRect.getW() - 100, 0, 1000,
                tempLife.getEndpointY() * 500);

        panelDimension = new Dimension(0,0);
        for (ClassRectangle rectx : classRectangles)
            panelDimension.width=rectx.getW()+300;
        
        for (LifeLine life : lifeLines) 
            panelDimension.height=life.getEndpointY()+300;
        

        scrollPane.setViewportView(panelx);
        panelx.computeVisibleRect(rect);
        JScrollPane scrollPane1 = new JScrollPane(panelx);

        panelx.setPreferredSize(panelDimension);
        panelx.repaint();
        panelx.revalidate();
        p1.x=0;
        p1.y=0;
        p2.y=panelDimension.height;
        p2.x=panelDimension.width;
        f.add( scrollPane1);
        scrollPane.revalidate();
        f.setBackground(Color.white);
    

    public DiagramPanel(ArrayList<Rectangle> classRectangles,
            ArrayList<Pair> pairs, ArrayList<Line> lines,
            ArrayList<Life> meth) 

            // constructing obj of DrawingPanel Here
    

    public class SeqDiaPanel extends JPanel 
        /**
         * 
         */

        private static final long serialVersionUID = 1L;

        public void paintComponent(Graphics g) 
            super.paintComponent(g);
            Graphics2D g2d2 = (Graphics2D) g;
            g2d2.setColor(Color.orange);

            //grid
            for (int i = 0; i < panelDimension.height; i++) 
                g2d2.drawLine(0, 0 + i * 5, panelDimension.width+1000, 0 + i * 5);

            

            for (int i = 0; i < panelDimension.width; i++) 
                g2d2.drawLine(0 + i * 5, 0, 0 + i *5,panelDimension.height+300);
            

            g2d2.setColor(Color.black);

            // objects 
            .......... some objects here


            
            
            // draw Lines

            Stroke drawingStroke = new BasicStroke(2, BasicStroke.CAP_BUTT,
                    BasicStroke.JOIN_BEVEL, 0, new float[]  5 , 0);
            // Stroke drawingStroke = new BasicStroke();
            Graphics2D g2d = (Graphics2D) g;
            g2d.setStroke(drawingStroke);
            for (Line life : lines) 
                g2d.drawLine(life.getStartpointX(), life.getStartpointY(),
                        life.getEndpointX(), life.getEndpointY());
                panelDimension.height=life.getEndpointY()+300;
            

            // draw methodLfe
            for (Object2 ml1 : Obj2) 
                g2d2.fill3DRect(ml1.StartX(), ml1.getMethodStartY(),
                        ml1.getBreadth(), ml1.getEndX(),true);
            

        


    

    // tobeused

    public int calculateWidth(String name)
        Font font = new Font("Serif", Font.BOLD, 12);
         FontMetrics metrics = new FontMetrics(font)

            /**
             * 
             */
            private static final long serialVersionUID = 1L;;
        int tempInt2=SwingUtilities.computeStringWidth( metrics, name);
        tempInt2=tempInt2+10;
        return tempInt2;
    




    /*public class MouseClick implements MouseListener
        Point p =  new Point(0,0);
        @Override
        public void mouseClicked(MouseEvent evnt) 

            p.x=evnt.getX();
            p.y=evnt.getY();
            System.out.println("MouseClicked @"+p.x+":"+p.y);

        

        @Override
        public void mouseEntered(MouseEvent arg0) 
            // TODO Auto-generated method stub

        

        @Override
        public void mouseExited(MouseEvent arg0) 
            // TODO Auto-generated method stub

        

        @Override
        public void mousePressed(MouseEvent arg0) 
            // TODO Auto-generated method stub

        

        @Override
        public void mouseReleased(MouseEvent arg0) 
            // TODO Auto-generated method stub

        

    */

    @Override
    public void mouseClicked(MouseEvent evnt) 
        click++;
        if(click==1)
        //Point p= new Point();
        p1.x=evnt.getX();
        p1.y=evnt.getY();
    //  System.out.println("MouseClicked1 @"+p1.x+":"+p1.y);
        

        if(click==2)
            p2.x=evnt.getX();
            p2.y=evnt.getY();
            //System.out.println("MouseClicked2 @"+p2.x+":"+p2.y);
            click=0;
            if(p1.x<p2.x&&p1.y<p2.y)
            panelx.repaint();
            
            else

            

        /*else
            p1.x=0;
            p1.y=0;
            p2.x=panelDimension.width+500;
            p2.y=panelDimension.height+700;
        */
    

    @Override
    public void mouseEntered(MouseEvent arg0) 
        // TODO Auto-generated method stub

    

    @Override
    public void mouseExited(MouseEvent arg0) 
        // TODO Auto-generated method stub

    

    @Override
    public void mousePressed(MouseEvent arg0) 
        // TODO Auto-generated method stub

    

    @Override
    public void mouseReleased(MouseEvent arg0) 
        // TODO Auto-generated method stub

    

【问题讨论】:

你的意思是当你滚动的时候,你是在调用repaint?发布SSCCE 以获得更好的帮助 我猜你需要使用“双缓冲”。搜索这个词或发布一些代码以获得进一步的帮助。 感谢您的代码,但它无法编译且不可执行。无论如何,请遵循 @mKorbel 的建议并考虑 Graphics 对象的剪辑边界。在您的绘制方法中,您重新绘制整个组件,而您应该尝试仅绘制剪切区域。另外,我看不到您在滚动期间调用重绘的位置。 mKorbel 和我现在都发布了一个 SSCCE 作为答案。 如您所见,代码以复制/粘贴的方式运行,而且它们(非常)短。请阅读@GuillaumePolet 链接的文档 @GuillaumePolet 是的,它实际上有 5 个类是这段代码的一部分,所以我试着用简短的方式传达代码 【参考方案1】:

第 1 部分

感谢他和 Gilbert Le Blanc,我对 mKorbel 的回答做了一些小改动:

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;
        
    


解释:

右侧和底部滚动有点问题:为了避免 ArrayOutOfBoundsException,我在 x 和 y 上实现了测试。

我将TILE_SIZE 分成两部分以适应图像比例。

正如我在之前评论的链接中提到的,将磁贴与 WeakReference 数组耦合可调节内存使用:我将 boolean[][] loaded 替换为 WeakReference[][] 并实现了 tileGet(x,y) 函数来获取磁贴。

setImage() 方法初始化类字段,例如图块的大小。

this._image 字段继承自超类,实现如下:

protected BufferedImage _image = null;

我希望这可以帮助某人。

【讨论】:

我发现有一种解决方案可以减少瓷砖上的“切换”:[[***.com/questions/4781100/…【参考方案2】:

为什么不将Graphics2D 绘图放在(大)BufferedImage 中并将其显示在滚动窗格的标签中?像这样的东西(动画,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();
            
        );
    

它尝试每秒绘制 20000 条线,并且似乎在此处平滑滚动。

【讨论】:

【参考方案3】:

这个想法或许能帮到你

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;
    

【讨论】:

不错的答案。但显然我不能再+1(检查)9 小时! :( 对paintComponent(someRectengle) 或paintImediatelly(someRectsngle) 的体验不好,我不喜欢以某种方式打扰... +1 基本的东西 ..滴答,滴答,滴答。哦,现在可以了。 +1 :) 正如 Gilbert Le Blanc 向我指出的那样,这是一个不错的解决方案(请参阅:[[***.com/questions/13386332/…) +1 非常棒,尤其是对于一些纯 Java 地图图块 :) 并且看起来非常高效

以上是关于如何在 Java 中平滑 JFrame 的滚动的主要内容,如果未能解决你的问题,请参考以下文章

在 Android 中平滑滚动画布

H5页面在ios中平滑滚动

在 UICollectionViewCell 内的 UICollectionView 中平滑滚动

css 在jQuery中平滑页面滚动

在纯 CSS 视差中平滑滚动

在 XCode 中平滑滚动视图和表格视图之间的滚动过渡