如何使 JLabel 变得可拖动?

How to make a JLabel became dragable?

伙计们,我想知道是否有办法让这个 arrow 可以只用 X 轴拖动。我这里用的是null layout,这个arrow是加在jframe里的jlabel。这是更多信息的图像。提前谢谢你。

(在解决这个问题之前,我已经编辑了它以显示我的一些代码。这里显示的代码只是关于 ImageIconJLabel 和一些部分JFrame)

public class Level03 extends JFrame implements ActionListener, MouseListener, WindowListener {
// These are the Global Variables of the Level03 Class
ImageIcon slide = new ImageIcon("slide to unlock.png");
    ImageIcon slideButton = new ImageIcon("arrow icon.png");
    JLabel slideLabel = new JLabel(slide);
    JLabel slideArrow = new JLabel(slideButton);
Level03 (){ // This is the constructor
        // This is just setting the bounds of the arrow on the JFrame
        slideLabel.setBounds(100,350, 400,50);
        slideArrow.setBounds(117,350,50,50); // slideArrow is the JLabel with the ImageIcon that looks like an arrow.
        slideArrow.addMouseListener(this);
        mainFrame.add(slideArrow);
        mainFrame.add(slideLabel);
}

注意!!! 我还从 implements ActionListener, MouseListener, WindowListener

下面获得了这些覆盖
@Override
    public void actionPerformed(ActionEvent e){}

@Override
    public void mouseClicked(MouseEvent e) {}

    @Override
    public void mousePressed(MouseEvent e) {}

    @Override
    public void mouseReleased(MouseEvent e) {}

    @Override
    public void mouseEntered(MouseEvent e) {}

