Как включить таймер в анимацию сердцебиения? - PullRequest
0 голосов
/ 04 февраля 2020

Я отчаянно пытался понять, как создать анимацию для домашней работы по курсу. Цель состояла в том, чтобы создать сердце, используя Path2D.curveTo, а затем имитировать анимацию сердцебиения.

    import java.awt.BasicStroke;
    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.EventQueue;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.geom.AffineTransform;
    import java.awt.geom.Path2D;
    import javax.swing.Timer;

    import javax.swing.JFrame;
    import javax.swing.JPanel;
    public class HelloCurves {
        public HelloCurves() {
            JFrame jf = new JFrame("HelloCurves");
            jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            JPanel jp = new MyJPanel();
            jf.add(jp);
            jf.pack();
            jf.setResizable(false);
            jf.setLocationRelativeTo(null);
            jf.setVisible(true);
        }
        public static void main(String[] args) {
            EventQueue.invokeLater(HelloCurves::new);
        }
        class MyJPanel extends JPanel {
            private static final long serialVersionUID = 1L;
            public MyJPanel() {
                super();
                setPreferredSize(new Dimension(800, 600));
                setBackground(new Color(200, 200, 255));
            }
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                Graphics2D g2d = (Graphics2D) g.create();
                AffineTransform gat = new AffineTransform();
                gat.scale(1.0, -1.0);
                gat.translate(getWidth() / 2.0, -getHeight() / 2.0);
                g2d.transform(gat);
                Path2D p2d = new Path2D.Double();

//Larger First Heart
    //            p2d.moveTo(0.0, -250.0);
    //            p2d.curveTo(-350.0, -125.0,-350.0, 375.0, 0, 175.0);
    //            p2d.curveTo(350.0, 375.0,350.0, -125.0, 0, -250.0);

//Smaller Second Heart
                p2d.moveTo(0.0, -150.0);
                p2d.curveTo(-200.0, -25.0,-200.0, 225.0, 0, 100.0);
                p2d.moveTo(0.0, -150.0);
                p2d.curveTo(200.0, -25.0,205.0, 235.0, 0, 100.0);

                g2d.setPaint(Color.PINK);
                g2d.fill(p2d);
                g2d.setStroke(new BasicStroke(5.0f));
                g2d.setPaint(Color.BLACK);
                g2d.draw(p2d);
                g2d.dispose();
            }
        }
    }

Итак, заключительная часть этого задания - это то, что действительно ставит меня в тупик, я никогда не создавал анимацию раньше, поэтому я не знаю, какие именно пакеты использовать. Я знаю, что должен быть задействован таймер. Как я могу использовать таймер, чтобы иметь функцию постоянного появления и исчезновения первого сердца; в то время как второй делает наоборот?

1 Ответ

0 голосов
/ 04 февраля 2020

Начните с идеи, что анимация - это иллюзия изменений во времени. Таким образом, вам нужно иметь возможность переходить из одного состояния в другое в зависимости от количества доступного времени и текущего используемого времени.

Итак, нам действительно нужен какой-то способ динамического изменения размера фигуры. на количество времени, которое было сыграно.

Итак, первое, что нужно сделать, это взять код рисования и создать из него Shape. Это обеспечивает, как с базовой линией.

public class HeartShape extends Path2D.Double {

    public HeartShape() {
        moveTo(0.0, -150.0);
        curveTo(-200.0, -25.0, -200.0, 225.0, 0, 100.0);
        moveTo(0.0, -150.0);
        curveTo(200.0, -25.0, 205.0, 235.0, 0, 100.0);
    }

}

Это станет нашим "источником", это будет иметь смысл позже ?

Далее нам нужно настроить состояние воспроизведения, это включает «время начала» или «время привязки» и желаемая продолжительность воспроизведения.

Как только мы это получим, мы можем использовать Timer, чтобы вычислить количество сыгранного времени и сгенерировать из него значение «прогресса».

В качестве примечания, подобные анимации на основе времени более гибкие и обычно выглядят лучше, чем линейные прогрессии (IMHO)

Затем мы можем использовать значение прогрессии в качестве основы для изменения размера форма.

Для этого мы используем Shape API ...

Shape shape = heartShape.createTransformedShape(AffineTransform.getScaleInstance(scale, scale));

Все это может выглядеть примерно так ...

class MyJPanel extends JPanel {

    private static final long serialVersionUID = 1L;

    private HeartShape heartShape = new HeartShape();

    private Instant anchorPoint;
    private Duration playDuration = Duration.ofSeconds(5);
    private double scale = 1;

