Removing flashiness/ shakiness from scrolling text

90 views Asked by At

I have finally made a ticker that functions how I want it to, but the only problem now is that it seems kind of shaky and looks like it is being displayed on an old tv. How do I make it look smoother?

Here is my code:

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;

import javax.swing.JFrame;
import javax.swing.JPanel;

public class Scroll1 extends JPanel{
    private int x;
    private int x2;
    private int y;
    private int width;
    private String text;
    private Font f=(new Font("SansSerif", Font.PLAIN,24));
    public Scroll1(int startX)
    {
        x=Integer.MIN_VALUE;
        x2=Integer.MIN_VALUE;
        y=150;
        text= "We are pleased to announce that Actavis has completed the acquisition of Auden Mckenzie, a dynamic and fast growing company focused on the development, licensing and marketing of generic medicines and proprietary brands in the UK.  ";
    }
    @Override
    public void paintComponent(Graphics g)
    {
            g.setFont(f);
          if ( x == Integer.MIN_VALUE ){
              width=g.getFontMetrics().stringWidth(text);
                x = 0;

            }
          if ( x2 == Integer.MIN_VALUE ){
                x2 = g.getFontMetrics().stringWidth(text);
            }
        g.setColor(Color.white);

        g.fillRect(this.getX(), this.getY(), (int)this.getBounds().getWidth(), (int)this.getBounds().getHeight());
        g.setColor(Color.black);
        g.drawString(text, x, y);
        g.drawString(text, x2, y);
    }

