Fire stateChanged event on JTabbedPane

耗尽温柔 提交于 2019-12-02 02:52:26

问题


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:

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.


回答1:


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.




回答2:


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.

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();
            }
        });
    }
}


来源:https://stackoverflow.com/questions/17992512/fire-statechanged-event-on-jtabbedpane

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!