    public MyJPanel() {
        super();
        setPreferredSize(new Dimension(800, 600));
        setBackground(new Color(200, 200, 255));

        Timer timer = new Timer(5, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (anchorPoint == null) {
                    anchorPoint = Instant.now();
                }
                Duration playTime = Duration.between(anchorPoint, Instant.now());
                double progress = (double)playTime.toMillis() / playDuration.toMillis();
                if (progress >= 1) {
                    anchorPoint = null;
                    progress = 1;
                }

                scale = progress;
                repaint();
            }
        });
        timer.start();
    }

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

        AffineTransform gat = new AffineTransform();

        gat.scale(1.0, -1.0);
        gat.translate(getWidth() / 2.0, -getHeight() / 2.0);
        g2d.transform(gat);

        Shape shape = heartShape.createTransformedShape(AffineTransform.getScaleInstance(scale, scale));

        g2d.setPaint(Color.PINK);
        g2d.fill(shape);
        g2d.setStroke(new BasicStroke(5.0f));
        g2d.setPaint(Color.BLACK);
        g2d.draw(shape);
        g2d.dispose();
    }
}

Хорошо, но это простое движение от 0-1, нам нужно, чтобы оно «отскочило» (от начальной точки к конечной точке и обратно).

В этот момент мы вошли на территорию «ключевые кадры», но я постараюсь сделать это проще, но если вам интересно, вы можете посмотреть:

Но мы предупреждаем ed, это будет как твоя голова ? - это мое ?

Во-первых, нам нужно определить «диапазон» действительных значений. Это будет представлять диапазон, в котором будет масштабироваться фигура ...

private double lowerRange = 0.75;
private double upperRange = 1.25;

Здесь я только что выбрал несколько произвольных значений около 1.0, вы можете поиграть с ними и посмотреть, что вам нравится.

Далее, в Timer нам нужно изменить способ вычисления scale на что-то более похожее ...

scale = ((upperRange - lowerRange) * progress) + lowerRange;
repaint();

Хорошо, круто, теперь у нас есть растущая фигура от 0.75 до 1.25, но это все еще только в линейном направлении.

Хорошо, здесь ключевые кадры и временная шкала были бы действительно полезны, так как все, что мы действительно хотим, это что-то вроде «автореверс» (за исключением примененного времени), мы можем сделать что-то более похожее на ...

if (progress > 0.5) {
    progress = 1.0 - progress;
}

scale = ((upperRange - lowerRange) * progress) + lowerRange;
repaint();

И, до того, у нас пульсирующее сердце ?, сладкое

Хорошо, но подождите, это немного медленно. Что ж, тогда просто измените playDuration, возможно, вместо этого попробуйте private Duration playDuration = Duration.ofSeconds(1);!

Посмотрите, как это просто, вам нужно всего лишь изменить одну переменную, и вы полностью изменили способ воспроизведения анимации !! Сказал, что анимация, основанная на времени, потрясла ?

Последний пример запуска ...

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
import java.time.Duration;
import java.time.Instant;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

public class HelloCurves {

    public HelloCurves() {
        JFrame jf = new JFrame("HelloCurves");
        jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JPanel jp = new MyJPanel();
        jf.add(jp);
        jf.pack();
        jf.setResizable(false);
        jf.setLocationRelativeTo(null);
        jf.setVisible(true);
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(HelloCurves::new);
    }

    class MyJPanel extends JPanel {

        private static final long serialVersionUID = 1L;

        private HeartShape heartShape = new HeartShape();

        private Instant anchorPoint;
        private Duration playDuration = Duration.ofSeconds(1);
        private double scale = 1;

        private double lowerRange = 0.75;
        private double upperRange = 1.25;

        public MyJPanel() {
            super();
            setPreferredSize(new Dimension(800, 600));
            setBackground(new Color(200, 200, 255));

            Timer timer = new Timer(5, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    if (anchorPoint == null) {
                        anchorPoint = Instant.now();
                    }
                    Duration playTime = Duration.between(anchorPoint, Instant.now());
                    double progress = (double) playTime.toMillis() / playDuration.toMillis();
                    if (progress >= 1) {
                        anchorPoint = null;
                        progress = 1;
                    }

                    if (progress > 0.5) {
                        progress = 1.0 - progress;
                    }

                    scale = ((upperRange - lowerRange) * progress) + lowerRange;
                    repaint();
                }
            });
            timer.start();
        }

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

            AffineTransform gat = new AffineTransform();

            gat.scale(1.0, -1.0);
            gat.translate(getWidth() / 2.0, -getHeight() / 2.0);
            g2d.transform(gat);

            Shape shape = heartShape.createTransformedShape(AffineTransform.getScaleInstance(scale, scale));

            g2d.setPaint(Color.PINK);
            g2d.fill(shape);
            g2d.setStroke(new BasicStroke(5.0f));
            g2d.setPaint(Color.BLACK);
            g2d.draw(shape);
            g2d.dispose();
        }
    }

    public class HeartShape extends Path2D.Double {

        public HeartShape() {
            moveTo(0.0, -150.0);
            curveTo(-200.0, -25.0, -200.0, 225.0, 0, 100.0);
            moveTo(0.0, -150.0);
            curveTo(200.0, -25.0, 205.0, 235.0, 0, 100.0);
        }

    }
}

Убедитесь, что вы можете объяснить это в своей лекции, потому что, если вы представили мне этот код, я я буду задавать много вопросов о том, как это работает и почему вы выбираете этот путь 1080

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...