web-dev-qa-db-fra.com

Affichage de l'animation GIF en Java

Bonjour, j'écris une application graphique sur Java 1.6 avec Swing.

J'ai un écran contextuel qui devrait afficher une animation gif pendant le chargement de mon interface graphique Swing, ainsi qu'un peu après.

Mon écran contextuel est un JDialog. L'animation doit être affichée sur un JLabel ajouté au Jdialog de la manière suivante:

ImageIcon myImgIcon = getMyImgIcon();
JLabel imageLbl = new JLabel(myImgIcon);
add(imageLbl, BorderLayout.CENTER); 

Maintenant, l’animation ne s’affiche que lorsque l’interface graphique a été chargée. Je pense que pendant le chargement de l'interface graphique (opération lourde dans mon application), l'EDT est tellement occupé qu'il ne peut pas exécuter l'animation.

Voir Comment afficher une image GIF animée à l'aide d'un fil .

Le problème, c’est qu’il serait faux pour moi de charger l’interface graphique sur un autre thread (pas avec l’EDT), donc je ne sais pas comment résoudre le problème.

Est-ce que quelqu'un a une idée?

13
whomaniac

Vous devez juste libérer le fil EDT de certaines tâches lourdes et les faire dans un fil séparé. Dans ce cas, l'animation gif fonctionnera avec d'autres processus en cours d'exécution.

Vous pouvez également créer votre interface d’application dans un thread séparé (oui oui, pas à l’intérieur de l’EDT), mais seulement jusqu’à ce que vous l’affichez. Ensuite, vous devez faire toutes les modifications dans l’EDT, sinon vous pourriez rencontrer beaucoup de problèmes. 

Vous pouvez également charger ultérieurement plus d'éléments de l'interface utilisateur dans un thread séparé. Assurez-vous simplement de les ajouter aux cadres/conteneurs affichés dans EDT - c'est la chose la plus importante.

Voici un petit exemple de chargement d'interface "lourde":

public static void main ( String[] args ) throws InvocationTargetException, InterruptedException
{
    // Main window

    final JFrame frame = new JFrame ();

    final JPanel panel = new JPanel ( new FlowLayout ( FlowLayout.LEFT, 5, 5 ) )
    {
        public Dimension getPreferredSize ()
        {
            Dimension ps = super.getPreferredSize ();
            ps.width = 0;
            return ps;
        }
    };
    frame.add ( new JScrollPane ( panel ) );

    frame.setSize ( 600, 500 );
    frame.setDefaultCloseOperation ( JFrame.EXIT_ON_CLOSE );
    frame.setLocationRelativeTo ( null );

    SwingUtilities.invokeAndWait ( new Runnable ()
    {
        public void run ()
        {
            frame.setVisible ( true );
        }
    } );

    // Load dialog

    final JDialog load = new JDialog ( frame );

    JPanel panel2 = new JPanel ( new BorderLayout () );
    panel2.setBorder ( BorderFactory.createEmptyBorder ( 15, 15, 15, 15 ) );
    load.add ( panel2 );

    final JProgressBar progressBar = new JProgressBar ( 0, 100 );
    panel2.add ( progressBar );

    load.setModal ( false );
    load.pack ();
    load.setLocationRelativeTo ( frame );

    SwingUtilities.invokeAndWait ( new Runnable ()
    {
        public void run ()
        {
            load.setVisible ( true );
        }
    } );

    // Heavy task (takes approx. 10 seconds + some time on buttons creation) 

    for ( int i = 0; i < 100; i++ )
    {
        Thread.sleep ( 100 );

        final JButton button = new JButton ( "Button" + i );
        final int finalI = i;

        // Updating panel and progress in EDT
        SwingUtilities.invokeLater ( new Runnable ()
        {
            public void run ()
            {
                panel.add ( button );
                button.revalidate ();
                progressBar.setValue ( finalI );
            }
        } );
    }
}

Comme vous pouvez le constater, toutes les opérations de mise à jour d'interface sont effectuées dans EDT, tout le reste s'exécute dans l'autre thread. 

Notez également que le fil principal n'est pas un fil EDT, nous pouvons donc faire quelque chose de lourd immédiatement.

