CardLayout showing two panels, flashing

2019-01-12 08:16发布

问题:

I'm trying to use CardLayout to show two JPanels, a main menu, and a controls screen. When I add two JPanels to my cards JPanel, it just shows two with flashing images. Here is my code:

package main;

public class MazeGame {

// Layout

public static JPanel cards = new JPanel();

// Window

public static JFrame window;
public static String windowLabel = "2D Maze Game - Before Alpha";

// Window Dimensions and Location

public static int WIDTH = 600;
public static int HEIGHT = 600;

public static Component center = null;
public static int exit = 3;

public static void main(String[] args) {
    window = new JFrame(windowLabel);

    window.setSize(new Dimension(WIDTH, HEIGHT));
    window.setResizable(false);
    window.setLocationRelativeTo(center);
    window.setDefaultCloseOperation(exit);

    cards.setLayout(new CardLayout());

    cards.add(new MazeGamePanel(), "main");
    cards.add(new MazeControlsPanel(), "controls");
    window.add(cards);

    CardLayout cl = (CardLayout) cards.getLayout();
    cl.show(cards, "main");



    window.setVisible(true);
}

}

MazeGamePanel:

public class MazeGamePanel extends JPanel implements Runnable {

private static final long serialVersionUID = 1L;


// Timer

public Timer timer;
// Font

public Font bitTrip;

public Thread thread;

public BufferedImage canvas;
public Graphics2D g;

public boolean running;

public int HEIGHT = MazeGame.HEIGHT;

public int WIDTH = MazeGame.WIDTH;

public int FPS = 30;

public int opacity = 255;

public int selectedOption = 0;
public String option1 = "Play";
public String option2 = "Controls";
public String option3 = "Quit";

public MazeGamePanel() {
    this.setFocusable(true);
    this.requestFocus();

    addKeyListener(new MazeGameKeyListener());

    try {
        bitTrip = Font.createFont(Font.TRUETYPE_FONT, new File(
                "res/font/BitTrip7.TTF"));
    } catch (FontFormatException | IOException e) {
        e.printStackTrace();
    }

    /**
    ActionListener action = new ActionListener () {

        public void actionPerformed (ActionEvent e) {
        if(opacity != 0) {
            opacity--;
        } else {
            timer.stop();
            opacity = 0;
        }
    }
};  


timer = new Timer(10, action);
timer.setInitialDelay(0);
timer.start();
    */
}

public void addNotify() {
    super.addNotify();
    if (thread == null) {
        thread = new Thread(this);
        thread.start();
    }
}

public void run() {
    running = true;
    canvas = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);

    g = (Graphics2D) canvas.getGraphics();

    long startTime = 0;
    long millis = 0;
    long waitTime = 0;

    long targetTime = 1000 / FPS;

    while (running) {

        startTime = System.nanoTime();

        update();
        render();
        draw();

        millis = (System.nanoTime() - startTime) / 1000000;

        waitTime = targetTime - millis;

        try {
            Thread.sleep(waitTime);

        } catch (Exception e) {

        }
    }

}

// TODO
public void render() {
    g.setColor(Color.WHITE);
    g.fillRect(0, 0, WIDTH, HEIGHT);

    bitTrip = bitTrip.deriveFont(40F);
    g.setFont(bitTrip);

    if (selectedOption == 0) {

        //Play
        g.setColor(Color.BLACK);
        g.drawString(option1, WIDTH / 2  - 200, HEIGHT / 2);

        //Controls
        g.setColor(Color.GRAY);
        g.drawString(option2, WIDTH / 2  - 200, HEIGHT / 2 + 50);

        //Quit
        g.setColor(Color.GRAY);
        g.drawString(option3, WIDTH / 2  - 200, HEIGHT / 2 + 100);

    } else if (selectedOption == 1) {

        //Play
        g.setColor(Color.GRAY);
        g.drawString(option1, WIDTH / 2  - 200, HEIGHT / 2);

        //Controls
        g.setColor(Color.BLACK);
        g.drawString(option2, WIDTH / 2  - 200, HEIGHT / 2 + 50);

        //Quit
        g.setColor(Color.GRAY);
        g.drawString(option3, WIDTH / 2  - 200, HEIGHT / 2 + 100);


    } else if (selectedOption == 2) {
        //Play
        g.setColor(Color.GRAY);
        g.drawString(option1, WIDTH / 2  - 200, HEIGHT / 2);

        //Controls
        g.setColor(Color.GRAY);
        g.drawString(option2, WIDTH / 2  - 200, HEIGHT / 2 + 50);

        //Quit
        g.setColor(Color.BLACK);
        g.drawString(option3, WIDTH / 2  - 200, HEIGHT / 2 + 100);
    }


    //g.setColor(new Color(0, 0, 0, opacity));
    //g.fillRect(0, 0, WIDTH, HEIGHT);
}

public void update() {

}

public void draw() {
    Graphics g2 = this.getGraphics();
    g2.drawImage(canvas, 0, 0, null);
    g2.dispose();

}

private class MazeGameKeyListener extends KeyAdapter {

    public void keyPressed(KeyEvent e) {

        if (e.getKeyCode() == KeyEvent.VK_UP) {
            if (selectedOption == 1) {
                selectedOption = 0;
            } else if (selectedOption == 2) {
                selectedOption = 1;
            }
        }

        if (e.getKeyCode() == KeyEvent.VK_DOWN) {
            if (selectedOption == 0) {
                selectedOption = 1;
            } else if (selectedOption == 1) {
                selectedOption = 2;
            }
        }

        if(e.getKeyCode() == KeyEvent.VK_ENTER) {
            if(selectedOption == 1) {
                MazeGame.window.removeAll();
                MazeGame.window.add(new MazeControlsPanel());
                MazeGame.window.validate();
            }
        }
    }
}

}

MazeControlsPanel:

package screens;

public class MazeControlsPanel extends JPanel implements Runnable {

private static final long serialVersionUID = 1L;

// Font

public Font bitTrip;

public Thread thread;

public BufferedImage canvas;
public Graphics2D g;

public boolean running;

public int HEIGHT = MazeGame.HEIGHT;

public int WIDTH = MazeGame.WIDTH;

public int FPS = 30;

public int opacity = 255;

public int selectedOption = 0;

public MazeControlsPanel() {
    this.setFocusable(true);
    this.requestFocus();

    addKeyListener(new MazeControlsKeyListener());

    try {
        bitTrip = Font.createFont(Font.TRUETYPE_FONT, new File(
                "res/font/BitTrip7.TTF"));
    } catch (FontFormatException | IOException e) {
        e.printStackTrace();
    }

    /**
    final Timer timer = new Timer();
    timer.scheduleAtFixedRate(new TimerTask() {

        public void run() {
            if (opacity != 0) {
                opacity--;
            } else {
                timer.cancel();
                opacity = 0;
            }
        }

    }, 0, 4);

*/ }

public void addNotify() {
    super.addNotify();
    if (thread == null) {
        thread = new Thread(this);
        thread.start();
    }
}

public void run() {
    running = true;
    canvas = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);

    g = (Graphics2D) canvas.getGraphics();

    long startTime = 0;
    long millis = 0;
    long waitTime = 0;

    long targetTime = 1000 / FPS;

    while (running) {

        startTime = System.nanoTime();

        update();
        render();
        draw();

        millis = (System.nanoTime() - startTime) / 1000000;

        waitTime = targetTime - millis;

        try {
            Thread.sleep(waitTime);

        } catch (Exception e) {

        }
    }

}

// TODO
public void render() {
    g.setColor(Color.WHITE);
    g.fillRect(0, 0, WIDTH, HEIGHT);

    bitTrip = bitTrip.deriveFont(40F);
    g.setFont(bitTrip);

    // Quit
    g.setColor(Color.BLACK);
    g.drawString("Main Menu", WIDTH / 2 - 200, HEIGHT / 2 + 100);

    //g.setColor(new Color(0, 0, 0, opacity));
    //g.fillRect(0, 0, WIDTH, HEIGHT);
}

public void update() {

}

public void draw() {
    Graphics g2 = this.getGraphics();
    g2.drawImage(canvas, 0, 0, null);
    g2.dispose();

}

private class MazeControlsKeyListener extends KeyAdapter {

    public void keyPressed(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_ENTER) {
        }
    }
}

}

回答1:

Here's a problem:

final Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {

Don't use a java.util.Timer in a Swing program as you will have threading problems. Instead use a Swing Timer.

Also, you're making Swing calls in background threads and using Graphics object obtained by calling getGraphics() on a component, two no-nos for Swing programs.


EDIT
Here is a program that I worked on that swaps components with a CardLayout, but fades one component out as it fades the other one in. What it does:

  • The program adds all the swapping components to the CardLayout using JPanel.
  • It also adds a single SwappingImgPanel, a JPanel created to draw two images, one of the component that is fading out, and one of the component that is fading in.
  • When you swap components, you create images of the two components, the one currently visible, and the one that will next be visible.
  • You send the images to the SwappingImgPanel instance
  • You call swap() on the SwappingImgPanel instance.
  • The SwappingImgPanel will then draw both images but uses a Swing Timer to change the Graphic object's composite value. This is what causes an image to be partially visible.
  • When the SwappingImgPanel's Timer is done, a done() method is called which sets the SwappingImgPanel's State to State.DONE.
  • The main GUI is listening to the SwappingImgPanel's state value, and when it achieves State.DONE, the main GUI shows the actual next component (and not an image of it).

import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.HashMap;
import java.util.Map;
import javax.swing.*;


@SuppressWarnings("serial")
public class DimmingPanelSwaps extends JPanel {
   private static final int DELTA_TIME = 10;
   private static final int ELAPSED_TIME = 3000;
   private static final String SWAPPING_IMG_PANEL = "swapping img panel";
   private CardLayout cardlayout = new CardLayout();
   private JPanel cardHolderPanel = new JPanel(cardlayout);
   private DefaultComboBoxModel<String> comboModel = new DefaultComboBoxModel<>();
   private JComboBox<String> cardCombo = new JComboBox<>(comboModel);
   private Map<String, JComponent> componentMap = new HashMap<String, JComponent>();
   private String key = "";
   private SwappingImgPanel swappingImgPanel = new SwappingImgPanel(DELTA_TIME, ELAPSED_TIME);

   public DimmingPanelSwaps() {
      registerComponent(createComponentOne(), "one");
      registerComponent(createComponentTwo(), "two");      
      registerComponent(createComponentThree(), "three");
      registerComponent(createComponentFour(), "four");
      key = "one";
      cardHolderPanel.add(swappingImgPanel, SWAPPING_IMG_PANEL);

      JPanel southPanel = new JPanel();
      southPanel.add(cardCombo);

      setLayout(new BorderLayout());
      add(cardHolderPanel, BorderLayout.CENTER);
      add(southPanel, BorderLayout.SOUTH);

      swappingImgPanel.addPropertyChangeListener(new PropertyChangeListener() {

         @Override
         public void propertyChange(PropertyChangeEvent pcEvt) {
            if (pcEvt.getNewValue() == State.DONE) {
               cardlayout.show(cardHolderPanel, key);
               cardCombo.setEnabled(true);
            }
         }
      });

      cardCombo.addActionListener(new CardComboListener());
   }

   private JPanel createComponentFour() {
      int rows = 4;
      int cols = 4;
      int gap = 5;
      int tfColumns = 8;
      JPanel panel = new JPanel(new GridLayout(rows, cols, gap, gap));
      for (int i = 0; i < rows * cols; i++) {
         JTextField textField = new JTextField(tfColumns);
         JPanel tfPanel = new JPanel();
         tfPanel.add(textField);
         panel.add(tfPanel);
      }
      return panel;
   }

   private JLabel createComponentThree() {
      int biWidth = 200;
      BufferedImage img = new BufferedImage(biWidth, biWidth, BufferedImage.TYPE_INT_ARGB);
      Graphics2D g2 = img.createGraphics();
      g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);
      g2.setPaint(new GradientPaint(0, 0, Color.red, 20, 20, Color.blue, true));
      g2.fillOval(0, 0, biWidth, biWidth);
      g2.dispose();
      Icon icon = new ImageIcon(img);
      JLabel label = new JLabel(icon);
      return label;
   }

   private JScrollPane createComponentTwo() {
      JTextArea textArea = new JTextArea(15, 40);
      JScrollPane scrollpane = new JScrollPane(textArea);
      scrollpane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
      scrollpane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
      return scrollpane;
   }

   private JPanel createComponentOne() {
      JPanel innerPanel = new JPanel(new GridLayout(1, 0, 5, 0));
      String[] btnTitles = {"One", "Two", "Three"};
      for (String btnTitle : btnTitles) {
         JButton btn = new JButton(btnTitle);
         innerPanel.add(btn);
      }
      JPanel panel = new JPanel(new GridBagLayout());
      panel.add(innerPanel);
      return panel;
   }

   @SuppressWarnings("hiding")
   private void registerComponent(JComponent jComp, String key) {
      cardHolderPanel.add(jComp, key);
      componentMap.put(key, jComp);
      comboModel.addElement(key);
   }

   private class CardComboListener implements ActionListener {
      @Override
      public void actionPerformed(ActionEvent e) {
         final String oldKey = key;
         key  = (String) cardCombo.getSelectedItem();
         cardCombo.setEnabled(false);

         final JComponent firstComp = componentMap.get(oldKey);
         final BufferedImage firstImg = extractComponentImg(firstComp);
         final JComponent secondComp = componentMap.get(key);
         final BufferedImage secondImg = extractComponentImg(secondComp);

         cardlayout.show(cardHolderPanel, SWAPPING_IMG_PANEL);
         swappingImgPanel.setFirstImg(firstImg);
         swappingImgPanel.setSecondImg(secondImg);
         swappingImgPanel.swap();

      }

      private BufferedImage extractComponentImg(final JComponent component) {
         Dimension size = component.getSize();
         BufferedImage img = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_ARGB);
         Graphics2D g2 = img.createGraphics();
         component.paint(g2);
         g2.dispose();
         return img;
      }
   }

   private static void createAndShowGui() {
      DimmingPanelSwaps mainPanel = new DimmingPanelSwaps();

      JFrame frame = new JFrame("Dimming Panel Swaps");
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.getContentPane().add(mainPanel);
      frame.pack();
      frame.setLocationByPlatform(true);
      frame.setVisible(true);
   }

   public static void main(String[] args) {
      SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            createAndShowGui();
         }
      });
   }
}

