使用paintComponent实现Java幻灯片图片延迟

4
我正在制作一个幻灯片程序,可以测量用户在每张幻灯片上花费的时间。该幻灯片会展示几个不同的魔术技巧,并且每个技巧都会展示两次。在重复之间会显示中间图像,在每个技巧之间会显示转换图像。
在技巧的第一次重复中,在下一张图片显示之前,JPanel颜色会在屏幕上闪烁一次。但在同一技巧的第二次重复中,这种情况并不会发生。有可能是图片加载时间太长导致的。
是否有一种简单的方法来预加载这些图片,以便第一次播放时没有延迟?
NOTE: Original code deleted.

EDIT 1/10/2013: 这段代码现在适用于速度较慢的机器。trashgod的第二个补充帮助最大。mouseClick控制结构定期要求SwingWorker类加载40张或更少的当前特技图片,同时将使用的图片设置为null。我简化了代码,只使用了两个Image[]s,并添加了一个main方法,使其独立存在。但仍然需要图片才能运行。现在这是相当基本的代码,如果你想制作一个有很多图片的幻灯片,我认为这是一个不错的开始。
注意:我认为我已经正确实现了SwingWorker,同时仍然使用了多个Image[]s。trashgod和kleopatra,这个实现方式是否符合你们的建议?我最终没有使用publish和process,因为我无法弄清如何将其与索引数组适当地配合使用,但由于StringWorker并不会加载数组中的所有图像(仅限40个),而且代码每20个图像就调用一次StringWorker,所以应该有一个相当好的缓冲区。
EDIT 1/10/2013:通过在我的Mouse类上继承MouseAdapter而不是MouseListener来替换了MouseListener。还修复了我的paintComponent方法,包括对super.paintComponent(g)的调用。为我的SwingWorker类ImageWorker添加了publish/process方法。添加了一个包装类ArrayWrapper,以便将imageArray[i]及其相应的索引int i与publish一起传递到process中。
package slideshow3;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseAdapter;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import java.util.List;

public class SlideShow3 extends JFrame
{
    //screenImage will be replaced with each new slide
    private Image screenImage;
    private int width;
    private int height;

    //Create panel for displaying images using paintComponent()
    private SlideShow3.PaintPanel mainImagePanel;

    //Used for keybinding
    private Action escapeAction;

    //Image array variables for each trick
    private Image[] handCuffs; //h
    private Image[] cups; //c

    //Used to step through the trick arrays one image at a time
    private int h = 0;
    private int c = 0;

    //Used by timeStamp() for documenting time per slide
    private long time0 = 0;
    private long time1;

    public SlideShow3()
    {
        super();

        //Create instance of each Image array
        handCuffs = new Image[50];
        cups = new Image[176];

        //start(handCuffsString);
        start("handCuffs");

        try
        {
            screenImage = ImageIO.read(new File("images/begin1.jpg"));
        }
        catch (IOException nm) 
        {
            System.out.println("begin");
            System.out.println(nm.getMessage());
            System.exit(0);
        }

        /****************************************** 
         * Removes window framing. The next line sets fullscreen mode.
         * Once fullscreen is set width and height are determined for the window
         ******************************************/

        this.setUndecorated(true);
        GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().setFullScreenWindow(this);
        width = this.getWidth();
        height = this.getHeight();

        //Mouse click binding to slide advance control structure
        addMouseListener(new Mouse());

        //Create panel so that I can use key binding which requires JComponent
        mainImagePanel = new PaintPanel();      
        add(mainImagePanel);

        /****************************************** 
         * Key Binding
         * ESC will exit the slideshow
         ******************************************/

        // Key bound AbstractAction items 
        escapeAction = new EscapeAction();

        // Gets the mainImagePanel InputMap and pairs the key to the action
        mainImagePanel.getInputMap().put(KeyStroke.getKeyStroke("ESCAPE"), "doEscapeAction");

        // This line pairs the AbstractAction enterAction to the action "doEnterAction"
        mainImagePanel.getActionMap().put("doEscapeAction", escapeAction);

        /******************************************
         * End Key Binding
         ******************************************/
    }