    @Override
    public void mouseExited(MouseEvent e) {}

JLabel 做这种事情并非不可能,只是很复杂。

就个人而言,我很想只使用自定义绘制的路线,因为它可以让您进行所有控制。

例如...

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.RoundRectangle2D;
import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.border.EmptyBorder;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    JFrame frame = new JFrame();
                    frame.add(new TestPane());
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                } catch (IOException ex) {
                    Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        });
    }

    public class TestPane extends JPanel {

        public TestPane() throws IOException {
            setBorder(new EmptyBorder(32, 32, 32, 32));
            setLayout(new GridBagLayout());
            add(new SlideToUnlock());
        }

    }

    public class SlideToUnlock extends JPanel {

        private String text;
        private Image indicatorImage;
        private Rectangle indicatorBounds;

        private Integer clickXOffset; // I can make it null and then ignore it
        private Integer dragX; // The x position of the drag

        public SlideToUnlock() throws IOException {
            indicatorImage = ImageIO.read(getClass().getResource("/images/ArrowRight.png"));
            setText("Slide to unlock");

            MouseAdapter mouseAdapter = new MouseAdapter() {
                @Override
                public void mousePressed(MouseEvent e) {
                    resetTimer();

                    Rectangle bounds = getIndiciatorImageBounds();
                    if (bounds.contains(e.getPoint())) {
                        clickXOffset = e.getPoint().x - bounds.x;
                    } else {
                        clickXOffset = null;
                    }
                    dragX = null;
                    repaint();
                }

                @Override
                public void mouseReleased(MouseEvent e) {
                    invalidate();
                    repaint();
                }

                @Override
                public void mouseDragged(MouseEvent e) {
                    dragX = e.getPoint().x;
                    if (didReachTheOtherSide()) {
                        // Notifiy some kind of observer
                    }
                    repaint();
                }
            };

            addMouseListener(mouseAdapter);
            addMouseMotionListener(mouseAdapter);
        }

        @Override
        public Dimension getPreferredSize() {
            FontMetrics fm = getFontMetrics(getFont());
            Image indicatorImage = getIndicatorImage();
            Insets insets = getInsets();

            int imageWidth = 0;
            int imageHeight = 0;

            if (indicatorImage != null) {
                imageWidth = indicatorImage.getWidth(this);
                imageHeight = indicatorImage.getHeight(this);
            }

            int height = Math.max(fm.getHeight(), imageHeight)
                    + 1 // Border
                    + 8; // Inner track

            int width = 1 + 8 + fm.stringWidth(getText()) + imageWidth;

            width += insets.left + insets.right;
            height += insets.top + insets.top;

            return new Dimension(width, height);
        }

        @Override
        public void invalidate() {
            super.invalidate();
            indicatorBounds = null;
            clickXOffset = null;
            dragX = null;
        }

        @Override
        public void revalidate() {
            super.revalidate();
            indicatorBounds = null;
            clickXOffset = null;
            dragX = null;
        }

        protected boolean didReachTheOtherSide() {
            Rectangle bounds = getIndiciatorImageBounds();

            return bounds.x + bounds.width >= getWidth() - 1;
        }

        protected Rectangle getIndiciatorImageBounds() {
            if (getParent() == null) {
                return null;
            }

            if (dragX == null && indicatorBounds != null) {
                return indicatorBounds;
            }

            Image indicatorImage = getIndicatorImage();
            int indicatorX = 1;
            int indicatorY = (getHeight() - indicatorImage.getHeight(this)) / 2;
            indicatorBounds = new Rectangle(indicatorX, indicatorY, indicatorImage.getWidth(this), indicatorImage.getHeight(this));

            if (dragX != null) {
                indicatorBounds.x = (indicatorBounds.x - clickXOffset) + dragX;

                if (indicatorBounds.x + indicatorBounds.width > (getWidth() - 1)) {
                    indicatorBounds.x = getWidth() - indicatorBounds.width - 1;
                } else if (indicatorBounds.x < 1) {
                    indicatorBounds.x = 1;
                }
            }

            return indicatorBounds;
        }

        public String getText() {
            return text;
        }

        public void setText(String text) {
            this.text = text;
        }

        public Image getIndicatorImage() {
            return indicatorImage;
        }

        public void setIndicatorImage(Image indicatorImage) {
            this.indicatorImage = indicatorImage;
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();

            int cornerRadius = 16;

            paintText(g2d);
            paintOverlay(g2d);
            paintIndicator(g2d);

            g2d.setColor(getForeground());
            g2d.draw(new RoundRectangle2D.Double(0, 0, getWidth() - 1, getHeight() - 1, cornerRadius, cornerRadius));

            g2d.dispose();
        }

        protected void paintOverlay(Graphics2D g) {
            Graphics2D g2d = (Graphics2D) g.create();
            g2d.setColor(getBackground());
            Rectangle bounds = getIndiciatorImageBounds();
            g2d.fillRect(1, 1, bounds.x + bounds.width, getHeight() - 2);
            g2d.dispose();
        }

        protected void paintText(Graphics2D g) {
            Graphics2D g2d = (Graphics2D) g.create();
            g2d.setColor(getForeground());
            FontMetrics fm = g2d.getFontMetrics();
            String text = getText();
            int xPos = getWidth() - 1 - 4 - fm.stringWidth(text);
            int yPos = ((getHeight() - fm.getHeight()) / 2) + fm.getAscent();
            g2d.drawString(text, xPos, yPos);
            g2d.dispose();
        }

        protected void paintIndicator(Graphics2D g) {
            Graphics2D g2d = (Graphics2D) g.create();
            Rectangle bounds = getIndiciatorImageBounds();

            g2d.translate(bounds.x, bounds.y);
            Image indicatorImage = getIndicatorImage();
            g2d.drawImage(indicatorImage, 0, 0, this);
            g2d.dispose();
        }

    }
}

好的,所以,“有效”,它完成了工作,但它缺少一些东西......反弹!当用户松开滑动控件时,它应该为反弹设置动画!

(我打赌你很抱歉你现在问了)

