Предотвратить скачок анимации прыжков - PullRequest
1 голос
/ 13 апреля 2020

Я сделал этот базовый пример c, используя JPanel и javax.swing.Timer, и я ожидаю, что анимация будет относительно плавной.

Если я удерживаю курсор мыши над окном, анимация плавная. Если я вообще не взаимодействую с окном, анимация начинает прыгать.

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

public class SmoothSwing{
    double x = 0;
    double y = 0;
    double theta = 0;

    public void step(){
        x = 175 + 175*Math.sin( theta );
        y = 175 + 175*Math.cos( theta );
        theta += 0.02;
        if(theta > 6.28) theta = 0;
    }

    public void buildGui(){
        JFrame frame = new JFrame("smooth");
        JPanel panel = new JPanel(){
            Dimension dim = new Dimension(400, 400);
            @Override
            public void paintComponent(Graphics g){
                super.paintComponent(g);
                g.drawOval((int)x, (int)y, 50, 50);
            }
            @Override
            public Dimension getMinimumSize(){
                return dim;
            }
            @Override
            public Dimension getPreferredSize(){
                return dim;
            }
        };
        frame.add(panel, BorderLayout.CENTER);
        frame.setVisible(true);
        frame.pack();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        Timer timer = new Timer(30, evt->{
            step();
            panel.repaint();
        });
        timer.start();
    }
    public static void main(String[] args){
        EventQueue.invokeLater( new SmoothSwing()::buildGui );
    }

}

Мне показалось, что перекрашивание накапливается и перекрашивается в комки. Поэтому я закомментировал строку super.paintComponent(g);, из-за которой панель не будет очищена.

Screen without clearing circles

Когда я это сделаю, я вижу, что paintComponent запускается, потому что все круги нарисованы, но дисплей все еще обновляется только после того, как нарисованы несколько кругов.

У меня есть эта проблема на jdk-14 + 36, jdk-11.0.6 и 1.8 .0_181 используя Linux. Ubuntu 18.04.

Вот графики, сравнивающие разницу. У меня были проблемы с управлением скоростью GIF-файлов, но это показывает, что происходит.

Expected behavior. After short inactivity. After a bit longer.

Слева направо, если я перемещаю мышь по окну, я получаю поведение слева, после остановки оно становится немного нервным (посередине), тогда оно становится более нервным (справа).

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

Timer t1 = new Timer(30, evt->step());
Timer t2 = new Timer(3, evt->panel.repaint());
t1.start();
t2.start();

Ответы [ 2 ]

2 голосов
/ 13 апреля 2020

Вы можете добиться более плавной анимации с помощью: - более точного шага тета - обновления с более высокой частотой - использования двойных значений для рисования:

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Ellipse2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

public class SmoothSwing{
    double x = 0;
    double y = 0;
    double theta = 0;

    public void step(){
        x = 175 + 175*Math.sin( theta );
        y = 175 + 175*Math.cos( theta );
        theta += 0.002; //finer theta step
        if(theta > 6.28) {
            theta = 0;
        }
    }

    public void buildGui(){
        JFrame frame = new JFrame("smooth");
        JPanel panel = new JPanel(){
            Dimension dim = new Dimension(400, 400);
            @Override
            public void paintComponent(Graphics g){
                super.paintComponent(g);
                Graphics2D g2d = (Graphics2D) g;
                g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
                g2d.draw(new Ellipse2D.Double(x, y, 50, 50));
            }
            @Override
            public Dimension getMinimumSize(){
                return dim;
            }
            @Override
            public Dimension getPreferredSize(){
                return dim;
            }
        };
        frame.add(panel, BorderLayout.CENTER);
        frame.setVisible(true);
        frame.pack();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        Timer timer = new Timer(3, evt->{ //higher frequency update
            step();
            panel.repaint();
        });
        timer.start();
    }
    public static void main(String[] args){
        EventQueue.invokeLater( new SmoothSwing()::buildGui );
    }   
}
1 голос
/ 13 апреля 2020

Когда я запускаю ваш пример или пример cOder, я не замечаю каких-либо изменений в движении с течением времени. Насколько я могу судить, он остается постоянным.

Мне показалось, что перекрашивание накапливается и перекрашивается в комки.

Единственный способ обойти RepaintManager в Swing - это использовать paintImmediately(…) метод. Это приводит к тому, что компонент будет окрашен сразу, без добавления в конец EDT. Его не рекомендуется использовать, поскольку это может снизить общую производительность рисования.

Любые методы, которые обновляют состояние вашего класса, должны быть частью класса, а не внешними по отношению к классу. Итак, я изменил вам оригинальный код. Это позволяет мне играть с логикой рисования c в методе step ():

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Ellipse2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

public class SmoothSwing2 extends JPanel
{
    double x = 0;
    double y = 0;
    double theta = 0;

    Dimension dim = new Dimension(400, 400);

    @Override
    public void paintComponent(Graphics g)
    {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g;
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
        g2d.draw(new Ellipse2D.Double(x, y, 50, 50));
    }

    @Override
    public Dimension getMinimumSize()
    {
        return dim;
    }

    @Override
    public Dimension getPreferredSize()
    {
        return dim;
    }

    public void step()
    {
        x = 175 + 175*Math.sin( theta );
        y = 175 + 175*Math.cos( theta );
        theta += 0.008; //finer theta step
        if(theta > 6.28) {
            theta = 0;
        }

        repaint();
//      paintImmediately( getBounds() ); // repaints the entire panel
//      paintImmediately(x - 5, y - 5, 60, 60); // repaint only the circle

    }

    public void buildGui()
    {
        SmoothSwing panel = new SmoothSwing();

        JFrame frame = new JFrame("smooth");
        frame.add(panel, BorderLayout.CENTER);
        frame.setVisible(true);
        frame.pack();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        Timer timer = new Timer(3, evt-> panel.step)
        timer.start();
    }
    public static void main(String[] args){
        EventQueue.invokeLater( new SmoothSwing2()::buildGui );
    }
}

Я не вижу никакой разницы при использовании любого из 3 подходов.

Если вы продолжайте замечать, что перерисовки «накапливаются», тогда, может быть, ваша ОС не дает процессору приложения постоянно?

Я использую JDK 11, на Windows 10.

...