TA的每日心情 | 开心 2021-3-12 23:18 |
---|
签到天数: 2 天 [LV.1]初来乍到
|
Swing中Popup结构是Swing所有弹出窗口的基础类。Swing的弹出式窗口包括菜单、JComboBox下拉框、JToolTip等
,它们都使用PopupFactory.getPopup来获得Popup窗口。目前Swing弹出窗口总是瞬时显示,没有像滚动弹出和淡入淡出等动画效果
。但在一些操作系统上,弹出窗口可以被配置为动画弹出模式的。比如在Windows中,通过桌面属性->外观->效
果的对话框,可以为菜单和工具设置位滚动效果。如下图所示:

如果你选择这种效果,Windows桌面应用程序的菜单和提示都会滚动弹出。但即使在Windows启动了这一效果,Swing
仍然使用瞬时弹出模式,对于追求完美的人来说总让人觉得不满意。
幸运的是Swing的PopupFactory工厂提供了使用自定义PopupFactory的方法,你可以编写自己的PopupFactory类,来实现
这种滚动弹出窗口,并在程序开始调用PopupFactory.setSharedInstance(PopupFactory factory)来替换Swing缺省的
PopupFactory,从而使得swing的所有弹出窗口都具有这种效果。
如何编写PopupFactory呢?首先要了解Swing应用程序的Popup窗口类型。为提高效率,Swing会尽可能地使用轻量级的弹出窗口,这在前面的一篇文章《如何混排Swing和AWT组件》中提到过。Swing弹出窗口分为三种类型:轻量级弹出窗口、中量级弹出窗口及重量级弹出窗口。下面简要叙述一下它们适用范围。
轻量级弹出是窗口是继承自JComponent的,它们实际上是添加到顶层容器的JLayeredPane浮动层的普通组件。由于被添加到的层高于ContentPane的层,因此在显示时会覆盖其他Swing组件。这种轻量级组件只能在弹出窗口边界不超出顶层容器的边界时使用,否则显示的内容由于剪裁作用就会不完全。
重量级组件是使用JWindow等重量级顶层容器实现的弹出时窗口。这样做的目的是为了实现边界超出顶层容器边界的弹出窗口。比如多级长菜单经常会超出JFrame的边界,这时它所使用的弹出式窗口就是重量级弹出窗口。
中量级组件是使用AWT/Canvas实现的介于轻量级组件和重量级组件之间的弹出窗口。这类窗口的边界同轻量级组件一样不超出顶层容器的边界,但由于某些原因,比如Swing和AWT混排的窗口,由于AWT组件的Z-order通常要高于Swing组件所依赖的顶层AWT容器组件的Z-order,所以Swing的可扩展组件如菜单就可能被AWT组件所遮盖,这时就要求使用AWT组件来实现弹出窗口。这种情况一般使用AWT/Canvas作为弹出窗口的实现。
因此轻量级弹出窗口的内容组件通常就是弹出窗口本身,而重量级弹出窗口的通常是内容组件的顶层容器JWindow对象,而中量级组件通常是它的父容器AWT/Canvas对象。另外JToolTip是个特殊的依赖于特定组件的组件,当它弹出窗口是轻/中量级时,它的弹出窗口是它的父容器。
明白这些原理后,我们只需要继承Swing的PopupFactory,重载它的getPopup方法,提供自己的Popup。自定义的Popup实现实际上是一个Popup的代理类,该代理类将Popup的显示和关闭方法重载,在显示时启动动画,进行滚动,在关闭前,结束可能的滚动时钟。这个类是这样定义的:
/**
* 这个类是一个Popup代理,将真实Popup的显示过程动画弹出
*/
class PopupProxy extends Popup implements ActionListener{
//一些常量
private static final int ANIMATION_FRAME_INTERVAL=10;
private static final int ANIMATION_FRAMES=10;
//被代理的弹出式窗口,这个弹出式窗口是从缺省工厂那儿获得的。
private Popup popupProxy;
//当前组件
private Component topComponent;
//弹出式窗口最终尺寸
private Dimension fullSize;
//动画时钟
private Timer timer;
//动画的当前帧
private int frameIndex;
public PopupProxy(Popup popup, Component component){
popupProxy=popup;
topComponent=component;
}
/**
* 覆盖show方法启动动画线程
*/
@Override
public void show() {
//代理窗口显示
popupProxy.show();
//获取显示后窗口的最终大小。
fullSize=topComponent.getSize();
//设置窗口的初始尺寸
topComponent.setSize(
horizontalExtending?0:fullSize.width,
verticalExtending?0:fullSize.height);
//初始化为第一帧
frameIndex=1;
//启动动画时钟
timer=new Timer(ANIMATION_FRAME_INTERVAL, this);
timer.start();
}
/**
* 重载hide,关闭可能的时钟
*/
@Override
public void hide() {
if(timer!=null&&timer.isRunning()){
//关闭时钟
timer.stop();
timer=null;
}
//代理弹出窗口关闭
popupProxy.hide();
}
//动画时钟事件的处理,其中一帧
public void actionPerformed(ActionEvent e) {
//设置当前帧弹出窗口组件的尺寸
topComponent.setSize(
horizontalExtending?
fullSize.width*frameIndex/ANIMATION_FRAMES:
fullSize.width,
verticalExtending?
fullSize.height*frameIndex/ANIMATION_FRAMES:
fullSize.height);
if(frameIndex==ANIMATION_FRAMES){
//最后一帧,关闭时钟
timer.stop();
timer=null;
}else
//前进一帧
frameIndex++;
}
}
其构造函数从PopupFactory获取一个Popup及顶层弹出窗口组件。显示时,启动时钟,设置最小尺寸。每一动画帧计算当前尺寸并更新弹出窗口的大小。最后一帧关闭时钟。在关闭弹出窗口时,关闭有可能正在进行动画滚动的时钟。
自定义的ScrollablePopupFactory是这样定义的:
public class ScrollablePopupFactory extends PopupFactory{
//是否横向滚动,缺省不
private boolean horizontalExtending;
//是否垂直滚动,缺省不
private boolean verticalExtending;
/**
* Creates a new instance of ScrollablePopupFactory
*/
public ScrollablePopupFactory(){
}
/**
* @param h 是否横向滚动
* @param v 是否垂直滚动
*/
public ScrollablePopupFactory(boolean h, boolean v) {
horizontalExtending=h;
verticalExtending=v;
}
/**
* 覆盖PopupFactory.getPopup方法提供自定义Popup代理
*
*/
@Override
public Popup getPopup(Component owner, Component contents, int x, int y) throws IllegalArgumentException {
//获取缺省的Popup
Popup popup = super.getPopup(owner, contents, x, y);
//如果纵横都不滚动,直接使用缺省的弹出式窗口
if(!(horizontalExtending||verticalExtending))
return popup;
//目前没有好办法判断弹出窗口是何种类型,只好用类名字来判断
String name=popup.getClass().getName();
if(name.equals("javax.swing.PopupFactory$HeavyWeightPopup")){
//重量级的弹出窗口,其顶层容器为JWindow
return new PopupProxy(
popup,
SwingUtilities.getWindowAncestor(contents));
}else{
//轻量级的弹出窗口
if(contents instanceof JToolTip)
//如果组件是JToolTip,则其父亲容器就是顶层容器
return new PopupProxy(
popup,
contents.getParent());
else
//其他弹出式窗口则组件本身就是顶层容器
return new PopupProxy(
popup,
contents);
}
}
/**
* 这个类是一个Popup代理,将真实Popup的显示过程动画弹出
*/
class PopupProxy extends Popup implements ActionListener{
......
}
}
ScrollablePopupFactory.getPopup使用父类方法的getPopup获取Popup对象,并根据其类型计算出不同弹出式窗口的顶层组件,然后将Popup该顶层组件封装成PopupProxy对象返回给调用者。
如何使用这个ScrollablePopupFactory?只需要在程序开始时设置一下就可以了:
public static void main(String args[]) {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception ex) {}
//设置自定义的PopupFactory,注意纵横都扩展,可以改变布尔值只横向或纵向扩展,或者没有动画
PopupFactory.setSharedInstance(new ScrollablePopupFactory(true, true));
EventQueue.invokeLater(new Runnable() {
public void run() {
new ScrollablePopupDemo().setVisible(true);
}
});
}
下面是演示程序的抓图,菜单正在滚动展开,下面有两个checkbox指定滚动展开方式。另外可以拖动窗口尺寸,以便让菜单、下拉框和ToolTip的窗口边界超过外层窗口的边界,测试重量级弹出窗口的效果。
同样原理可以实现弹出式窗口的淡入淡出动画效果。该演示的源码需要JDK1.6编译,其主类是dyno.swing.beans.test.ScrollablePopupDemo,下载后导入NetBeans进行编译运行。
更新:针对有的网友提出的JInternalFrame内显示菜单先画顶层容器,再在该容器上形成动画效果的问题,我稍微做了一下修改。新版本的不再有这种问题。
源码下载:http://file.javaxxz.com/2014/11/4/235600015.zip |
|