啊,这样更好,是小细节造就了

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.RoundRectangle2D;
import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.border.EmptyBorder;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    JFrame frame = new JFrame();
                    frame.add(new TestPane());
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                } catch (IOException ex) {
                    Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        });
    }

    public class TestPane extends JPanel {

        public TestPane() throws IOException {
            setBorder(new EmptyBorder(32, 32, 32, 32));
            setLayout(new GridBagLayout());
            add(new SlideToUnlock());
        }

    }

    public class SlideToUnlock extends JPanel {

        private String text;
        private Image indicatorImage;
        private Rectangle indicatorBounds;

        private Integer clickXOffset; // I can make it null and then ignore it
        private Integer dragX; // The x position of the drag

        private Timer reboundTimer;

        public SlideToUnlock() throws IOException {
            indicatorImage = ImageIO.read(getClass().getResource("/images/ArrowRight.png"));
            setText("Slide to unlock");

            MouseAdapter mouseAdapter = new MouseAdapter() {
                @Override
                public void mousePressed(MouseEvent e) {
                    resetReboundTimer();

                    Rectangle bounds = getIndiciatorImageBounds();
                    if (bounds.contains(e.getPoint())) {
                        clickXOffset = e.getPoint().x - bounds.x;
                    } else {
                        clickXOffset = null;
                    }
                    dragX = null;
                    repaint();
                }

                @Override
                public void mouseReleased(MouseEvent e) {
                    startReboundTimer();
                }

                @Override
                public void mouseDragged(MouseEvent e) {
                    dragX = e.getPoint().x;
                    if (didReachTheOtherSide()) {
                        // Notifiy some kind of observer
                    }
                    repaint();
                }
            };

            addMouseListener(mouseAdapter);
            addMouseMotionListener(mouseAdapter);
        }

        protected void resetReboundTimer() {
            if (reboundTimer == null) {
                return;
            }
            reboundTimer.stop();
            reboundTimer = null;
        }

        protected void startReboundTimer() {
            resetReboundTimer();
            Rectangle bounds = getIndiciatorImageBounds();

            clickXOffset = 0;
            dragX = bounds.x + (bounds.width / 2);

            int lowerRange = 1 + (bounds.width / 2);
            int upperRange = getWidth() - 1 - (bounds.width / 2);

            int fullRange = upperRange - lowerRange;
            int currentRange = (bounds.x + (bounds.width / 2)) - lowerRange;
            double progressRange = currentRange / (double) fullRange;
            Duration fullDuration = Duration.ofMillis(250);
            Duration desiredDuration = Duration.ofMillis((long) (fullDuration.toMillis() * progressRange));

            int remainingRange = (int) (fullRange * progressRange);

            reboundTimer = new Timer(5, new ActionListener() {
                private Instant startTime = null;

                @Override
                public void actionPerformed(ActionEvent e) {
                    if (startTime == null) {
                        startTime = Instant.now();
                    }
                    Duration runTime = Duration.between(startTime, Instant.now());
                    double runTimeProgress = runTime.toMillis() / (double) desiredDuration.toMillis();
                    if (runTimeProgress >= 1.0) {
                        resetReboundTimer();
                        invalidate();
                    } else {
                        dragX = (int) (remainingRange * (1.0 - runTimeProgress));
                    }

                    repaint();
                }
            });
            reboundTimer.setInitialDelay(0);
            reboundTimer.start();
        }

        @Override
        public Dimension getPreferredSize() {
            FontMetrics fm = getFontMetrics(getFont());
            Image indicatorImage = getIndicatorImage();
            Insets insets = getInsets();

            int imageWidth = 0;
            int imageHeight = 0;

            if (indicatorImage != null) {
                imageWidth = indicatorImage.getWidth(this);
                imageHeight = indicatorImage.getHeight(this);
            }

            int height = Math.max(fm.getHeight(), imageHeight)
                    + 1 // Border
                    + 8; // Inner track

            int width = 1 + 8 + fm.stringWidth(getText()) + imageWidth;

            width += insets.left + insets.right;
            height += insets.top + insets.top;

            return new Dimension(width, height);
        }

        @Override
        public void invalidate() {
            super.invalidate();
            indicatorBounds = null;
            clickXOffset = null;
            dragX = null;
        }

        @Override
        public void revalidate() {
            super.revalidate();
            indicatorBounds = null;
            clickXOffset = null;
            dragX = null;
        }

        protected boolean didReachTheOtherSide() {
            Rectangle bounds = getIndiciatorImageBounds();

            return bounds.x + bounds.width >= getWidth() - 1;
        }

        protected Rectangle getIndiciatorImageBounds() {
            if (getParent() == null) {
                return null;
            }

            if (dragX == null && indicatorBounds != null) {
                return indicatorBounds;
            }

            Image indicatorImage = getIndicatorImage();
            int indicatorX = 1;
            int indicatorY = (getHeight() - indicatorImage.getHeight(this)) / 2;
            indicatorBounds = new Rectangle(indicatorX, indicatorY, indicatorImage.getWidth(this), indicatorImage.getHeight(this));

            if (dragX != null) {
                indicatorBounds.x = (indicatorBounds.x - clickXOffset) + dragX;

                if (indicatorBounds.x + indicatorBounds.width > (getWidth() - 1)) {
                    indicatorBounds.x = getWidth() - indicatorBounds.width - 1;
                } else if (indicatorBounds.x < 1) {
                    indicatorBounds.x = 1;
                }
            }

            return indicatorBounds;
        }

        public String getText() {
            return text;
        }

        public void setText(String text) {
            this.text = text;
        }

        public Image getIndicatorImage() {
            return indicatorImage;
        }

        public void setIndicatorImage(Image indicatorImage) {
            this.indicatorImage = indicatorImage;
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();

            int cornerRadius = 16;

            paintText(g2d);
            paintOverlay(g2d);
            paintIndicator(g2d);

            g2d.setColor(getForeground());
            g2d.draw(new RoundRectangle2D.Double(0, 0, getWidth() - 1, getHeight() - 1, cornerRadius, cornerRadius));

            g2d.dispose();
        }

        protected void paintOverlay(Graphics2D g) {
            Graphics2D g2d = (Graphics2D) g.create();
            g2d.setColor(getBackground());
            Rectangle bounds = getIndiciatorImageBounds();
            g2d.fillRect(1, 1, bounds.x + bounds.width, getHeight() - 2);
            g2d.dispose();
        }

        protected void paintText(Graphics2D g) {
            Graphics2D g2d = (Graphics2D) g.create();
            g2d.setColor(getForeground());
            FontMetrics fm = g2d.getFontMetrics();
            String text = getText();
            int xPos = getWidth() - 1 - 4 - fm.stringWidth(text);
            int yPos = ((getHeight() - fm.getHeight()) / 2) + fm.getAscent();
            g2d.drawString(text, xPos, yPos);
            g2d.dispose();
        }

        protected void paintIndicator(Graphics2D g) {
            Graphics2D g2d = (Graphics2D) g.create();
            Rectangle bounds = getIndiciatorImageBounds();

            g2d.translate(bounds.x, bounds.y);
            Image indicatorImage = getIndicatorImage();
            g2d.drawImage(indicatorImage, 0, 0, this);
            g2d.dispose();
        }

    }
}