Dans certains cas, il n'est pas nécessaire d'afficher immédiatement les parties d'interface chargées. Vous pouvez donc les ajouter toutes ensemble à la fin de l'opération "lourde". Cela économisera un peu de temps de chargement et rendra le code d'initialisation beaucoup plus simple.

Brève explication sur l'EDT et ce que j'ai dit dans la réponse ...

... c'est quelque chose que j'ai trouvé après trois ans sous Swing L & F et de nombreuses applications basées sur Swing. J'ai creusé beaucoup de sources Swing et trouvé beaucoup de choses intéressantes qui ne sont pas très connues.

Comme vous le savez, l’idée d’un seul thread pour les mises à jour d’interface (son EDT en Swing) consiste à conserver chaque composant séparé visual updates (et ses événements) dans une file d’attente et à les exécuter un par un dans ce thread. . Cela est principalement nécessaire pour éviter les problèmes de peinture car chaque composant à l'intérieur d'un seul cadre est peint sur l'image single qui est conservée en mémoire. L'ordre de la peinture est strict pour qu'un composant n'en écrase pas un autre sur l'image finale. L'ordre de peinture dépend de l'arborescence des composants créée en ajoutant des composants ou des conteneurs à l'intérieur d'un autre conteneur (opération de base lors de la création d'une interface d'application sur Swing). 

Pour résumer, vous devez conserver toutes les mises à jour visual (méthodes/opérations qui pourraient les provoquer) à l'intérieur de l'EDT. Toute autre opération pourrait être effectuée en dehors de l'EDT - vous pouvez par exemple préparer l'interface de l'application en dehors de l'EDT (encore une fois, sauf si vous ajoutez/supprimez/déplacez un composant dans un conteneur déjà visible).

Cela pourrait néanmoins poser des problèmes internes dans des cas très très rares. Il y a longtemps que nous avons eu une bonne discussion sur cette question:
http://www.velocityreviews.com/forums/t707173-why-does-jdk-1-6-recommend-creating-swing-components-on-the-edt.html

Pour être bref: depuis la 6ème version du JDK, Sun a déclaré dans la documentation que même la création de composants Swing devait être effectuée dans EDT pour éviter possible problèmes. Ils peuvent apparaître dans certains cas spécifiques lors de la création d'interfaces lourdes en raison d'événements se produisant pendant la création des composants.

Quoi qu'il en soit, je dirais que dans certains cas, vous pouvez créer votre interface en dehors de l'EDT pour éviter que le chargeur/l'application ne soit bloqué. Dans d'autres cas, quand il est indifférent que l'application soit bloquée pendant le temps de création de l'interface, vous devez utiliser EDT. Et je ne peux rien dire de plus spécifique puisque tout dépend de votre cas ...

7
Mikle Garin

Peut-être essayez-vous de créer une animation qui doit être lue juste au début de votre application, sans interférer avec les événements ou les composants à venir. Vous voudrez peut-être essayer - écrans de démarrage . Lire à ce sujet à partir d'ici: http://docs.Oracle.com/javase/tutorial/uiswing/misc/splashscreen.html

Dans le lien ci-dessus, il montre l'utilisation d'une classe nommée SplashScreen qui est simplement dérivée de la classe Frame. Donc, le mécanisme est comme ça: vous affichez un cadre séparé (écran de démarrage, vos animations vont ici) et après un certain temps, votre application principale est lancée.

3
Juvanis

La classe 'ImageIcon' vous permet de charger des animations GIF. Je charge l'image avec 'getResource ()'. Pour ce faire, je demande normalement à la classe d’URL de transmettre le chemin du fichier. Le chemin ne doit pas nécessairement être nécessaire sur une machine distante, comme le suggère l'URL du nom. 

URL url = this.getClass().getResource(path);
Icon myImgIcon = new ImageIcon(url);
JLabel imageLbl = new JLabel(myImgIcon);
component.add(imageLbl, BorderLayout.CENTER);

chemin sera le chemin du gif à l'intérieur du dossier de la classe.

Références: http://docs.Oracle.com/javase/tutorial/uiswing/components/icon.html#getresource

1
Tony