65.9K
CodeProject 正在变化。 阅读更多。
Home

Java Swing 中的可拖动组件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (16投票s)

2010年10月6日

Apache

4分钟阅读

viewsIcon

91725

downloadIcon

5376

创建一个有用的可拖动通用组件,用于创建自定义图形桌面应用程序,如可视化编辑器或多媒体管理。

引言

为了吸引人地展示工作成果,我向您展示这张图片。

screen1.PNG

这就是目标。一个可视化的照片管理器,带有可拖动的照片。显然这是一个无用的应用程序,但在这里展示是为了demonstrate Swing 的强大功能。

背景

我们处理的基础元素是 JComponent

对于我们的目的,此组件是 Swing 框架的图形基础元素。在图中,我们将检查开发的工作流程。

uml.PNG

工作流程是从左到右,但继承是从右到左。因此,我们首先需要从 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)。如果使用简单的鼠标坐标来设置组件的位置,会有一个糟糕的闪烁效果。下面是计算的表示,如果您感兴趣(否则请相信我):

Vectors.PNG

最后两个重要方法是:

 @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 是要在组件中显示的图片,autoSizeTRUE 如果组件显示图片时保持原始比例,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。如果您对此感兴趣,请投票!这样我就会知道您的需求。:)

© . All rights reserved.