Событие fire 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 по умолчанию. Как я могу это сделать?

EDIT Небольшой график моей проблемы:

введите здесь описание изображения

Красная линия представляет видимость. Поэтому, когда пользователь меняет вкладку, вызывается только stateChanged, вкладка 0 плавно меняется на вкладка 1. Когда я использую setSelectedIndex, вкладка 0 немедленно заменяется вкладкой 1, и только тогда я могу увидеть желаемую анимацию из stateChanged, поэтому пользователи видят вспышку.


person Demiurg    schedule 01.08.2013    source источник
comment
Могу я увидеть этот сексуальный код анимации? Мое первое предположение будет заключаться в том, что stateChanged не является подходящим местом для анимации, потому что это происходит после смены табуляции. Возможно, вы могли бы переопределить JTabbedPane.setSelectedIndex(int ​​i) и поместить код анимации перед вызовом super или непосредственно перед вызовом model.setSelectedIndex(i). Если нет, вам придется перехватывать и обрабатывать события.   -  person DSquare    schedule 01.08.2013
comment
Я добавил полный код своего AnimatedTabbedPane. Я нашел это где-то в Интернете, но я не могу найти источник прямо сейчас.   -  person Demiurg    schedule 01.08.2013
comment
Аниматор из какого пакета/проекта?   -  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