Moving JLabel to other JLabels – GUI

nb: I don’t like null layouts, I don’t condone null layouts, I would prefer to have used custom painting, but that’s a lot of work not related to the question. This example is intended to focus on the implementation of a Timeline and KeyFrame animation

Because you have to move the object through both the x/y positions, but in different directions over the same time period, this becomes a very complex problem…

You could try and set up four, chained Timers, which trigger the next Timer when they complete, and which all do a different part of the animation…but frankly, that becomes a mess real quick…

A better idea is to use a concept of a time line and key frames. The idea is, that over the time line (0-1), certain events occur at prescribed times (key frames). The time line will then blend between these key frames…

enter image description here

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class MoveLabel {

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

    public MoveLabel() {
        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.getContentPane().setLayout(new BorderLayout());
                TestPane testPane = new TestPane();
                testPane.setBackground(Color.WHITE);
                frame.getContentPane().add(testPane);

                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public static class TestPane extends JPanel {

        private JTextField tf;
        private List<JTextField> tracks;

        protected static final int PLAY_TIME = 4000;

        private Timeline timeline;
        private long startTime;

        public TestPane() {
            setLayout(null);

            tracks = new ArrayList<JTextField>(20);

            int x = 20;
            int y = 20;

            for (int index = 0; index < 6; index++) {
                x += 20;
                tracks.add(createTrack(x, y, 20, 20));
            }
            for (int index = 0; index < 6; index++) {
                y += 20;
                tracks.add(createTrack(x, y, 20, 20));
            }
            for (int index = 0; index < 6; index++) {
                x -= 20;
                tracks.add(createTrack(x, y, 20, 20));
            }
            for (int index = 0; index < 6; index++) {
                y -= 20;
                tracks.add(createTrack(x, y, 20, 20));
            }

            for (JTextField track : tracks) {
                add(track);
            }

            tf = new JTextField("");
            tf.setSize(20, 20);
            tf.setBackground(Color.red);
            add(tf);
            setComponentZOrder(tf, 0);

            timeline = new Timeline();
            timeline.add(0, new Point(20, 20));
            timeline.add(0.25f, new Point(20 * 7, 20));
            timeline.add(0.5f, new Point(20 * 7, 20 * 7));
            timeline.add(0.75f, new Point(20, 20 * 7));
            timeline.add(1f, new Point(20, 20));

            Timer timer = new Timer(40, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    long duration = System.currentTimeMillis() - startTime;
                    float progress = (float) duration / (float) PLAY_TIME;
                    if (progress > 1f) {
                        startTime = System.currentTimeMillis();
                        progress = 0;
//                      ((Timer) (e.getSource())).stop();
                    }

                    Point p = timeline.getPointAt(progress);
                    tf.setLocation(p);

                }
            });
            startTime = System.currentTimeMillis();
            timer.start();

        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(600, 500);

        }

        protected JTextField createTrack(int x, int y, int width, int height) {
            JTextField field = new JTextField();
            field.setBounds(x, y, width, height);
            field.setEditable(false);
            field.setFocusable(false);
            return field;
        }

    }

    public static class Timeline {

        private Map<Float, KeyFrame> mapEvents;

        public Timeline() {
            mapEvents = new TreeMap<>();
        }

        public void add(float progress, Point p) {
            mapEvents.put(progress, new KeyFrame(progress, p));
        }

        public Point getPointAt(float progress) {

            if (progress < 0) {
                progress = 0;
            } else if (progress > 1) {
                progress = 1;
            }

            KeyFrame[] keyFrames = getKeyFramesBetween(progress);

            float max = keyFrames[1].progress - keyFrames[0].progress;
            float value = progress - keyFrames[0].progress;
            float weight = value / max;

            return blend(keyFrames[0].getPoint(), keyFrames[1].getPoint(), 1f - weight);

        }

        public KeyFrame[] getKeyFramesBetween(float progress) {

            KeyFrame[] frames = new KeyFrame[2];
            int startAt = 0;
            Float[] keyFrames = mapEvents.keySet().toArray(new Float[mapEvents.size()]);
            while (startAt < keyFrames.length && keyFrames[startAt] <= progress) {
                startAt++;
            }

            if (startAt >= keyFrames.length) {
                startAt = keyFrames.length - 1;
            }

            frames[0] = mapEvents.get(keyFrames[startAt - 1]);
            frames[1] = mapEvents.get(keyFrames[startAt]);

            return frames;

        }

        protected  Point blend(Point start, Point end, float ratio) {
            Point blend = new Point();

            float ir = (float) 1.0 - ratio;

            blend.x = (int)(start.x * ratio + end.x * ir);
            blend.y = (int)(start.y * ratio + end.y * ir);

            return blend;
        }

        public class KeyFrame {

            private float progress;
            private Point point;

            public KeyFrame(float progress, Point point) {
                this.progress = progress;
                this.point = point;
            }

            public float getProgress() {
                return progress;
            }

            public Point getPoint() {
                return point;
            }

        }

    }
}

You could then do silly things like vary the speed between sections…

timeline = new Timeline();
timeline.add(0, new Point(20, 20));
timeline.add(0.1f, new Point(20 * 7, 20));
timeline.add(0.5f, new Point(20 * 7, 20 * 7));
timeline.add(0.6f, new Point(20, 20 * 7));
timeline.add(1f, new Point(20, 20));

enter image description here

Leave a Comment