Trong phần này chúng ta sẽ làm một số thao tác với chuột.
Ví dụ 1
Bất cứ hình nào trong Java cũng đều kế thừa từ lớp Shape, lớp này có phương thức contains()
dùng để kiểm tra xem một điểm có nằm trong phạm vi của hình đó hay không. Trong ví dụ này chúng ta sẽ sử dụng đến phương thức này,
import java.awt.AlphaComposite; import java.awt.Color; import java.awt.EventQueue; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.geom.Ellipse2D; import java.awt.geom.Rectangle2D; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.JFrame; import javax.swing.JPanel; class Surface extends JPanel { private Rectangle2D rect; private Ellipse2D ellipse; private float alpha_rectangle; private float alpha_ellipse; public Surface() { initSurface(); } private void initSurface() { addMouseListener(new HitTestAdapter()); rect = new Rectangle2D.Float(20f, 20f, 80f, 50f); ellipse = new Ellipse2D.Float(120f, 30f, 60f, 60f); alpha_rectangle = 1f; alpha_ellipse = 1f; } private void doDrawing(Graphics g) { Graphics2D g2d = (Graphics2D) g.create(); g2d.setPaint(new Color(50, 50, 50)); RenderingHints rh = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); rh.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); g2d.setRenderingHints(rh); g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha_rectangle)); g2d.fill(rect); g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha_ellipse)); g2d.fill(ellipse); g2d.dispose(); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); doDrawing(g); } class RectRunnable implements Runnable { private Thread runner; public RectRunnable() { initThread(); } private void initThread() { runner = new Thread(this); runner.start(); } @Override public void run() { while (alpha_rectangle >= 0) { repaint(); alpha_rectangle += -0.01f; if (alpha_rectangle < 0) { alpha_rectangle = 0; } try { Thread.sleep(50); } catch (InterruptedException ex) { Logger.getLogger(Surface.class.getName()).log(Level.SEVERE, null, ex); } } } } class HitTestAdapter extends MouseAdapter implements Runnable { private RectRunnable rectAnimator; private Thread ellipseAnimator; @Override public void mousePressed(MouseEvent e) { int x = e.getX(); int y = e.getY(); if (rect.contains(x, y)) { rectAnimator = new RectRunnable(); } if (ellipse.contains(x, y)) { ellipseAnimator = new Thread(this); ellipseAnimator.start(); } } @Override public void run() { while (alpha_ellipse >= 0) { repaint(); alpha_ellipse += -0.01f; if (alpha_ellipse < 0) { alpha_ellipse = 0; } try { Thread.sleep(50); } catch (InterruptedException ex) { Logger.getLogger(Surface.class.getName()).log(Level.SEVERE, null, ex); } } } } } public class HitTestingEx extends JFrame { public HitTestingEx() { add(new Surface()); setTitle("Hit testing"); setSize(250, 150); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLocationRelativeTo(null); } public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { @Override public void run() { HitTestingEx ex = new HitTestingEx(); ex.setVisible(true); } }); } }
Chúng ta vẽ một hình chữ nhật và một hình tròn, nếu click chuột lên hình nào thì hình đó sẽ từ từ mờ dần rồi biến mất.
private float alpha_rectangle; private float alpha_ellipse;
Hai biến alpha_rectangle
và alpha_allipse
lưu trữ độ trong suốt của 2 hình. Hai biến này được kiểm soát thông qua các luồng độc lập (chi tiết về lập trình đa luồng sẽ được bàn tới trong một bài viết khác).
Chúng ta tạo lớp HitTestAdapter
, lớp này xử lý sự kiện click chuột. Lớp này implements giao diện Runnable
.
if (ellipse.contains(x, y)) { ellipseAnimator = new Thread(this); ellipseAnimator.start(); }
Khi click vào bên trong hình tròn thì chúng ta tạo một Thread mới rồi gọi phương thức start()
để chạy luồng, ở đây là phương thức run()
của chính lớp HitTestAdapter
.
if (rect.contains(x, y)) { rectAnimator = new RectRunnable(); }
Đối với hình chữ nhật thì chúng ta chạy trong một luồng khác (từ lớp RectRunnable
), chúng ta cho lớp này tự tạo luồng và tự chạy luôn trong phương thức khởi tạo.
public void run() { while (alpha_ellipse >= 0) { repaint(); alpha_ellipse += -0.01f; ... }
Bên trong phương thức run()
chúng ta code các vòng lặp để giảm dần độ trong suốt của các hình.
Ví dụ 2
Trong ví dụ này chúng ta sẽ di chuyển và phóng to/thu nhỏ hình bằng chuột.
import java.awt.Color; import java.awt.EventQueue; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import java.awt.geom.Ellipse2D; import java.awt.geom.Rectangle2D; import javax.swing.JFrame; import javax.swing.JPanel; class Surface extends JPanel { private ZRectangle zrect; private ZEllipse zell; public Surface() { initUI(); } private void initUI() { MovingAdapter ma = new MovingAdapter(); addMouseMotionListener(ma); addMouseListener(ma); addMouseWheelListener(new ScaleHandler()); zrect = new ZRectangle(50, 50, 50, 50); zell = new ZEllipse(150, 70, 80, 80); } private void doDrawing(Graphics g) { Graphics2D g2d = (Graphics2D) g; Font font = new Font("Serif", Font.BOLD, 40); g2d.setFont(font); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); g2d.setPaint(new Color(0, 0, 200)); g2d.fill(zrect); g2d.setPaint(new Color(0, 200, 0)); g2d.fill(zell); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); doDrawing(g); } class ZEllipse extends Ellipse2D.Float { public ZEllipse(float x, float y, float width, float height) { setFrame(x, y, width, height); } public boolean isHit(float x, float y) { return getBounds2D().contains(x, y); } public void addX(float x) { this.x += x; } public void addY(float y) { this.y += y; } public void addWidth(float w) { this.width += w; } public void addHeight(float h) { this.height += h; } } class ZRectangle extends Rectangle2D.Float { public ZRectangle(float x, float y, float width, float height) { setRect(x, y, width, height); } public boolean isHit(float x, float y) { return getBounds2D().contains(x, y); } public void addX(float x) { this.x += x; } public void addY(float y) { this.y += y; } public void addWidth(float w) { this.width += w; } public void addHeight(float h) { this.height += h; } } class MovingAdapter extends MouseAdapter { private int x; private int y; @Override public void mousePressed(MouseEvent e) { x = e.getX(); y = e.getY(); } @Override public void mouseDragged(MouseEvent e) { doMove(e); } private void doMove(MouseEvent e) { int dx = e.getX() - x; int dy = e.getY() - y; if (zrect.isHit(x, y)) { zrect.addX(dx); zrect.addY(dy); repaint(); } if (zell.isHit(x, y)) { zell.addX(dx); zell.addY(dy); repaint(); } x += dx; y += dy; } } class ScaleHandler implements MouseWheelListener { @Override public void mouseWheelMoved(MouseWheelEvent e) { doScale(e); } private void doScale(MouseWheelEvent e) { int x = e.getX(); int y = e.getY(); if (e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL) { if (zrect.isHit(x, y)) { float amount = e.getWheelRotation() * 5f; zrect.addWidth(amount); zrect.addHeight(amount); repaint(); } if (zell.isHit(x, y)) { float amount = e.getWheelRotation() * 5f; zell.addWidth(amount); zell.addHeight(amount); repaint(); } } } } } public class MovingScalingEx extends JFrame { public MovingScalingEx() { initUI(); } private void initUI() { add(new Surface()); setTitle("Moving and scaling"); setSize(300, 300); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLocationRelativeTo(null); } public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { @Override public void run() { MovingScalingEx ex = new MovingScalingEx(); ex.setVisible(true); } }); } }
Chúng ta vẽ một hình vuông và một hình tròn, khi chuột đang nằm trong phạm vi của một trong hai hình, chúng ta có thể kéo hình đó đi bằng cách click giữ và kéo, hoặc phóng to/thu nhỏ hình bằng con lăn trên chuột.
private ZRectangle zrect; private ZEllipse zell;
Ở đây chúng ta không dùng các lớp hình học có sẵn mà tự viết một lớp kế thừa.
addMouseMotionListener(ma); addMouseListener(ma); addMouseWheelListener(new ScaleHandler());
Ba dòng code trên thêm các đối tượng lắng nghe sự kiện click chuột, kéo chuột và lăn con lăn.
class ZEllipse extends Ellipse2D.Float { public ZEllipse(float x, float y, float width, float height) { setFrame(x, y, width, height); } public boolean isHit(float x, float y) { return getBounds2D().contains(x, y); } ... }
Chúng ta kế thừa các lớp hình học và thêm vào một số phương thức để chủ động hơn trong việc thay đổi kích thước, vị trí… Ngoài ra chúng ta còn có phương thức isHit()
để kiểm tra xem vị trí của chuột có nằm trong hình hay không.
Lớp MovingAdapter
chịu trách nhiệm xử lý sự kiện click chuột và kéo thả chuột.
@Override public void mousePressed(MouseEvent e) { x = e.getX(); y = e.getY(); }
Trong phương thức mousePressed(),
chúng ta lưu lại vị trí của chuột.
int dx = e.getX() - x; int dy = e.getY() - y; if (zrect.isHit(x, y)) { zrect.addX(dx); zrect.addY(dy); repaint(); } if(zell.isHit(x, y)) { zell.addX(dx); zell.addY(dy); repaint(); } x += dx; y += dy;
Trong phương thức doMove()
, nếu vị trí của chuột nằm trong vùng của hình thì chúng ta tính khoảng cách x, y hiện tại của chuột với vị trí khi click rồi di chuyển hình theo đúng vị trí đó.
Lớp ScaleHandler
phụ trách việc phóng to/thu nhỏ hình.
if (e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL) { if (zrect.isHit(x, y)) { float amount = e.getWheelRotation() * 5f; zrect.addWidth(amount); zrect.addHeight(amount); repaint(); } ... }
Nếu chuột đang nằm trong hình và chúng ta lăn con lăn trên chuột thì kích thước của hình sẽ được phóng to hoặc thu nhỏ. Phương thức getWheelRotation()
trả về số góc mà con lăn đã quay.
Ví dụ 3
Trong ví dụ này, chúng ta vẽ một hình chữ nhật lớn, ở hai góc của hình chữ nhật này có 2 hình vuông nhỏ, chúng ta có thể kéo các hình vuông nhỏ này để thay đổi kích thước của hình chữ nhật lớn.
import java.awt.EventQueue; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Point; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import javax.swing.JFrame; import javax.swing.JPanel; class Surface extends JPanel { private Point2D[] points; private final int SIZE = 8; private int pos; public Surface() { initUI(); } private void initUI() { addMouseListener(new ShapeTestAdapter()); addMouseMotionListener(new ShapeTestAdapter()); pos = -1; points = new Point2D[2]; points[0] = new Point2D.Double(50, 50); points[1] = new Point2D.Double(150, 100); } private void doDrawing(Graphics g) { Graphics2D g2 = (Graphics2D) g; for (Point2D point : points) { double x = point.getX() - SIZE / 2; double y = point.getY() - SIZE / 2; g2.fill(new Rectangle2D.Double(x, y, SIZE, SIZE)); } Rectangle2D r = new Rectangle2D.Double(); r.setFrameFromDiagonal(points[0], points[1]); g2.draw(r); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); doDrawing(g); } private class ShapeTestAdapter extends MouseAdapter { @Override public void mousePressed(MouseEvent event) { Point p = event.getPoint(); for (int i = 0; i < points.length; i++) { double x = points[i].getX() - SIZE / 2; double y = points[i].getY() - SIZE / 2; Rectangle2D r = new Rectangle2D.Double(x, y, SIZE, SIZE); if (r.contains(p)) { pos = i; return; } } } @Override public void mouseReleased(MouseEvent event) { pos = -1; } @Override public void mouseDragged(MouseEvent event) { if (pos == -1) { return; } points[pos] = event.getPoint(); repaint(); } } } public class ResizingRectangleEx extends JFrame { public ResizingRectangleEx() { initUI(); } private void initUI() { add(new Surface()); setTitle("Resize rectangle"); setSize(300, 300); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLocationRelativeTo(null); } public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { @Override public void run() { ResizingRectangleEx ex = new ResizingRectangleEx(); ex.setVisible(true); } }); } }
Có hai cách để vẽ một hình chữ nhật, cách thứ nhất là cho tọa độ x, y của điểm trái-trên và số đo dài-rộng rồi vẽ, cách thứ hai là cho tọa độ x, y của 2 điểm trái-trên và phải-dưới rồi vẽ. Trong ví dụ này chúng ta dùng cả hai cách đó.
private Point2D[] points;
Đầu tiên là hai chình vuông nhỏ, chúng ta lưu trong 2 đối tượng Point2D
.
private final int SIZE = 8;
Kích thước của hai hình vuông lưu trong hằng số SIZE
.
points = new Point2D[2]; points[0] = new Point2D.Double(50, 50); points[1] = new Point2D.Double(150, 100);
Khởi tạo vị trí ban đầu cho hai hình vuông.
for (int i = 0; i < points.length; i++) { double x = points[i].getX() - SIZE / 2; double y = points[i].getY() - SIZE / 2; g2.fill(new Rectangle2D.Double(x, y, SIZE, SIZE)); }
Trong phương thức doDrawing()
, đầu tiên chúng ta vẽ hai hình vuông nhỏ trước.
Rectangle2D s = new Rectangle2D.Double(); s.setFrameFromDiagonal(points[0], points[1]); g2.draw(s);
Tiếp theo chúng ta vẽ một hình chữ nhật dựa theo hai hình vuông nhỏ.
@Override public void mousePressed(MouseEvent event) { Point p = event.getPoint(); for (int i = 0; i < points.length; i++) { double x = points[i].getX() - SIZE / 2; double y = points[i].getY() - SIZE / 2; Rectangle2D r = new Rectangle2D.Double(x, y, SIZE, SIZE); if (r.contains(p)) { pos = i; return; } } }
Trong phương thức mousePressed()
, chúng ta kiểm tra xem chuột có nằm trong một trong hai hình vuông nhỏ hay không, nếu có thì lưu thứ tự của hình vuông đó lại trong biến pos
.
@Override public void mouseDragged(MouseEvent event) { if (pos == -1) { return; } points[pos] = event.getPoint(); repaint(); }
Trong phương thức mouseDragged(),
nếu chuột đang nằm trong một hình vuông thì chúng ta cập nhật lại tọa độ của hình vuông đó theo tọa độ của chuột.