    public static void main(String[] args) 
    {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() 
            {
                SlideShow3 show = new SlideShow3();
                show.setVisible(true);
            }
        });
    }

    //This method executes a specific SwingWorker class to preload images
    public void start(String e) 
    {
        if(e.equals("handCuffs"))
        {
            new ImageWorker(handCuffs.length, h, e).execute();
        }
        else if(e.equals("cups"))
        {
            new ImageWorker(cups.length, c, e).execute();
        }
    }

    //Stretches and displays images in fullscreen window
    private class PaintPanel extends JPanel
    {
        @Override
        public void paintComponent(Graphics g) 
        { 
            if(screenImage != null)
            {
                super.paintComponent(g);
                g.drawImage(screenImage, 0, 0, width, height, this);
            }  
        }
    }

    /******************************************
     * The following SwingWorker class Pre-loads all necessary images.
     ******************************************/

    private class ArrayWrapper
    {
        private int i;
        private Image image;

        public ArrayWrapper(Image image, int i)
        {
            this.i = i;
            this.image = image;
        }

        public int getIndex()
        {
            return i;
        }

        public Image getImage()
        {
            return image;
        }
    }

    private class ImageWorker extends SwingWorker<Image[], ArrayWrapper>
    {
        private int currentPosition;
        private int arraySize;
        private String trickName;
        private Image[] imageArray;

        public ImageWorker(int arraySize, int currentPosition, String trick)
        {
            super();
            this.currentPosition = currentPosition;
            this.arraySize = arraySize;
            this.trickName = trick;
        }

        @Override
        public Image[] doInBackground()
        {
            imageArray = new Image[arraySize];
            for(int i = currentPosition; i < currentPosition+40 && i < arraySize; i++)
            {
                try 
                {
                    imageArray[i] = ImageIO.read(new File("images/" + trickName + (i+1) + ".jpg"));
                    ArrayWrapper wrapArray = new ArrayWrapper(imageArray[i], i);
                    publish(wrapArray);
                } 
                catch (IOException e) 
                {
                    System.out.println(trickName);
                    System.out.println(e.getMessage());
                    System.exit(0);
                }
            }
            return imageArray;
        }

        @Override
        public void process(List<ArrayWrapper> chunks)
        {
            for(ArrayWrapper element: chunks)
            {
                if(trickName.equals("handCuffs"))
                {
                    handCuffs[element.getIndex()] = element.getImage();
                }
                else if(trickName.equals("cups"))
                {
                    cups[element.getIndex()] = element.getImage();
                }
            }
        }

        @Override
        public void done()
        {
            try
            {
                if(trickName.equals("handCuffs"))
                {
                    handCuffs = get();
                }
                else if(trickName.equals("cups"))
                {
                    cups = get();
                }
            }
            catch(InterruptedException ignore){}
            catch(java.util.concurrent.ExecutionException e)
            {
                String why = null;
                Throwable cause = e.getCause();
                if(cause != null)
                {
                    why = cause.getMessage();
                }
                else
                {
                    why = e.getMessage();
                }
                System.err.println("Error retrieving file: " + why);
            }
        }
    }

     /******************************************
     * End SwingWorker Pre-Loading Classes
     ******************************************/

    //Prints out time spent on each slide
    public void timeStamp()
    {
        time1 = System.currentTimeMillis();
        if(time0 != 0)
        {
            System.out.println(time1 - time0);
        }
        time0 = System.currentTimeMillis();
    } 

    /******************************************
     * User Input Classes for Key Binding Actions and Mouse Click Actions
     ******************************************/

    private class EscapeAction extends AbstractAction
    {
        @Override
        public void actionPerformed(ActionEvent e)
        {
            System.exit(0);
        }
    }

    public class Mouse extends MouseAdapter
    {
        @Override
        public void mouseClicked(MouseEvent e) 
        {
            if(!(h<handCuffs.length) && !(c<cups.length))
            {
                timeStamp();
                System.exit(0);
            }
            else if(h<handCuffs.length)
            {
                timeStamp();
                screenImage = handCuffs[h];
                repaint();
                System.out.print("handCuffs[" + (h+1) + "]\t");
                h++;
                //purge used slides and refresh slide buffer
                if(h == 20 || h == 40)
                {
                    for(int i = 0; i < h; i++)
                    {
                        handCuffs[i] = null;
                    }
                    start("handCuffs");
                }
                if(h == 45)
                {
                    start("cups");
                }
            }
            else if(c<cups.length)
            {
                timeStamp();
                screenImage = cups[c];
                repaint();
                System.out.print("cups[" + (c+1) + "]\t");
                c++;
                //purge used slides and refresh slide buffer
                if(c == 20 || c == 40 || c == 60 || c == 80 || c == 100 || c == 120 || c == 140 || c == 160)
                {
                    for(int i = 0; i < c; i++)
                    {
                        cups[i] = null;
                    }
                    start("cups");
                }
            }
        }
    }

    /******************************************
     * End User Input Classes for Key Binding Actions and Mouse Click Actions
     ******************************************/ 
}

