2

I want to make an animated numeric counter, like this one:

https://i.stack.imgur.com/qiA5t.gif

I want to be able to input the value and have the counter update with animation.

I can find how to do this on Android from Google, but I cannot find any information on how to make it in Java Swing. How would I make something like this in Swing?

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
  • 4
    I hate to say it but if you were familiar enough with Swing to get this done you would not be asking the question. I have done a fair amount of Swing related programming and this could still be somewhat of a challenge. I would recommend you look at what the animated image shows and try to repeat different sections of it. First, just display the numbers. They figure out how to rotate them up and down. Then have them rotate independently. There is a lot to consider so just break it down and work on different aspects. – WJS Dec 02 '20 at 15:09
  • 2
    Agree with above comment. But in general any animation is done by using a [Swing Timer](https://docs.oracle.com/javase/tutorial/uiswing/misc/timer.html). So you would somehow need to animate the painting of the digits at different positions. Here is a basic example that does horizontal scrolling: https://stackoverflow.com/questions/33907207/how-to-make-jscrollpane-in-borderlayout-containing-jpanel-smoothly-autoscroll/33907282#33907282. Vertical scrolling between multiple digits would be much more complicated. – camickr Dec 02 '20 at 15:14
  • 1
    I created a clock with numbers that slid up., I created a BufferedImage of the digits 0 - 9 arranged vertically. Unfortunately, the images are gone from this archived article, [Sliding Clock Using Java Swing](https://web.archive.org/web/20170917061440/http://java-articles.info/articles/?p=885). – Gilbert Le Blanc Dec 02 '20 at 18:05
  • 1
    This is "how to" question. It is not soliciting recommendations for an off-site resource and does not need to be closed on that basis. – Cody Gray - on strike Dec 05 '20 at 12:29

2 Answers2

6

This isn't a complete answer, but this is a working example of a sliding JPanel. This code could be modified to create the display in the question.

Here's the complete runnable example.

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;

import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class SlidingDigitGUI implements Runnable {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new SlidingDigitGUI());
    }
    
    private SlidingPanel secondPanel;

    @Override
    public void run() {
        JFrame frame = new JFrame("Sliding Digit");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        frame.add(createSlidingPanel(), BorderLayout.CENTER);
        
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
        
        Animation animation = new Animation();
        new Thread(animation).start();
    }
    
    public JPanel createSlidingPanel() {
        String[] digitValues = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" };
        
        JPanel panel = new JPanel();
        panel.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY, 4, false));
        panel.setPreferredSize(new Dimension(300, 100));
        
        Font font = panel.getFont();
        Font derivedFont = font.deriveFont(Font.BOLD, 48F);
        
        secondPanel = new SlidingPanel(digitValues, derivedFont);
        secondPanel.setPanelValue("0");
        panel.add(secondPanel);
        
        return panel;
    }
    
    public class SlidingPanel extends JPanel {

        private static final long serialVersionUID = 661553022861652947L;

        private static final int MARGIN = 4;

        private int imageY;

        private BufferedImage slidingImage;

        private Dimension characterDimension;

        private final Font font;

        private String currentValue;

        private final String[] panelValues;

        public SlidingPanel(String[] panelValues, Font font) {
            this.panelValues = panelValues;
            this.font = font;
            this.characterDimension = calculateFontSize();
            this.slidingImage = generateSlidingImage();
            this.setPreferredSize(characterDimension);
        }

        private Dimension calculateFontSize() {
            int maxWidth = 0;
            int maxHeight = 0;
            FontRenderContext frc = new FontRenderContext(null, true, true);
            for (String s : panelValues) {
                Rectangle2D r2D = font.getStringBounds(s, frc);
                int rWidth = (int) Math.round(r2D.getWidth());
                int rHeight = (int) Math.round(r2D.getHeight());
                maxWidth = Math.max(maxWidth, rWidth);
                maxHeight = Math.max(maxHeight, rHeight);
            }

            return new Dimension(maxWidth, maxHeight);
        }

        private BufferedImage generateSlidingImage() {
            int height = calculateStringHeight() * (panelValues.length + 1);
            BufferedImage slidingImage = new BufferedImage(characterDimension.width, 
                    height, BufferedImage.TYPE_INT_RGB);
            Graphics g = slidingImage.getGraphics();
            g.setColor(Color.WHITE);
            g.fillRect(0, 0, characterDimension.width, height);
            g.setColor(Color.BLACK);
            g.setFont(font);

            int y = characterDimension.height - MARGIN;

            for (String s : panelValues) {
                g.drawString(s, 0, y);
                y += calculateStringHeight();
            }

            g.drawString(panelValues[0], 0, y);
            g.dispose();
            return slidingImage;
        }

        public void setPanelValue(String value) {
            int index = getValueIndex(value);
            this.currentValue = value;
            this.imageY = calculateStringHeight() * index;
            repaint();
        }

        public void updatePanelValue(String value) {
            if (!currentValue.equals(value)) {
                int index = getValueIndex(value);
                int finalY = calculateStringHeight() * index;
                SliderAnimation sliderAnimation = new SliderAnimation(imageY, finalY);
                new Thread(sliderAnimation).start();
                this.currentValue = value;
            }
        }

        private int getValueIndex(String value) {
            for (int index = 0; index < panelValues.length; index++) {
                if (value.equals(panelValues[index])) {
                    return index;
                }
            }

            return -1;
        }

        private int calculateStringHeight() {
            return characterDimension.height + MARGIN;
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            BufferedImage subImage = slidingImage.getSubimage(0, imageY, 
                    characterDimension.width,
                    characterDimension.height);
            g.drawImage(subImage, 0, 0, this);
        }

        public class SliderAnimation implements Runnable {
            private int originalY;
            private int finalY;

            public SliderAnimation(int originalY, int finalY) {
                this.originalY = originalY;
                this.finalY = finalY;
            }

            @Override
            public void run() {
                int differenceY = finalY - originalY;
                if (finalY == 0) {
                    differenceY = characterDimension.height + MARGIN;
                }

                int steps = 10;
                double difference = (double) differenceY / steps;
                for (int index = 1; index <= steps; index++) {
                    imageY = (int) Math.round(difference * index + originalY);
                    update();
                    sleep(120L);
                }

                if (finalY == 0) {
                    imageY = 0;
                    update();
                } else {
                    imageY = finalY;
                }
            }

            private void update() {
                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        SlidingPanel.this.repaint();
                    }
                });
            }

            private void sleep(long duration) {
                try {
                    Thread.sleep(duration);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    }

    public class Animation implements Runnable {
        
        @Override
        public void run() {
            while (true) {
                update("3");
                sleep(2000L);
                update("8");
                sleep(2000L);
            }
        }
        
        private void update(final String value) {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    secondPanel.updatePanelValue(value);
                }
            });
        }

        private void sleep(long duration) {
            try {
                Thread.sleep(duration);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
    }

}
Gilbert Le Blanc
  • 50,182
  • 6
  • 67
  • 111
