如何使 JLabel 变得可拖动?
How to make a JLabel became dragable?
伙计们,我想知道是否有办法让这个 arrow
可以只用 X 轴拖动。我这里用的是null layout
,这个arrow
是加在jframe
里的jlabel
。这是更多信息的图像。提前谢谢你。
(在解决这个问题之前,我已经编辑了它以显示我的一些代码。这里显示的代码只是关于 ImageIcon
、JLabel
和一些部分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”。您需要提供附加功能来更改文本、支持更改背景颜色并在更改时更新状态
伙计们,我想知道是否有办法让这个 arrow
可以只用 X 轴拖动。我这里用的是null layout
,这个arrow
是加在jframe
里的jlabel
。这是更多信息的图像。提前谢谢你。
(在解决这个问题之前,我已经编辑了它以显示我的一些代码。这里显示的代码只是关于 ImageIcon
、JLabel
和一些部分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”。您需要提供附加功能来更改文本、支持更改背景颜色并在更改时更新状态