1
警告:你的SwingWorker实现是错误的 - doInBackground不能触碰在视图领域中使用的字段/属性(正如@trashgod在他的评论中已经指出的那样 - 为了强调而重复),完全分离EDT和后台线程是SwingWorker的核心目标。 - kleopatra
@kleopatra 我仍在努力学习这个概念。我正在阅读关于并发和SwingWorker文档的内容。我还没有完成,但如果有其他材料或好的教程,我会非常感激。我想我理解你的意思,但为了清晰起见,例如HandCuffWorker的哪些部分被实现得不正确? - sage88
@kleopatra,嘿,我认为我已经修复了SwingWorker的分离问题,你介意对上面展示的新代码给予反馈吗? - sage88
1
@sage88:进展不错,但缺少publish()/process()并且取决于延迟,您的MouseListenerMouseAdapter?)可以在done()之前访问图像数组。这可能是一个新问题;在您的sscce中,使用一张图片,并用createGraphics()drawString(String.valueOf(i), 5, 20)标记它。赞赏您使用Action - trashgod
@trashgod 谢谢您介绍 MouseAdapter。我读到的文本谈到了 WindowListener 及其关联的 WindowAdapter,所以我想知道是否存在类似于 MouseListener 的东西。我认为您使用 createGraphics() 和 drawString() 是想让我注意到我的 paintComponent 方法缺少 super.paintComponent(g)?我找不到使用数组/列表图像的 publish/process 代码示例。我只能找到一些简单的示例,这些示例在字符串或整数上使用 publish。您知道使用 publish/process 和数组的 SwingWorker 的好例子吗? - sage88
显示剩余2条评论
1个回答

5
这个示例使用List<ImageIcon>作为getImage()返回的图像的cache。使用getResource(),延迟是无法察觉的。下一个和上一个按钮默认绑定到Space键。
补充说明:您可以通过使用javax.swing.Timer的实例条件化地设置按钮的setEnabled()状态来控制导航,例如这里
补充说明:您的第二个示例等待鼠标点击开始读取图像,这是一个不确定的过程,可能会立即返回副本,也可能在repaint()之后才完全完成。相反,使用ImageIO.read()在后台开始读取图像,如此处所示。您可以process()您的List<Image>并显示进度,如此处所见。可以从初始线程启动SwingWorker,在EDT上建立GUI时运行。您可以在第一个图像处理完成后立即显示它。

1
我建议您也可以考虑使用javax.imageio.ImageIO.read(File)来替代读取图像... - Sinkingpoint
2
请编辑你的问题,包括一个sscce,专注于这个问题。FauxImage可能是一个方便的方法,来隔离任何剩余的延迟。 - trashgod
@trashgod,我加了一些使用基于URL的图像来演示问题的自包含代码。我发现每个图像必须不同,否则错误不会显示。 - sage88
1
@sage88:这是一段旅程。请注意,在后台线程中不应直接更新实例变量;相反,每当图像到达时,请使用publish()方法,并将其添加到process()中的相关实例变量中。 - trashgod
@trashgod,嗨,我又整理了一份代码的SSCCE,并想出了如何将所有的SwingWorkers重构为一个单独的类。你能告诉我你认为这个实现是否正确吗? - sage88
显示剩余7条评论

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