简体   繁体   中英

How do you create multiple animated components with timer/actionevents with two different time intervals on the same panel?

I need help with a simple animation assignment. It goes as follows.

I have two stop lights on a JPanel and the object is for the two of them to have different time intervals ie there lights cycle at different times.

Everything works fine if I only have one light at a time. I am relatively new to this but I believe I know the problem.

In the code under this text, I use this several times. I believe my issue occurs in the public void cycle() method in which it just says this.repaint() ; I have a feeling that the panel is being repainted at the two different time periods and it gives me a somewhat random light changing instead of a nice cycle.

Is there a way I can have these two components on the same JPanel with a more specific repaint method (maybe a bounding box around the individual light fixtures) or would creating separate panels be a better option (and a little help if that is the case because I understand the basic layouts but have never used them before).

import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;

public class DrawingPanel extends JPanel implements Lighter
{
    // instance variables 
    private final int INTERVAL1 = 2000;
    private final int INTERVAL2 = 5000;
    private TrafficLight _light1, _light2;
    private LightTimer _timer1,_timer2;

    /**
     * Constructor for objects of class DrawingPanel
     */
    public DrawingPanel()
    {
        // initialise instance variables
        super();
        this.setBackground(Color.CYAN);
        _light1 = new TrafficLight(50,50);
        _light2 = new TrafficLight(200,50);
        _timer1 = new LightTimer(INTERVAL1,this);
        _timer2 = new LightTimer(INTERVAL2,this);
        _timer1.start();
        _timer2.start();
    }

    public void cycle(){
        _light1.cycle();
        _light2.cycle();
        this.repaint();
        }

        public void paintComponent(Graphics pen)
        {
        super.paintComponent(pen);
        Graphics2D aBetterPen = (Graphics2D)pen;
        _light1.fill(aBetterPen);
        _light2.fill(aBetterPen);
    }

}

Running two timers in the fashion is achievable. Personally, I would write a "signal" class that controls a single light with it's own timing and painting routines, but that's not what you've asked.

What you need to do is maintain some kind of state variable for each signal and update them separately.

You would then need to modify the paint code to detect these states and take appropriate actions...for example

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class TestBlink {

    public static void main(String[] args) {
        new TestBlink();
    }

    public TestBlink() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private Timer blink1;
        private Timer blink2;

        private boolean light1 = false;
        private boolean light2 = false;

        public TestPane() {

            blink1 = new Timer(250, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    light1 = !light1;
                    repaint();
                }
            });
            blink2 = new Timer(1000, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    light2 = !light2;
                    repaint();
                }
            });

            blink1.start();
            blink2.start();

        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();

            int radius = 20;
            int x = (getWidth() - (radius * 2)) / 2;
            int y = (getHeight() - (radius * 2)) / 2;

            Ellipse2D signal1 = new Ellipse2D.Float(x, y, radius, radius);
            Ellipse2D signal2 = new Ellipse2D.Float(x + radius, y, radius, radius);

            g2d.setColor(Color.RED);
            g2d.draw(signal1);
            if (light1) {
                g2d.fill(signal1);
            }
            g2d.setColor(Color.GREEN);
            g2d.draw(signal2);
            if (light2) {
                g2d.fill(signal2);
            }

            g2d.dispose();
        }
    }
}

Updated

A better solution (as I stated earlier) would be to contain all the logic for a single sequence in a single class. This isolates the painting and allows you to change the individual nature each sequence.

For example...

This is a simple example which uses a fixed rate of change, so each light gets the same about of time...

public class TraficLight01 extends JPanel {

    public static final int RADIUS = 20;

    private Timer timer;
    private int state = 0;

    public TraficLight01() {
        timer = new Timer(500, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                state++;
                if (state > 2) {
                    state = 0;
                }
                repaint();
            }
        });
        timer.start();
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(RADIUS, (RADIUS + 1) * 3);
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g.create();
        int radius = 20;
        Ellipse2D light = new Ellipse2D.Float(0, 0, RADIUS, RADIUS);
        int x = (getWidth() - radius) / 2;
        int y = ((getHeight()- (radius * 3)) / 2) + (radius * 2);

        Color color[] = new Color[]{Color.RED, Color.YELLOW, Color.GREEN};
        for (int index = 0; index < color.length; index++) {
            g2d.translate(x, y);
            g2d.setColor(color[index]);
            g2d.draw(light);
            if (state == index) {
                g2d.fill(light);
            }
            g2d.translate(-x, -y);
            y -= radius + 1;
        }
        g2d.dispose();
    }        
}

Or you provide a variable interval for each light...

public static class TraficLight02 extends JPanel {

    public static final int RADIUS = 20;

    private Timer timer;
    private int state = 0;

    // Green, Yellow, Red
    private int[] intervals = new int[]{3000, 500, 3000};

    public TraficLight02() {
        timer = new Timer(intervals[0], new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                timer.stop();
                state++;
                if (state > 2) {
                    state = 0;
                }
                timer.setInitialDelay(intervals[state]);
                repaint();
                timer.restart();
            }
        });
        timer.start();
        timer.setRepeats(false);
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(RADIUS, (RADIUS + 1) * 3);
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g.create();
        int radius = 20;
        Ellipse2D light = new Ellipse2D.Float(0, 0, RADIUS, RADIUS);
        int x = (getWidth() - radius) / 2;
        int y = ((getHeight()- (radius * 3)) / 2) + (radius * 2);

        Color color[] = new Color[]{Color.GREEN, Color.YELLOW, Color.RED};
        for (int index = 0; index < color.length; index++) {
            g2d.translate(x, y);
            g2d.setColor(color[index]);
            g2d.draw(light);
            if (state == index) {
                g2d.fill(light);
            }
            g2d.translate(-x, -y);
            y -= radius + 1;
        }
        g2d.dispose();
    }        
}

It wouldn't take much to change these two concepts to seed them with variable intervals via a setter method.

Equally, you could use three Timer s, each time one fires, you would simply start the next one. This way you could define a chain of timers, each one firing at different intervals after the completion of it's parent...

As a Note, your comment

it gives me a somewhat random light changing instead of a nice cycle.

What are you expecting it to look like?

With the Time intervals you have set it may appear somewhat random but it is actually working ie your intervals would work like this (well my assumptions of what the Interval variable are for)

Time(s)    1    2   3   4   5   6   7   8   9   10   11   12   13   14   15   16
Light1         ON      OFF     ON      OFF      ON        OFF       ON        OFF
Light2                      ON                  OFF                      ON

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM