2

I have JTabbedPane with fade animation, which is performed when user clicks tabs. To handle animation I override stateChanged method.

public class AnimatedJTabbedPane extends JTabbedPane implements ChangeListener, TimingTarget{

/**
 * 
 */
private static final long serialVersionUID = 1L;
protected BufferedImage previousTabImage;
protected BufferedImage bufferImage;
protected BufferedImage nextTabImage;
protected boolean animationStarted = false;
protected boolean paintPreviousImage = false;
protected boolean leftToRightIndex = false;
protected float fraction = 0.0f;
protected Animator animator;
protected int previousTabIndex = -1;

public AnimatedJTabbedPane(int tabPlacement) {
    super(tabPlacement);

    this.animator = new Animator(300, this);
    this.animator.setAcceleration(0.1f);
    this.animator.setDeceleration(0.8f);
    this.addChangeListener(this);
}

public void stateChanged(ChangeEvent e) {

    if(this.previousTabIndex != this.getSelectedIndex()){
        if(this.previousTabIndex == -1){
            this.previousTabIndex = this.getSelectedIndex();
        }
        else{
            this.paintPreviousImage = true; 
            boolean interrupted = false;
            if(this.animator.isRunning()){
                this.animator.stop();
                interrupted= true;
            }

            Component previousComponent = this.getComponentAt(this.previousTabIndex);

            this.previousTabImage = this.getGraphicsConfiguration().createCompatibleImage(
                    previousComponent.getWidth(), previousComponent.getHeight(), Transparency.TRANSLUCENT);

            previousComponent.paint(this.previousTabImage.getGraphics());

            Component nextComponent = this.getComponentAt(this.getSelectedIndex());
            this.nextTabImage = this.getGraphicsConfiguration().createCompatibleImage(
                    previousComponent.getWidth(), previousComponent.getHeight(), Transparency.TRANSLUCENT);

            nextComponent.paint(this.nextTabImage.getGraphics());

            if(this.previousTabIndex < this.getSelectedIndex()){
                this.leftToRightIndex = true;
            }
            else{
                this.leftToRightIndex = false;
            }

            this.previousTabIndex = this.getSelectedIndex();
            if(interrupted){
                this.animator.setStartFraction(1-this.fraction);
            }
            else{
                this.animator.setStartFraction(0);
            }
            this.animationStarted = true;
            this.animator.start();
        }
    }
}


@Override
public void paintChildren(Graphics g){
    if(this.getComponentCount() != 0){
        Rectangle childrenPosition = this.getComponentAt(0).getBounds();

        if(this.bufferImage == null || 
                this.bufferImage.getWidth() != this.getWidth() ||
                this.bufferImage.getHeight() != this.getHeight()){
            this.bufferImage = new BufferedImage(
                    this.getWidth(), 
                    this.getHeight(), 
                    BufferedImage.TYPE_INT_ARGB);
        }

        if(this.animationStarted){
            if(this.paintPreviousImage){
                g.drawImage(this.bufferImage, 0, 0, null);
                this.paintPreviousImage = false;
            }
            else{
                Graphics2D g2d = (Graphics2D)this.bufferImage.createGraphics(); 
                int x = (int)childrenPosition.getX();
                int y = (int)childrenPosition.getY();

                this.performFadeAnimation(g2d, x, y);

                g.drawImage(this.bufferImage, 0, 0, null);
                g2d.dispose();
            }
            g.dispose();
        }
        else{
            Graphics2D g2d = (Graphics2D)this.bufferImage.createGraphics(); 

            super.paintChildren(g2d);
            g.drawImage(this.bufferImage, 0, 0, null);
            g2d.dispose();
            g.dispose();
        }
    }
    else{
        super.paintChildren(g);
    }
}


protected void performFadeAnimation(Graphics2D g2d, final double x, final double y){
    g2d.drawImage(this.previousTabImage, (int)x, (int)y, null);

    AlphaComposite composite = 
            AlphaComposite.getInstance(AlphaComposite.SRC_OVER, this.fraction);
    g2d.setComposite(composite);

    g2d.drawImage(this.nextTabImage,  (int)x, (int)y, null);
}

@Override
public void begin() {
    // TODO Auto-generated method stub
}

@Override
public void end() {
    this.animationStarted = false;
    this.repaint();
    this.nextTabImage.flush();
    this.nextTabImage = null;
    this.previousTabImage.flush();
    this.previousTabImage = null;
}

@Override
public void repeat() {
    // TODO Auto-generated method stub
}

@Override
public void timingEvent(float fraction) {
    this.fraction = fraction;
    this.repaint();
}

}

This works great when user clicks tabs manually. stateChanged event is called and tabs are changing with animation.

I need to change selected tab from code, so I'm using setSelectedIndex(int) method.

public void setSelectedTab(int tab){
    animatedTabbedPane.setSelectedIndex(tab);
}

But right now tab is changed instantly after setSelectedIndex ends - without animation, and then stateChanged method is called, so tab switch back to previously tab selected and perform (correctly) my animation.

So tab is changing twice, first without animation after setSelectedIndex and second time after stateChanged in AnimatedJTabbedPane.

I need something like:

 animatedTabbedPane.firePropertyChange("stateChanged", old, new);

I want to change tabs only with stateChanged method, so I can do without default setSelectedIndex method. How can I do that?

EDIT A little graph of my problem:

enter image description here

The red line represents visibility. So when user changes tab only stateChanged is called, tab 0 smoothly changes to tab 1. When I use setSelectedIndex tab 0 is immediately replaced with tab 1 and only then I can see my desired animation from stateChanged, so the users see a flash.

Demiurg
  • 347
  • 1
  • 4
  • 14
  • Can I see that sexy animation code? My first guess would be that stateChanged is not the apropiate place to do the animation, because it happens after the tab change. Maybe you could override JTabbedPane.setSelectedIndex(int i) and put the animation code before a super call, or directly a model.setSelectedIndex(i) call. If not that you'll have to intercept and handle events. – DSquare Aug 01 '13 at 11:41
  • I added full code of my AnimatedTabbedPane. I found this somewhere on the Internet but I can't find source right now. – Demiurg Aug 01 '13 at 11:50
  • Animator is from which package/project? – kleopatra Aug 01 '13 at 11:55
  • It is `timingframework` [link](http://mvnrepository.com/artifact/net.java.dev.timingframework/timingframework) – Demiurg Aug 01 '13 at 11:58
  • thanks - should have asked my IDE :-) Anyway, worksforme - I don't see a difference between manually/programatically changing the selection. Maybe something wrong in your setup? Please show a SSCCE that demonstrates the difference in your context – kleopatra Aug 01 '13 at 12:07
  • I added a little visualization of my problem :) – Demiurg Aug 01 '13 at 12:40
  • 1
    nice picture, just can't see much of a relation to the problem :-) Anyway, it's working fine for me, so the way to get more help is ... the SSCCE – kleopatra Aug 01 '13 at 13:49
  • Thank you for your time, during searching more information about my problem I found my anserw :) – Demiurg Aug 02 '13 at 07:16

2 Answers2

2

I found the real reason of my problem.

It was the thread problem, not the animation itself. I was calling setSelectedIndex outside EDT so my JTabbedPane was updated instantly and then the animation from EDT was performed.

The anserw is:

public void setSelectedTab(final int tab) throws InterruptedException, InvocationTargetException{
    if(!SwingUtilities.isEventDispatchThread()){
        SwingUtilities.invokeAndWait(new Runnable() {
            @Override
            public void run() {
                animatedTabbedPane.setSelectedIndex(tab);
            }
        });
    }
    else
        animatedTabbedPane.setSelectedIndex(tab);
}

Changing tabs inside EDT doesn't couse unwanted flash anymore.

Demiurg
  • 347
  • 1
  • 4
  • 14
1

One approach would be to add an AncestorListener to each tab's content. Let the listener trigger the desired effect as the tab is added to or removed from visibility.

image

import java.awt.EventQueue;
import java.util.Date;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;

/**
 * @see http://stackoverflow.com/a/17993449/230513
 */
public class Test {

    private void display() {
        JFrame f = new JFrame("Test");
        final JTabbedPane jtp = new JTabbedPane();
        jtp.add("One", createPanel());
        jtp.add("Two", createPanel());
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.add(jtp);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    private JPanel createPanel() {
        JPanel panel = new JPanel();
        final JLabel label = new JLabel(new Date().toString());
        panel.add(label);
        panel.addAncestorListener(new AncestorListener() {
            @Override
            public void ancestorAdded(AncestorEvent event) {
                // start animation
                label.setText(new Date().toString());
            }

            @Override
            public void ancestorRemoved(AncestorEvent event) {
                // stop animation
            }

            @Override
            public void ancestorMoved(AncestorEvent event) {
            }
        });
        return panel;
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                new Test().display();
            }
        });
    }
}
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • Some fading approaches are seen [here](http://stackoverflow.com/a/2124507/230513) and [here](http://stackoverflow.com/a/2234020/230513). – trashgod Aug 01 '13 at 12:06
  • Sorry but this is not this. `setSelectedIndex` still interrupt my animation. I need to prevent it from one of its event, so I would have effect like user selecting tab with mouse. – Demiurg Aug 01 '13 at 13:27
  • Are you animating both old and new to smooth the transition? It sounds like you're blocking the EDT. Use `javax.swing.Timer` to pace the animation; invoke `start()` and `stop()` in both the `AncestorListener` and whoever calls `setSelectedIndex()`. – trashgod Aug 01 '13 at 17:47
  • Yes, it was EDT problem. I was changing tabs outside EDT, putting my code inside EDT solves the problem. – Demiurg Aug 02 '13 at 07:18