Java Swing 中的可拖动组件
创建一个有用的可拖动通用组件,用于创建自定义图形桌面应用程序,如可视化编辑器或多媒体管理。
引言
为了吸引人地展示工作成果,我向您展示这张图片。

这就是目标。一个可视化的照片管理器,带有可拖动的照片。显然这是一个无用的应用程序,但在这里展示是为了demonstrate Swing 的强大功能。
背景
我们处理的基础元素是 JComponent
。
对于我们的目的,此组件是 Swing
框架的图形基础元素。在图中,我们将检查开发的工作流程。

工作流程是从左到右,但继承是从右到左。因此,我们首先需要从 JComponent
开发一个具有拖放功能的 Draggable Component,乍一看这是一个简单的功能,但深入查看会更复杂一些。因此,我们可以为这个特定范围开发一个自定义的 Draggable Component,例如 Image Component。
可拖动组件
我们可以从属性和构造函数开始。
public class DraggableComponent extends JComponent {
/** If sets <b>TRUE</b> this component is draggable */
private boolean draggable = true;
/** 2D Point representing the coordinate where mouse is, relative parent container */
protected Point anchorPoint;
/** Default mouse cursor for dragging action */
protected Cursor draggingCursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
/** If sets <b>TRUE</b> when dragging component,
it will be painted over each other (z-Buffer change) */
protected boolean overbearing = false;
public DraggableComponent() {
addDragListeners();
setOpaque(true);
setBackground(new Color(240,240,240));
}
属性通过注释很容易理解。请注意 anchorPoint
,这是用鼠标“捏住”对象的点。Overbearing
是一个自定义的方式,告诉 Drag.Comp
更改其 z 轴索引以相互重叠。
构造函数很简单。唯一需要提及的是 addDragListerner
方法,我们将在下面看到。
/**
* Add Mouse Motion Listener with drag function
*/
private void addDragListeners() {
/** This handle is a reference to THIS because in next Mouse Adapter
"this" is not allowed */
final DraggableComponent handle = this;
addMouseMotionListener(new MouseAdapter() {
@Override
public void mouseMoved(MouseEvent e) {
anchorPoint = e.getPoint();
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
}
@Override
public void mouseDragged(MouseEvent e) {
int anchorX = anchorPoint.x;
int anchorY = anchorPoint.y;
Point parentOnScreen = getParent().getLocationOnScreen();
Point mouseOnScreen = e.getLocationOnScreen();
Point position = new Point(mouseOnScreen.x - parentOnScreen.x -
anchorX, mouseOnScreen.y - parentOnScreen.y - anchorY);
setLocation(position);
//Change Z-Buffer if it is "overbearing"
if (overbearing) {
getParent().setComponentZOrder(handle, 0);
repaint();
}
}
});
}
此方法向对象上的 MouseMotion
添加一个侦听器。它必须重写两个事件。MouseMoved
用于始终保留鼠标-组件的相对坐标。MouseDragged
用于拖动对象(是 MouseDown + MouseMove
事件)。它使用特殊的向量计算,从 anchorPoint
(见上文)检索组件在拖动时的 Position
(left, top)。如果使用简单的鼠标坐标来设置组件的位置,会有一个糟糕的闪烁效果。下面是计算的表示,如果您感兴趣(否则请相信我):

最后两个重要方法是:
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (isOpaque()) {
g.setColor(getBackground());
g.fillRect(0, 0, getWidth(), getHeight());
}
}
private void removeDragListeners() {
for (MouseMotionListener listener : this.getMouseMotionListeners()) {
removeMouseMotionListener(listener);
}
setCursor(Cursor.getDefaultCursor());
}
第一个仅用于在 isOpaque = true
时绘制彩色框,因为否则此组件是透明的。第二个是在我们决定此组件需要冻结时移除侦听器。
可拖动图片组件
一如既往,我们从头开始。
public class DraggableImageComponent
extends DraggableComponent implements ImageObserver {
protected Image image;
private boolean autoSize = false;
private Dimension autoSizeDimension = new Dimension(0, 0);
public DraggableImageComponent() {
super();
setLayout(null);
setBackground(Color.black);
}
我们拥有的属性较少。Image
是要在组件中显示的图片,autoSize
是 TRUE
如果组件显示图片时保持原始比例,autoSizeDimension
用于此目的。我们扩展 DraggableComponent
,因为我们想要一个可拖动的图片项,但通过调用 setDraggable(false)
,我们可以得到一个简单的 static
图片组件。它实现了 ImageObserver
,因为它必须实现一个回调方法,该方法在某些图像加载完成时被调用。应用程序创建线程来加载外部数据,如图像,因为这可能需要更多时间,所以我们在加载完成后有一个回调。
下一篇
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.clearRect(0, 0, getWidth(), getHeight());
if (image != null) {
setAutoSizeDimension();
g2d.drawImage(image, 0, 0, getWidth(), getHeight(), this);
} else {
g2d.setColor(getBackground());
g2d.fillRect(0, 0, getWidth(), getHeight());
}
}
这是 ImageComponent
的核心,因为它在这里绘制选定的图像。它避免调用父类相应的方法,因为它不重要。但是,它非常容易理解。
图像缩放方法
private void setAutoSizeDimension() { // }
private Dimension adaptDimension(Dimension source, Dimension dest) { // }
它们对我们的讨论不是很重要,但是它们是保持 w/h 比例调整图像大小的简单方法。 如果您想进一步解释,请给我留言。
重要提示,正如我上面所说,是为图像加载创建回调。
public boolean imageUpdate(Image img, int infoflags, int x, int y, int w, int h) {
if (infoflags == ALLBITS) {
repaint();
setAutoSizeDimension();
return false;
}
return true;
}
这是 ImageObserver
实现的重写方法。InfoFlags
可以有多个值,当它等于 ImageObserver.ALLBITS
时,图像已加载。如果您不创建回调,图像将在第一次 repaint()
后加载,因此您看不到图像,只能看到一个黑框或空白框。
FooPhotoAlbum.JAR
这是第一个截图的应用程序。这是一个简单的 foo 应用程序,用于解释这些组件的用法。我在这里不发布代码片段,因为它过于分散。但是,您可以在源文件 - Main.Java 中阅读它,其中包含注释以帮助您理解工作流程。它非常简单:创建一个 DraggableImageComponents
的容器,并动态地将图像放入其中。您可以看到,只需很少的代码行即可完成。
结论
一如既往,这两个主要组件是完全可重用的。您可以在任何地方使用它们,并且可以自定义它们。只需几行代码,您就可以创建一个可拖动组件,例如用于多种用途的可拖动面板。例如,如果您想到一个可视化编辑器,例如 OpenOffice Draw,其基础就是一组可拖动的项。另一个功能是组件的 RESIZE。我们可以通过在 DraggableComponent
之前或之后插入另一个继承级别来轻松实现这一点,这样我们就可以扩展一个完全可拖动和可调整大小的 XXXComponent。如果您对此感兴趣,请投票!这样我就会知道您的需求。:)