1
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.Point;

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

public class Animation {
    boolean loop=true;//true                            //loop
    static int start=1;//1                              //start
    static int end=10+1;//20                            // end + 1
    int delay1=1000;//1s                                // delay
    int delay2=1;//1ms                                  // delay
    Font font=new Font("Helvetica",Font.BOLD,25);       //Font
    
    Timer timer=new Timer(delay1, e -> move());JPanel panel = new JPanel();int[] size = {50,100};Point point = new Point(210,size[1]);
    Timer timer1=null;int value=start-1;int i;
    public static void main(String[] args) {if (start!=end) {new Animation();}}
    public void move() {
        timer.stop();value ++;
        if (!loop && value==end) {timer.stop();}i=0;
        timer1 = new Timer(delay2, e -> {
            if (i==100) {
                timer1.stop();
                timer.start();
            }
            point.setLocation(point.getX(), point.getY()-1);
            panel.setLocation(point);
            panel.revalidate();
            i++;
        });timer1.start();
        if (loop && value==end-1) {point.setLocation(point.getX(),size[1]+size[1]);value = start-1;}else if (value==end-1 && !loop){System.exit(0);}
    }
    public Animation() {
        JFrame frame = new JFrame();frame.setSize(500,300);frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);frame.add(panel);frame.setLocationRelativeTo(null);
        panel.setLocation(point);frame.setLayout(null);frame.revalidate();panel.setSize(new Dimension(size[0],size[1]*Math.abs(end-(start-1))));
        panel.setLayout(new GridLayout(Math.abs(end-start)+start,1));if (!(end-start<0)) {for (int i=start;i!=(end-start)+start;i++) {JLabel la = new JLabel(String.valueOf(i));la.setFont(font);panel.add(la);
        }}timer.start();frame.revalidate();frame.setVisible(true);
    }
}