บางครั้งมันมีประโยชน์มากที่จะใช้เวลาสร้างล้อ อย่างที่คุณอาจสังเกตเห็นแล้วว่ามีเฟรมเวิร์กมากมาย แต่ก็ไม่ยากที่จะใช้โซลูชันที่เรียบง่าย แต่มีประโยชน์โดยไม่ต้องแนะนำความซับซ้อนทั้งหมดนั้น (โปรดอย่าเข้าใจฉันผิดเพื่อจุดประสงค์ที่จริงจังจะเป็นการดีกว่าหากใช้บางอย่างที่เป็นผู้ใหญ่และพิสูจน์แล้วว่าเป็นเฟรมเวิร์กที่เสถียร)
ฉันจะนำเสนอผลลัพธ์ของฉันก่อนแล้วจึงอธิบายแนวคิดที่เรียบง่ายและตรงไปตรงมา
คุณจะเห็นในการใช้งานของฉันไม่จำเป็นต้องวิเคราะห์ทุก ๆ จุดและทำการคำนวณที่ซับซ้อน แนวคิดคือการสังเกตข้อมูลเมตาที่มีค่าบางอย่าง ฉันจะใช้แทนเจนต์เป็นตัวอย่าง:
ลองระบุรูปแบบที่เรียบง่ายและตรงไปตรงมาโดยทั่วไปสำหรับรูปร่างที่เลือก:
ดังนั้นจึงไม่ยากที่จะใช้กลไกการตรวจจับแบบวงกลมตามแนวคิดนั้น ดูตัวอย่างการทำงานด้านล่าง (ขออภัยฉันใช้ Java เป็นวิธีที่เร็วที่สุดในการให้ตัวอย่างรวดเร็วและสกปรก):
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.HeadlessException;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class CircleGestureDemo extends JFrame implements MouseListener, MouseMotionListener {
enum Type {
RIGHT_DOWN,
LEFT_DOWN,
LEFT_UP,
RIGHT_UP,
UNDEFINED
}
private static final Type[] circleShape = {
Type.RIGHT_DOWN,
Type.LEFT_DOWN,
Type.LEFT_UP,
Type.RIGHT_UP};
private boolean editing = false;
private Point[] bounds;
private Point last = new Point(0, 0);
private List<Point> points = new ArrayList<>();
public CircleGestureDemo() throws HeadlessException {
super("Detect Circle");
addMouseListener(this);
addMouseMotionListener(this);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setPreferredSize(new Dimension(800, 600));
pack();
}
@Override
public void paint(Graphics graphics) {
Dimension d = getSize();
Graphics2D g = (Graphics2D) graphics;
super.paint(g);
RenderingHints qualityHints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
qualityHints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g.setRenderingHints(qualityHints);
g.setColor(Color.RED);
if (cD == 0) {
Point b = null;
for (Point e : points) {
if (null != b) {
g.drawLine(b.x, b.y, e.x, e.y);
}
b = e;
}
}else if (cD > 0){
g.setColor(Color.BLUE);
g.setStroke(new BasicStroke(3));
g.drawOval(cX, cY, cD, cD);
}else{
g.drawString("Uknown",30,50);
}
}
private Type getType(int dx, int dy) {
Type result = Type.UNDEFINED;
if (dx > 0 && dy < 0) {
result = Type.RIGHT_DOWN;
} else if (dx < 0 && dy < 0) {
result = Type.LEFT_DOWN;
} else if (dx < 0 && dy > 0) {
result = Type.LEFT_UP;
} else if (dx > 0 && dy > 0) {
result = Type.RIGHT_UP;
}
return result;
}
private boolean isCircle(List<Point> points) {
boolean result = false;
Type[] shape = circleShape;
Type[] detected = new Type[shape.length];
bounds = new Point[shape.length];
final int STEP = 5;
int index = 0;
Point current = points.get(0);
Type type = null;
for (int i = STEP; i < points.size(); i += STEP) {
Point next = points.get(i);
int dx = next.x - current.x;
int dy = -(next.y - current.y);
if(dx == 0 || dy == 0) {
continue;
}
Type newType = getType(dx, dy);
if(type == null || type != newType) {
if(newType != shape[index]) {
break;
}
bounds[index] = current;
detected[index++] = newType;
}
type = newType;
current = next;
if (index >= shape.length) {
result = true;
break;
}
}
return result;
}
@Override
public void mousePressed(MouseEvent e) {
cD = 0;
points.clear();
editing = true;
}
private int cX;
private int cY;
private int cD;
@Override
public void mouseReleased(MouseEvent e) {
editing = false;
if(points.size() > 0) {
if(isCircle(points)) {
cX = bounds[0].x + Math.abs((bounds[2].x - bounds[0].x)/2);
cY = bounds[0].y;
cD = bounds[2].y - bounds[0].y;
cX = cX - cD/2;
System.out.println("circle");
}else{
cD = -1;
System.out.println("unknown");
}
repaint();
}
}
@Override
public void mouseDragged(MouseEvent e) {
Point newPoint = e.getPoint();
if (editing && !last.equals(newPoint)) {
points.add(newPoint);
last = newPoint;
repaint();
}
}
@Override
public void mouseMoved(MouseEvent e) {
}
@Override
public void mouseEntered(MouseEvent e) {
}
@Override
public void mouseExited(MouseEvent e) {
}
@Override
public void mouseClicked(MouseEvent e) {
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
CircleGestureDemo t = new CircleGestureDemo();
t.setVisible(true);
}
});
}
}
มันไม่ควรเป็นปัญหาที่จะใช้พฤติกรรมที่คล้ายกันบน iOS เนื่องจากคุณเพียงแค่ต้องการเหตุการณ์และพิกัดหลายอย่าง สิ่งต่อไปนี้ (ดูตัวอย่าง ):
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch* touch = [[event allTouches] anyObject];
}
- (void)handleTouch:(UIEvent *)event {
UITouch* touch = [[event allTouches] anyObject];
CGPoint location = [touch locationInView:self];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
[self handleTouch: event];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[self handleTouch: event];
}
มีการปรับปรุงหลายอย่างที่เป็นไปได้
เริ่มที่จุดใดก็ได้
ข้อกำหนดปัจจุบันคือการเริ่มวาดวงกลมจากจุดกึ่งกลางด้านบนเนื่องจากการทำให้เข้าใจง่ายดังต่อไปนี้:
if(type == null || type != newType) {
if(newType != shape[index]) {
break;
}
bounds[index] = current;
detected[index++] = newType;
}
กรุณาแจ้งให้ทราบค่าเริ่มต้นของindex
ถูกนำมาใช้ การค้นหาอย่างง่ายผ่าน "ส่วน" ที่มีอยู่ของรูปร่างจะลบข้อ จำกัด นั้น โปรดทราบว่าคุณจะต้องใช้บัฟเฟอร์วงกลมเพื่อตรวจจับรูปร่างเต็ม:
ตามเข็มนาฬิกาและทวนเข็มนาฬิกา
ในการรองรับทั้งสองโหมดคุณจะต้องใช้บัฟเฟอร์แบบวงกลมจากการปรับปรุงก่อนหน้านี้และค้นหาทั้งสองทิศทาง:
วาดวงรี
คุณมีทุกสิ่งที่คุณต้องการอยู่ในbounds
อาเรย์แล้ว
ใช้ข้อมูลนั้นเพียง:
cWidth = bounds[2].y - bounds[0].y;
cHeight = bounds[3].y - bounds[1].y;
ท่าทางอื่น ๆ (ไม่บังคับ)
สุดท้ายคุณเพียงแค่ต้องจัดการสถานการณ์อย่างถูกต้องเมื่อdx
(หรือdy
) เท่ากับศูนย์เพื่อสนับสนุนท่าทางอื่น ๆ :
ปรับปรุง
PoC ขนาดเล็กนี้ได้รับความสนใจค่อนข้างสูงดังนั้นฉันจึงอัปเดตรหัสเล็กน้อยเพื่อให้ทำงานได้อย่างราบรื่นและให้คำแนะนำการวาดภาพไฮไลต์จุดสนับสนุน ฯลฯ :
นี่คือรหัส:
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.HeadlessException;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class CircleGestureDemo extends JFrame {
enum Type {
RIGHT_DOWN,
LEFT_DOWN,
LEFT_UP,
RIGHT_UP,
UNDEFINED
}
private static final Type[] circleShape = {
Type.RIGHT_DOWN,
Type.LEFT_DOWN,
Type.LEFT_UP,
Type.RIGHT_UP};
public CircleGestureDemo() throws HeadlessException {
super("Circle gesture");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new BorderLayout());
add(BorderLayout.CENTER, new GesturePanel());
setPreferredSize(new Dimension(800, 600));
pack();
}
public static class GesturePanel extends JPanel implements MouseListener, MouseMotionListener {
private boolean editing = false;
private Point[] bounds;
private Point last = new Point(0, 0);
private final List<Point> points = new ArrayList<>();
public GesturePanel() {
super(true);
addMouseListener(this);
addMouseMotionListener(this);
}
@Override
public void paint(Graphics graphics) {
super.paint(graphics);
Dimension d = getSize();
Graphics2D g = (Graphics2D) graphics;
RenderingHints qualityHints = new RenderingHints(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
qualityHints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g.setRenderingHints(qualityHints);
if (!points.isEmpty() && cD == 0) {
isCircle(points, g);
g.setColor(HINT_COLOR);
if (bounds[2] != null) {
int r = (bounds[2].y - bounds[0].y) / 2;
g.setStroke(new BasicStroke(r / 3 + 1));
g.drawOval(bounds[0].x - r, bounds[0].y, 2 * r, 2 * r);
} else if (bounds[1] != null) {
int r = bounds[1].x - bounds[0].x;
g.setStroke(new BasicStroke(r / 3 + 1));
g.drawOval(bounds[0].x - r, bounds[0].y, 2 * r, 2 * r);
}
}
g.setStroke(new BasicStroke(2));
g.setColor(Color.RED);
if (cD == 0) {
Point b = null;
for (Point e : points) {
if (null != b) {
g.drawLine(b.x, b.y, e.x, e.y);
}
b = e;
}
} else if (cD > 0) {
g.setColor(Color.BLUE);
g.setStroke(new BasicStroke(3));
g.drawOval(cX, cY, cD, cD);
} else {
g.drawString("Uknown", 30, 50);
}
}
private Type getType(int dx, int dy) {
Type result = Type.UNDEFINED;
if (dx > 0 && dy < 0) {
result = Type.RIGHT_DOWN;
} else if (dx < 0 && dy < 0) {
result = Type.LEFT_DOWN;
} else if (dx < 0 && dy > 0) {
result = Type.LEFT_UP;
} else if (dx > 0 && dy > 0) {
result = Type.RIGHT_UP;
}
return result;
}
private boolean isCircle(List<Point> points, Graphics2D g) {
boolean result = false;
Type[] shape = circleShape;
bounds = new Point[shape.length];
final int STEP = 5;
int index = 0;
int initial = 0;
Point current = points.get(0);
Type type = null;
for (int i = STEP; i < points.size(); i += STEP) {
final Point next = points.get(i);
final int dx = next.x - current.x;
final int dy = -(next.y - current.y);
if (dx == 0 || dy == 0) {
continue;
}
final int marker = 8;
if (null != g) {
g.setColor(Color.BLACK);
g.setStroke(new BasicStroke(2));
g.drawOval(current.x - marker/2,
current.y - marker/2,
marker, marker);
}
Type newType = getType(dx, dy);
if (type == null || type != newType) {
if (newType != shape[index]) {
break;
}
bounds[index++] = current;
}
type = newType;
current = next;
initial = i;
if (index >= shape.length) {
result = true;
break;
}
}
return result;
}
@Override
public void mousePressed(MouseEvent e) {
cD = 0;
points.clear();
editing = true;
}
private int cX;
private int cY;
private int cD;
@Override
public void mouseReleased(MouseEvent e) {
editing = false;
if (points.size() > 0) {
if (isCircle(points, null)) {
int r = Math.abs((bounds[2].y - bounds[0].y) / 2);
cX = bounds[0].x - r;
cY = bounds[0].y;
cD = 2 * r;
} else {
cD = -1;
}
repaint();
}
}
@Override
public void mouseDragged(MouseEvent e) {
Point newPoint = e.getPoint();
if (editing && !last.equals(newPoint)) {
points.add(newPoint);
last = newPoint;
repaint();
}
}
@Override
public void mouseMoved(MouseEvent e) {
}
@Override
public void mouseEntered(MouseEvent e) {
}
@Override
public void mouseExited(MouseEvent e) {
}
@Override
public void mouseClicked(MouseEvent e) {
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
CircleGestureDemo t = new CircleGestureDemo();
t.setVisible(true);
}
});
}
final static Color HINT_COLOR = new Color(0x55888888, true);
}