Saturday, June 5, 2010

Animated Scrolling text

Some days ago, a friend(hey Luiz!) asked me about a component that could display text with a kind of scrolling animation as you see in many media players where you see the band name / title name going from right to left. He wanted to add this kind of component to one of his application. As he knows I like to play with swing/java2D he asked me about it.
I like the idea of creating such component; so I had to come up with an implementation, even If I guess you can find some basic implementations on the net.

I wanted to add a way to control the animation to be able to have some basic animations (Left to right / Right to left / Top to bottom / Bottom to top) and also some more complex animation like sinusoidal animation.


I rapidly had a first draft working, doing the wished animation, but it took a few refractor sessions to have some clean code that gives full control over the animation.


Let’s have a look at the code.

First the interface that will be used to control the animation:


public static interface IScrollTextUtils {
    int getYpos(float animProgress, int charnum);

    int getXpos(float animProgress, int charnum);

    double getRotate(float animProgress, final int charnum);

    float getTransformedFontSize(float animProgress, int fontSize, final int charnum);

    boolean isTimeTolaunchNewTimeline(float animProgress);

    void textChanged();

    void paintCharacter(Graphics2D g, String s);

    int getDuration();
}


It will be used to find out the position of each character, paint the text, the duration of the animation and to determine when to start the next animation.

More than one of the same animation can run at the same time (you want to start the next animation before the end of the current one to always have some text to display).

So to deal with multiple animations at the same time we need a list of something that can handle an animation.
AnimationProgressHandler is used for this purpose.
It contains a timeline and a way to start the next animation when required




public static class AnimationProgressHandler {
        private Timeline timeline;
        private float animProgress = 0;
        private boolean AlreadyStartedNext = false;
        private ScrollingText scrolling;

        private AnimationProgressHandler(final ScrollingText scrolling) {
            this.scrolling = scrolling;
            //prepare the timeline
            this.timeline = new Timeline(this);
            this.timeline.addPropertyToInterpolate("animProgress", 0f, 1f);
            this.timeline.setDuration(scrolling.scrollTextUtils.getDuration());
            this.timeline.addCallback(createCallBack(this.timeline));
        }

        public void setAnimProgress(final float animProgress) {
            this.animProgress = animProgress;
            if (!this.AlreadyStartedNext && this.scrolling.scrollTextUtils.isTimeTolaunchNewTimeline(animProgress)) {
                this.AlreadyStartedNext = true;
                //start the next animation
                this.scrolling.createTimeline().play();
            }
            this.scrolling.repaint();
        }

        private TimelineCallback createCallBack(final Timeline parent) {
            return new TimelineCallbackAdapter() {
                @Override
                public void onTimelineStateChanged(final TimelineState oldState, final TimelineState newState,
                        final float durationFraction, final float timelinePosition) {
                    if (newState == TimelineState.DONE) {
                        //when the animation is done 
                        //remove it
                        AnimationProgressHandler.this.scrolling.animHandlers.remove(this);
                    }
                }
            };
        }
    }


the interesting methods in scrolling text are the paint methods


paintComponent paint the text for every active timeline

    protected void paintComponent(final Graphics g) {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g.create();
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        // paint the text for each running animation
        for (int i = 0; i < this.animHandlers.size(); i++) {
            paintText(g2, this.animHandlers.get(i).animProgress);
        }
        g2.dispose();
    }


paintText call scrollTextUtils to find the x and y coordinates
check if the coordinates are in the visible area
get and set the rotation/font and then paint each character of the text



protected void paintText(final Graphics2D g2, final float animProgress) {
        Font f = getFont();
        for (int i = 0; i < this.text.length(); i++) {
            int x = this.scrollTextUtils.getXpos(animProgress, i);
            if (x < -f.getSize() || x > getWidth()) {
                // do not paint, out of the visible bounds
            } else {
                int y = this.scrollTextUtils.getYpos(animProgress, i);
                if (y < 0 || y > getHeight() + f.getSize()) {
                    // do not paint, out of the visible bounds
                } else {
                    double angle = this.scrollTextUtils.getRotate(animProgress, i);
                    g2.rotate(angle, x, y);
                    g2.setFont(f.deriveFont(this.scrollTextUtils.getTransformedFontSize(animProgress, f.getSize(), i)));
                    g2.translate(x, y);
                    this.scrollTextUtils.paintCharacter(g2, String.valueOf(this.text.charAt(i)));
                    g2.translate(-x, -y);
                    g2.rotate(-angle, x, y);
                }
            }
        }
        g2.setFont(f);
    }

you can try it here







the full source code is available at https://free-the-pixel.dev.java.net/source/browse/free-the-pixel/trunk/src/com/community/xanadu/components/text/ScrollingText.java