    public void start() throws InterruptedException{
        while(true){
            while(x>= (-width)&&x2!=Integer.MIN_VALUE){
                x--;
                x2--;
                y = getHeight()/2;
                Thread.sleep(15);
                this.
                validate();
                repaint();
                if(x2<=-width)
                {
                    x2=width;
                }
            }

            if(x<-width&&x!=Integer.MIN_VALUE)
            {
                x=width;    
            }
        }
    }
    public static void main(String[] args) {
        JFrame frame = new JFrame("Scrolling Panel");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        Scroll1 scrolling = new Scroll1(-100);

        frame.getContentPane().add(scrolling);
        frame.setSize(400, 300);
        frame.setVisible(true);
        try {
            scrolling.start();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
2

There are 2 answers

2
Cole Gordon On

Welp Mad Programmer you are a boss, thanks for all the help. I running what you had and it didnt work, strange huh? Anyway here is what I got now that seems to be working a whole lot smoother but still a few glitches here and there:

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;


public class Scroll2 extends JPanel implements ActionListener{ 
    private int x;
    private int x2;
    private int y;
    private int width;
    private String text;
    private Font f=(new Font("SansSerif", Font.BOLD,34));
    Timer t;
    public Scroll2(int startX)
    {
        x=Integer.MIN_VALUE;
        x2=Integer.MIN_VALUE;
        y=150;
        text= "We are pleased to announce that Actavis has completed the acquisition of Auden Mckenzie, a dynamic and fast growing company focused on the development, licensing and marketing of generic medicines and proprietary brands in the UK.  ";
        Timer refreshTimer = new javax.swing.Timer(10, this);
        refreshTimer.start();
    }
    @Override
    protected void paintComponent(Graphics g)
    {

            super.paintComponent(g);
            Graphics2D g2 = (Graphics2D) g;
            g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
                    RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
            g.setFont(f);
          if ( x == Integer.MIN_VALUE ){
              width=g.getFontMetrics().stringWidth(text);
                x = 0;

            }
          if ( x2 == Integer.MIN_VALUE ){
                x2 = g.getFontMetrics().stringWidth(text);
            }
        g.setColor(Color.white);
        g.fillRect(this.getX(), this.getY(), (int)this.getBounds().getWidth(), (int)this.getBounds().getHeight());
        g.setColor(Color.black);

        g.drawString(text, x, y);
        g.drawString(text, x2, y);

    }

     public void refresh ()
        {
            if(x>=(-width)&&x2!=Integer.MIN_VALUE)
            {
                x--;
                x2--;
                y = getHeight()/2;
            }
            if(x2<=-width&&x!=Integer.MIN_VALUE)
            {
                x2=width;
            }
            if(x<-width&&x!=Integer.MIN_VALUE)
            {
                x=width;
            }
            repaint();
        }


    public static void main(String[] args) {
        JFrame frame = new JFrame("Scrolling Panel");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        Scroll2 scrolling = new Scroll2(-100);

        frame.getContentPane().add(scrolling);
        frame.setSize(400, 300);
        frame.setVisible(true);
    }
    @Override
    public void actionPerformed(ActionEvent e) {
        this.refresh();

    }

}
7
MadProgrammer On
  1. Call super.paintComponent before you perform custom painting
  2. Prefer Swing Timer of a Thread, as Swing Timers are triggered within the EDT and you run less risk of having dirty reads between the paintComponent method and the Thread
  3. paintComponent really shouldn't be making decisions about the rendering state, instead it should just paint the current state.
  4. You don't need g.fillRect(this.getX(), this.getY(), (int) this.getBounds().getWidth(), (int) this.getBounds().getHeight());, just set the background color of the panel and allow the painting routines to do their jobs. Painting is done from 0x0, been the top/left corner of the component
  5. There is an issue with your while loop in your start method which doesn't seem to allow it to run (at least in my testing)

Can you help me try to transfer it over to a swing timer or give me a good source to do so?

Can you pay me? ;)

First, getting it to be completely "stagger" free isn't going to be possible, as the process is reliant on the system's capability of keeping up to date with your requests. For example, you really don't need to it be running at > 60fps, seriously, 25fps or even 15fps would be more the suitable for what you are trying to do. You control the speed of the scroll by using the movement delta instead

The following example uses a Swing Timer, as did my previous example, but this example will also create a "continuous" loop, where if the amount of text been displayed is less then the width of the visible area, it will add a "new" message to trail it.

This example would be capable of display multiple different messages of varying lengths but I guess, that's a bonus

import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Scroll1 extends JPanel {

    private String text;
    private Font f = (new Font("SansSerif", Font.PLAIN, 24));

    private List<TickerTap> tickerTaps;

    public Scroll1(int startX) {
        text = "We are pleased to announce that Actavis has completed the acquisition of Auden Mckenzie, a dynamic and fast growing company focused on the development, licensing and marketing of generic medicines and proprietary brands in the UK.";
        setFont(f);
        setBackground(Color.WHITE);
        tickerTaps = new ArrayList<>(4);

        Timer timer = new Timer(40, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                FontMetrics fm = getFontMetrics(getFont());
                if (tickerTaps.isEmpty()) {
                    tickerTaps.add(new TickerTap(text, getWidth()));
                }

                TickerTap taps[] = tickerTaps.toArray(new TickerTap[tickerTaps.size()]);
                int visibleWidth = 0;
                for (TickerTap tap : taps) {
                    tap.move();
                    int messageWidth = fm.stringWidth(tap.getMessage());
                    if (tap.getxPos() <= -messageWidth) {
                        tickerTaps.remove(tap);
                    } else {
                        visibleWidth += tap.getxPos() + messageWidth;
                    }
                }

                while (visibleWidth < getWidth()) {
                    TickerTap last = tickerTaps.get(tickerTaps.size() - 1);
                    int xPos = last.getxPos() + fm.stringWidth(last.getMessage());
                    String message = " * " + text;
                    TickerTap next = new TickerTap(message, xPos);
                    visibleWidth += next.getxPos() + fm.stringWidth(next.getMessage());
                    tickerTaps.add(next);
                }

                repaint();
            }
        });
        timer.start();
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g.create();
        g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
        g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
        g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

        for (TickerTap tap : tickerTaps) {
            FontMetrics fm = g2d.getFontMetrics();
            int y = ((getHeight() - fm.getHeight()) / 2) + fm.getAscent();
            g2d.setColor(Color.black);
            g2d.drawString(tap.getMessage(), tap.getxPos(), y);
        }
    }

    public class TickerTap {

        private String message;
        private int xPos;

        public TickerTap(String message, int xPos) {
            this.message = message;
            this.xPos = xPos;
        }

        public String getMessage() {
            return message;
        }

        public int getxPos() {
            return xPos;
        }

        public void move() {
            xPos -= 2;
        }
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Scrolling Panel");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                Scroll1 scrolling = new Scroll1(-100);

                frame.getContentPane().add(scrolling);
                frame.setSize(400, 300);
                frame.setVisible(true);
            }
        });
    }
}

Take a look at How to use Swing Timers for more details. Timer is actually really to understand if you simply think of it like a kind of loop