/**
 * A JPanel that draws two images
 * When swap is called, the first image is shown
 * Then a Timer dims the first image while it reveals
 * the second image.
 * When the elapsed time is complete, it sets its state to State.DONE.
 * @author Pete
 *
 */
@SuppressWarnings("serial")
class SwappingImgPanel extends JPanel {
   public static final String STATE = "state";
   private BufferedImage firstImg;
   private BufferedImage secondImg;
   private int deltaTime;
   private int elapsedTime;
   // state is a "bound" property, one that is listened to via PropertyChangeSupport
   private State state = State.PENDING;
   private float alpha1;
   private float alpha2;

   public SwappingImgPanel(final int deltaTime, final int elapsedTime) {
      this.deltaTime = deltaTime;
      this.elapsedTime = elapsedTime;
   }

   public void swap() {
      setState(State.STARTED);
      if (firstImg == null || secondImg == null) {
         done();
      }
      alpha1 = 1.0f;
      alpha2 = 0.0f;
      new Timer(deltaTime, new ActionListener() {
         private int counter = 0;
         private int max = elapsedTime / deltaTime;

         @Override
         public void actionPerformed(ActionEvent e) {
            if (counter >= elapsedTime / deltaTime) {
               ((Timer)e.getSource()).stop();
               done();
               return;
            }
            // set new alpha composite values
            alpha1 = ((float)max - counter) / (float) max;
            alpha2 = (float) counter / (float) max;

            // make sure alphas are within bounds
            alpha1 = Math.min(1f, alpha1);
            alpha1 = Math.max(0f, alpha1);
            alpha2 = Math.min(1f, alpha2);
            alpha2 = Math.max(0f, alpha2);

            repaint();
            counter++;
         }
      }).start();

   }


   private void done() {
      firstImg = null;
      secondImg = null;
      setState(State.DONE);
   }

   @Override
   protected void paintComponent(Graphics g) {
      super.paintComponent(g);
      if (firstImg == null || secondImg == null) {
         return;
      }
      // create a new Graphics2D object with g.create()
      // to avoid any possible side effects from changing the 
      // composite of the JVM's Graphics object
      Graphics2D g2 = (Graphics2D) g.create();
      // set the first alpha composite, and draw the image
      g2.setComposite(((AlphaComposite)g2.getComposite()).derive(alpha1));
      g2.drawImage(firstImg, 0, 0, this);
      // set the second alpha composite, and draw the image
      g2.setComposite(((AlphaComposite)g2.getComposite()).derive(alpha2));
      g2.drawImage(secondImg, 0, 0, this);
      g2.dispose(); // can get rid of this Graphics because we created it
   }

   public void setFirstImg(BufferedImage firstImg) {
      this.firstImg = firstImg;
   }

   public void setSecondImg(BufferedImage secondImg) {
      this.secondImg = secondImg;
   }

   public State getState() {
      return state;
   }

   public void setState(State state) {
      State oldValue = this.state;
      State newValue = state; 
      this.state = state;
      firePropertyChange(STATE, oldValue, newValue);
   }
}

/**
 * Modeled on SwingWorker.StateValue
 * @author Pete
 *
 */
enum State {
   PENDING, STARTED, DONE
}