现在,使用标签执行此操作将遵循非常相似的过程,唯一的补充是,您必须自己管理标签大小和位置(因为大多数布局管理器不允许您这样做)

以下是通过 MouseMotionListener

拖动标签的一些示例
  • JLabel is not moving properly using mouse motion listener, Why?
  • How to prevent JLabel positions from resetting?
  • JLabels, that store ImageIcons, are returned back to original location when the mouse is clicked in the panel
  • How to make draggable components with ImageIcon

现在,在您的情况下,您并不真正关心 y 位置,因此它可以相对于容器(或轨道)保持居中,您只需要更新 x 根据点击偏移量和当前拖动位置的位置。复杂的情况是您需要监视标签的拖动,但要将移动的位置转换为父容器。可行,但这是一个额外的并发症

有了JLabel

基本工作流程是一样的,只是现在您需要管理标签的组件状态(实际上是标签,因为我假设您还想要一些文本)。

这是一个允许您拖动标签的基本示例,其核心工作流程与前面的示例基本相同,但它增加了管理组件大小和位置的开销嗯

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    JFrame frame = new JFrame();
                    frame.add(new TestPane());
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                } catch (IOException ex) {
                    Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        });
    }

    public class TestPane extends JPanel {

        public TestPane() throws IOException {
            setBorder(new EmptyBorder(32, 32, 32, 32));
            setLayout(new GridBagLayout());
            add(new SlideToUnlock());
        }

    }

    public class SlideToUnlock extends JPanel {

        private JLabel indicatorLabel;
        private JPanel overLayPane;
        private JLabel textLabel;

        private Integer clickXOffset;
        private Integer dragX;

        public SlideToUnlock() throws IOException {
            setLayout(null);
            setBorder(new LineBorder(getForeground(), 1, true));
            indicatorLabel = new JLabel(new ImageIcon(ImageIO.read(getClass().getResource("/images/ArrowRight.png"))));
            indicatorLabel.setBounds(1, 1, indicatorLabel.getPreferredSize().width, indicatorLabel.getPreferredSize().height);
            add(indicatorLabel);

            overLayPane = new JPanel();
            add(overLayPane);

            textLabel = new JLabel("Slide to unlock");
            textLabel.setBounds(1, 1, textLabel.getPreferredSize().width, textLabel.getPreferredSize().height);
            add(textLabel);

            MouseAdapter mouseAdapter = new MouseAdapter() {

                protected void resetDrag() {
                    clickXOffset = null;
                    dragX = null;
                }

                @Override
                public void mousePressed(MouseEvent e) {
                    Point point = e.getPoint();
                    Point localPoint = SwingUtilities.convertPoint(SlideToUnlock.this, point, indicatorLabel);
                    if (indicatorLabel.getBounds().contains(localPoint)) {
                        clickXOffset = point.x - indicatorLabel.getBounds().x;
                    } else {
                        resetDrag();
                    }
                }

                @Override
                public void mouseReleased(MouseEvent e) {
                    resetDrag();
                    doLayout();
                    repaint();
                }

                @Override
                public void mouseDragged(MouseEvent e) {
                    dragX = e.getPoint().x;
                    doLayout();
                    repaint();
                }
            };

            addMouseListener(mouseAdapter);
            addMouseMotionListener(mouseAdapter);
        }

        @Override
        public void doLayout() {
            Dimension preferredSize = indicatorLabel.getPreferredSize();
            int xPos = 1;
            if (dragX != null) {
                xPos = (1 - clickXOffset) + dragX;

                if (xPos + preferredSize.width > (getWidth() - 1)) {
                    xPos = getWidth() - preferredSize.width - 1;
                } else if (xPos < 1) {
                    xPos = 1;
                }
            }
            indicatorLabel.setBounds(xPos, 1, indicatorLabel.getPreferredSize().width, getHeight() - 2);
            overLayPane.setBounds(1, 1, indicatorLabel.getX() + indicatorLabel.getWidth() - 1, getHeight() - 2);
            textLabel.setBounds(getWidth() - textLabel.getPreferredSize().width - 4, 1, textLabel.getPreferredSize().width, getHeight() - 2);
        }

        @Override
        public Dimension getPreferredSize() {

            Dimension preferredSize = indicatorLabel.getPreferredSize();
            preferredSize.width = preferredSize.width * 4;

            int height = Math.max(indicatorLabel.getPreferredSize().height, textLabel.getPreferredSize().height);
            int width = indicatorLabel.getPreferredSize().width + 4 + textLabel.getPreferredSize().width + 4 + 1;

            Insets insets = getInsets();

            width += insets.left + insets.right;
            height += insets.top + insets.bottom;

            return new Dimension(width, height);
        }

    }
}

这是一个快速的“hack”。您需要提供附加功能来更改文本、支持更改背景颜色并在更改时更新状态