Задействане на събитие stateChanged на JTabbedPane

Имам JTabbedPane с избледняваща анимация, която се изпълнява, когато потребителят кликне върху раздели. За да се справя с анимация, замествам метода stateChanged.

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

}

Това работи чудесно, когато потребителят щрака раздели ръчно. Извиква се събитие stateChanged и разделите се променят с анимация.

Трябва да променя избрания раздел от кода, така че използвам метода setSelectedIndex(int).

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

Но точно сега разделът се променя незабавно след като setSelectedIndex приключи - без анимация, и след това се извиква методът stateChanged, така че разделът се връща към предишния избран раздел и изпълнява (правилно) моята анимация.

Така разделът се променя два пъти, първия път без анимация след setSelectedIndex и втори път след stateChanged в AnimatedJTabbedPane.

Имам нужда от нещо като:

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

Искам да сменям раздели само с метода stateChanged, така че мога да направя без метода setSelectedIndex по подразбиране. Как мога да направя това?

РЕДАКТИРАНЕ Малка графика на моя проблем:

въведете описание на изображението тук

Червената линия представлява видимост. Така че, когато потребител промени раздела, се извиква само stateChanged, раздел 0 плавно се променя на раздел 1. Когато използвам setSelectedIndex раздел 0 незабавно се заменя с раздел 1 и едва тогава мога да видя желаната от мен анимация от stateChanged, така че потребителите да виждат светкавица.


person Demiurg    schedule 01.08.2013    source източник
comment
Мога ли да видя кода за секси анимация? Първото ми предположение би било, че stateChanged не е подходящото място за извършване на анимацията, защото се случва след промяната на раздела. Може би бихте могли да замените JTabbedPane.setSelectedIndex(int ​​i) и да поставите кода на анимацията преди супер извикване или директно извикване на model.setSelectedIndex(i). Ако не, ще трябва да прихващате и обработвате събития.   -  person DSquare    schedule 01.08.2013
comment
Добавих пълен код на моя AnimatedTabbedPane. Намерих това някъде в интернет, но не мога да намеря източник в момента.   -  person Demiurg    schedule 01.08.2013
comment
Animator от кой пакет/проект е?   -  person kleopatra    schedule 01.08.2013
comment
Това е timingframework връзка   -  person Demiurg    schedule 01.08.2013
comment
благодаря - трябваше да попитам моята IDE :-) Както и да е, workforme - не виждам разлика между ръчна/програмна промяна на селекцията. Може би нещо не е наред в настройката ви? Моля, покажете SSCCE, който демонстрира разликата във вашия контекст   -  person kleopatra    schedule 01.08.2013
comment
Добавих малко визуализация на моя проблем :)   -  person Demiurg    schedule 01.08.2013
comment
хубава снимка, просто не виждам голяма връзка с проблема :-) Както и да е, работи добре за мен, така че начинът да получа повече помощ е ... SSCCE   -  person kleopatra    schedule 01.08.2013
comment
Благодаря ви за отделеното време, по време на търсене на повече информация за моя проблем намерих своя отговор :)   -  person Demiurg    schedule 02.08.2013


Отговори (2)


Открих истинската причина за моя проблем.

Проблемът беше в нишката, а не в самата анимация. Обаждах се на setSelectedIndex извън EDT, така че моят JTabbedPane беше актуализиран незабавно и след това анимацията от EDT беше изпълнена.

Отговорът е:

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

Смяната на раздели вътре в EDT вече не причинява нежелано мигане.

person Demiurg    schedule 02.08.2013

Един подход би бил да добавите AncestorListener към съдържанието на всеки раздел. Оставете слушателя да задейства желания ефект, докато разделът се добавя или премахва от видимостта.

изображение

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();
            }
        });
    }
}
person trashgod    schedule 01.08.2013
comment
Някои избледняващи подходи се виждат тук и тук. - person trashgod; 01.08.2013
comment
Съжалявам, но това не е това. setSelectedIndex все още прекъсва анимацията ми. Трябва да го предотвратя от някое от неговите събития, така че бих имал ефект като избиране от потребител на раздел с мишката. - person Demiurg; 01.08.2013
comment
Анимирате ли стари и нови, за да изгладите прехода? Звучи сякаш блокираш EDT. Използвайте javax.swing.Timer за темпо на анимацията; извикване на start() и stop() както в AncestorListener, така и във всеки, който извиква setSelectedIndex(). - person trashgod; 01.08.2013
comment
Да, това беше проблем с EDT. Сменях раздели извън EDT, поставянето на моя код вътре в EDT решава проблема. - person Demiurg; 02.08.2013