我有带有淡入淡出动画的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
标签时,会立即替换为标签1 ,然后我才能从stateChanged
看到我想要的动画,因此用户可以看到闪光。
答案 0 :(得分:2)
我找到了问题的真正原因。
这是线程问题,而不是动画本身。我在 EDT 之外呼叫setSelectedIndex
,因此我的JTabbedPane立即更新,然后执行了来自 EDT 的动画。
anserw是:
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 中的标签不再用于不需要的闪光灯。
答案 1 :(得分:1)
一种方法是在每个标签的内容中添加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();
}
});
}
}