- android/ 167个模块 (app/content/os/view/widget/graphics/media/hardware/net/telephony/provider等) - java/ 35个模块 (lang/io/util/net/nio/security/crypto/sql/beans/rmi/awt等) - javax/ 4个模块 (net/crypto/security/sql) - org/ 5个模块 (json/w3c_dom/xml_sax/xml_pull/apache_http) - dalvik/ 1个模块 (system) - ~3053个类/接口,方法签名完全对齐Android SDK - 桌面跨平台运行 (Windows/macOS/Linux) - 修复所有导入错误和Python关键字冲突
1158 lines
41 KiB
Python
1158 lines
41 KiB
Python
"""
|
|
Android Gesture 扩展组件 — GestureLibraries, GestureBuilderActivity,
|
|
GestureOverlayView (扩展), GestureStroke (扩展), GesturePoint (扩展),
|
|
各种手势识别器 (TapGesture, LongPressGesture, SwipeGesture 等)
|
|
方法签名完全对齐 Android SDK
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import math
|
|
import time
|
|
from typing import Any, Callable, Dict, List, Optional, Tuple
|
|
|
|
from .._core import Stub, AndroidOnlyError
|
|
|
|
__all__ = [
|
|
"GestureLibraries", "GestureBuilderActivity",
|
|
"GestureOverlayView", "GestureStroke", "GesturePoint",
|
|
"OrientationGesture", "FlingGesture", "SwipeGesture",
|
|
"PinchGesture", "TapGesture", "LongPressGesture",
|
|
"DoubleTapGesture", "ScrollGesture", "DragGesture",
|
|
"MultiFingerGesture", "GestureConfiguration", "GestureEvent",
|
|
"GestureRecognizer", "GesturePerformedListener",
|
|
"GestureDrawingListener", "GestureInputListener",
|
|
"GestureFeedback", "GestureFeedbackProvider",
|
|
]
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# 手势常量
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
ORIENTATION_ANY = 0
|
|
ORIENTATION_HORIZONTAL = 1
|
|
ORIENTATION_VERTICAL = 2
|
|
ORIENTATION_DIAGONAL_UP = 3
|
|
ORIENTATION_DIAGONAL_DOWN = 4
|
|
ORIENTATION_LEFT = 5
|
|
ORIENTATION_RIGHT = 6
|
|
ORIENTATION_UP = 7
|
|
ORIENTATION_DOWN = 8
|
|
|
|
GESTURE_STATE_UNDEFINED = 0
|
|
GESTURE_STATE_STARTED = 1
|
|
GESTURE_STATE_IN_PROGRESS = 2
|
|
GESTURE_STATE_COMPLETED = 3
|
|
GESTURE_STATE_CANCELLED = 4
|
|
GESTURE_STATE_FAILED = 5
|
|
|
|
GESTURE_EVENT_TYPE_TAP = 0
|
|
GESTURE_EVENT_TYPE_DOUBLE_TAP = 1
|
|
GESTURE_EVENT_TYPE_LONG_PRESS = 2
|
|
GESTURE_EVENT_TYPE_SWIPE = 3
|
|
GESTURE_EVENT_TYPE_FLING = 4
|
|
GESTURE_EVENT_TYPE_PINCH = 5
|
|
GESTURE_EVENT_TYPE_SCROLL = 6
|
|
GESTURE_EVENT_TYPE_DRAG = 7
|
|
GESTURE_EVENT_TYPE_ROTATION = 8
|
|
GESTURE_EVENT_TYPE_MULTI_FINGER_TAP = 9
|
|
GESTURE_EVENT_TYPE_ORIENTATION = 10
|
|
|
|
GESTURE_LIBRARY_FILE = 0
|
|
GESTURE_LIBRARY_RESOURCE = 1
|
|
GESTURE_LIBRARY_RAW = 2
|
|
GESTURE_LIBRARY_PRIVATE = 3
|
|
|
|
GESTURE_DIRECTION_ANY = 0
|
|
GESTURE_DIRECTION_LEFT = 1
|
|
GESTURE_DIRECTION_RIGHT = 2
|
|
GESTURE_DIRECTION_UP = 3
|
|
GESTURE_DIRECTION_DOWN = 4
|
|
GESTURE_DIRECTION_HORIZONTAL = 5
|
|
GESTURE_DIRECTION_VERTICAL = 6
|
|
|
|
GESTURE_FEEDBACK_NONE = 0
|
|
GESTURE_FEEDBACK_VIBRATE = 1
|
|
GESTURE_FEEDBACK_SOUND = 2
|
|
GESTURE_FEEDBACK_VISUAL = 4
|
|
GESTURE_FEEDBACK_ALL = 7
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# GesturePoint (扩展)
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class GesturePoint(Stub):
|
|
"""GesturePoint — 手势点 (完整版)"""
|
|
|
|
def __init__(self, x: float = 0.0, y: float = 0.0,
|
|
timestamp: int = 0, pressure: float = 1.0,
|
|
size: float = 0.0, pointerId: int = 0):
|
|
super().__init__()
|
|
self._x = x
|
|
self._y = y
|
|
self._timestamp = timestamp
|
|
self._pressure = pressure
|
|
self._size = size
|
|
self._pointer_id = pointerId
|
|
|
|
def getX(self) -> float:
|
|
return self._x
|
|
|
|
def getY(self) -> float:
|
|
return self._y
|
|
|
|
def getTimestamp(self) -> int:
|
|
return self._timestamp
|
|
|
|
def getPressure(self) -> float:
|
|
return self._pressure
|
|
|
|
def getSize(self) -> float:
|
|
return self._size
|
|
|
|
def getPointerId(self) -> int:
|
|
return self._pointer_id
|
|
|
|
def distance(self, other: "GesturePoint") -> float:
|
|
dx = self._x - other._x
|
|
dy = self._y - other._y
|
|
return math.sqrt(dx * dx + dy * dy)
|
|
|
|
def velocity(self, other: "GesturePoint") -> float:
|
|
dt = self._timestamp - other._timestamp
|
|
if dt <= 0:
|
|
return 0.0
|
|
dist = self.distance(other)
|
|
return dist / dt * 1000.0
|
|
|
|
def toString(self) -> str:
|
|
return f"GesturePoint({self._x:.1f}, {self._y:.1f})"
|
|
|
|
def equals(self, other: Any) -> bool:
|
|
if not isinstance(other, GesturePoint):
|
|
return False
|
|
return (self._x == other._x and self._y == other._y and
|
|
self._timestamp == other._timestamp)
|
|
|
|
def hashCode(self) -> int:
|
|
return hash((self._x, self._y, self._timestamp)) & 0x7FFFFFFF
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# GestureStroke (扩展)
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class GestureStroke(Stub):
|
|
"""GestureStroke — 手势笔画 (完整版)"""
|
|
|
|
def __init__(self, points: List[GesturePoint] = None):
|
|
super().__init__()
|
|
self._points = points or []
|
|
self._length = 0.0
|
|
self._bounding_box = (0, 0, 0, 0)
|
|
self._orientation = 0.0
|
|
self._recalculate()
|
|
|
|
def _recalculate(self):
|
|
if not self._points:
|
|
self._length = 0.0
|
|
self._bounding_box = (0, 0, 0, 0)
|
|
return
|
|
min_x = min(p.getX() for p in self._points)
|
|
max_x = max(p.getX() for p in self._points)
|
|
min_y = min(p.getY() for p in self._points)
|
|
max_y = max(p.getY() for p in self._points)
|
|
self._bounding_box = (min_x, min_y, max_x - min_x, max_y - min_y)
|
|
length = 0.0
|
|
for i in range(1, len(self._points)):
|
|
length += self._points[i - 1].distance(self._points[i])
|
|
self._length = length
|
|
|
|
def getPoints(self) -> List[GesturePoint]:
|
|
return list(self._points)
|
|
|
|
def getLength(self) -> float:
|
|
return self._length
|
|
|
|
def getBoundingBox(self) -> Tuple[float, float, float, float]:
|
|
return self._bounding_box
|
|
|
|
def getBoundingBoxCenter(self) -> Tuple[float, float]:
|
|
x, y, w, h = self._bounding_box
|
|
return (x + w / 2, y + h / 2)
|
|
|
|
def getOrientation(self) -> float:
|
|
return self._orientation
|
|
|
|
def addPoint(self, point: GesturePoint):
|
|
self._points.append(point)
|
|
self._recalculate()
|
|
|
|
def clear(self):
|
|
self._points.clear()
|
|
self._recalculate()
|
|
|
|
def isLongEnough(self, minLength: float) -> bool:
|
|
return self._length >= minLength
|
|
|
|
def isShortEnough(self, maxLength: float) -> bool:
|
|
return self._length <= maxLength
|
|
|
|
def getDuration(self) -> int:
|
|
if len(self._points) < 2:
|
|
return 0
|
|
return self._points[-1].getTimestamp() - self._points[0].getTimestamp()
|
|
|
|
def toString(self) -> str:
|
|
return f"GestureStroke(points={len(self._points)}, length={self._length:.1f})"
|
|
|
|
def equals(self, other: Any) -> bool:
|
|
if not isinstance(other, GestureStroke):
|
|
return False
|
|
return (self._length == other._length and
|
|
self._bounding_box == other._bounding_box)
|
|
|
|
def hashCode(self) -> int:
|
|
return hash(self._bounding_box) & 0x7FFFFFFF
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# GestureConfiguration
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class GestureConfiguration(Stub):
|
|
"""GestureConfiguration — 手势配置"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._tap_timeout_ms = 300
|
|
self._double_tap_timeout_ms = 400
|
|
self._long_press_timeout_ms = 500
|
|
self._swipe_min_distance = 50.0
|
|
self._swipe_max_distance = 500.0
|
|
self._fling_min_velocity = 200.0
|
|
self._pinch_min_span = 20.0
|
|
self._multi_finger_min_count = 2
|
|
self._gesture_min_length = 10.0
|
|
self._gesture_max_length = 500.0
|
|
self._orientation_slop = 30.0
|
|
|
|
def getTapTimeoutMs(self) -> int:
|
|
return self._tap_timeout_ms
|
|
|
|
def setTapTimeoutMs(self, timeout: int):
|
|
self._tap_timeout_ms = timeout
|
|
|
|
def getDoubleTapTimeoutMs(self) -> int:
|
|
return self._double_tap_timeout_ms
|
|
|
|
def setDoubleTapTimeoutMs(self, timeout: int):
|
|
self._double_tap_timeout_ms = timeout
|
|
|
|
def getLongPressTimeoutMs(self) -> int:
|
|
return self._long_press_timeout_ms
|
|
|
|
def setLongPressTimeoutMs(self, timeout: int):
|
|
self._long_press_timeout_ms = timeout
|
|
|
|
def getSwipeMinDistance(self) -> float:
|
|
return self._swipe_min_distance
|
|
|
|
def setSwipeMinDistance(self, distance: float):
|
|
self._swipe_min_distance = distance
|
|
|
|
def getSwipeMaxDistance(self) -> float:
|
|
return self._swipe_max_distance
|
|
|
|
def setSwipeMaxDistance(self, distance: float):
|
|
self._swipe_max_distance = distance
|
|
|
|
def getFlingMinVelocity(self) -> float:
|
|
return self._fling_min_velocity
|
|
|
|
def setFlingMinVelocity(self, velocity: float):
|
|
self._fling_min_velocity = velocity
|
|
|
|
def getPinchMinSpan(self) -> float:
|
|
return self._pinch_min_span
|
|
|
|
def setPinchMinSpan(self, span: float):
|
|
self._pinch_min_span = span
|
|
|
|
def getMultiFingerMinCount(self) -> int:
|
|
return self._multi_finger_min_count
|
|
|
|
def setMultiFingerMinCount(self, count: int):
|
|
self._multi_finger_min_count = count
|
|
|
|
def getGestureMinLength(self) -> float:
|
|
return self._gesture_min_length
|
|
|
|
def setGestureMinLength(self, length: float):
|
|
self._gesture_min_length = length
|
|
|
|
def getGestureMaxLength(self) -> float:
|
|
return self._gesture_max_length
|
|
|
|
def setGestureMaxLength(self, length: float):
|
|
self._gesture_max_length = length
|
|
|
|
def getOrientationSlop(self) -> float:
|
|
return self._orientation_slop
|
|
|
|
def setOrientationSlop(self, slop: float):
|
|
self._orientation_slop = slop
|
|
|
|
def toString(self) -> str:
|
|
return f"GestureConfiguration(tap={self._tap_timeout_ms}ms)"
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# GestureEvent
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class GestureEvent(Stub):
|
|
"""GestureEvent — 手势事件"""
|
|
|
|
def __init__(self, eventType: int = GESTURE_EVENT_TYPE_TAP,
|
|
position: Tuple[float, float] = (0.0, 0.0),
|
|
velocity: float = 0.0, direction: int = GESTURE_DIRECTION_ANY,
|
|
span: float = 0.0, fingerCount: int = 1,
|
|
stroke: Optional[GestureStroke] = None,
|
|
timestamp: int = 0, state: int = GESTURE_STATE_COMPLETED):
|
|
super().__init__()
|
|
self._event_type = eventType
|
|
self._x, self._y = position
|
|
self._velocity = velocity
|
|
self._direction = direction
|
|
self._span = span
|
|
self._finger_count = fingerCount
|
|
self._stroke = stroke
|
|
self._timestamp = timestamp or int(time.time() * 1000)
|
|
self._state = state
|
|
|
|
def getEventType(self) -> int:
|
|
return self._event_type
|
|
|
|
def getX(self) -> float:
|
|
return self._x
|
|
|
|
def getY(self) -> float:
|
|
return self._y
|
|
|
|
def getVelocity(self) -> float:
|
|
return self._velocity
|
|
|
|
def getDirection(self) -> int:
|
|
return self._direction
|
|
|
|
def getSpan(self) -> float:
|
|
return self._span
|
|
|
|
def getFingerCount(self) -> int:
|
|
return self._finger_count
|
|
|
|
def getStroke(self) -> Optional[GestureStroke]:
|
|
return self._stroke
|
|
|
|
def getTimestamp(self) -> int:
|
|
return self._timestamp
|
|
|
|
def getState(self) -> int:
|
|
return self._state
|
|
|
|
def toString(self) -> str:
|
|
return (f"GestureEvent(type={self._event_type}, "
|
|
f"pos=({self._x:.0f},{self._y:.0f}))")
|
|
|
|
def equals(self, other: Any) -> bool:
|
|
if not isinstance(other, GestureEvent):
|
|
return False
|
|
return (self._event_type == other._event_type and
|
|
self._timestamp == other._timestamp)
|
|
|
|
def hashCode(self) -> int:
|
|
return hash((self._event_type, self._timestamp)) & 0x7FFFFFFF
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# 手势监听器
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class GesturePerformedListener(Stub):
|
|
"""GesturePerformedListener — 手势执行监听器"""
|
|
|
|
def onGesturePerformed(self, gesture: GestureStroke):
|
|
pass
|
|
|
|
def toString(self) -> str:
|
|
return f"GesturePerformedListener@{hex(id(self))}"
|
|
|
|
|
|
class GestureDrawingListener(Stub):
|
|
"""GestureDrawingListener — 手势绘制监听器"""
|
|
|
|
def onGestureStarted(self, stroke: GestureStroke):
|
|
pass
|
|
|
|
def onGestureUpdated(self, stroke: GestureStroke):
|
|
pass
|
|
|
|
def onGestureCompleted(self, stroke: GestureStroke):
|
|
pass
|
|
|
|
def onGestureCancelled(self, stroke: GestureStroke):
|
|
pass
|
|
|
|
def toString(self) -> str:
|
|
return f"GestureDrawingListener@{hex(id(self))}"
|
|
|
|
|
|
class GestureInputListener(Stub):
|
|
"""GestureInputListener — 手势输入监听器"""
|
|
|
|
def onGestureEvent(self, event: GestureEvent):
|
|
pass
|
|
|
|
def toString(self) -> str:
|
|
return f"GestureInputListener@{hex(id(self))}"
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# GestureFeedback / GestureFeedbackProvider
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class GestureFeedback(Stub):
|
|
"""GestureFeedback — 手势反馈"""
|
|
|
|
def __init__(self, type_: int = GESTURE_FEEDBACK_NONE,
|
|
intensity: float = 1.0, durationMs: int = 50):
|
|
super().__init__()
|
|
self._type = type_
|
|
self._intensity = intensity
|
|
self._duration_ms = durationMs
|
|
|
|
def getType(self) -> int:
|
|
return self._type
|
|
|
|
def getIntensity(self) -> float:
|
|
return self._intensity
|
|
|
|
def getDurationMs(self) -> int:
|
|
return self._duration_ms
|
|
|
|
def toString(self) -> str:
|
|
return f"GestureFeedback(type={self._type})"
|
|
|
|
|
|
class GestureFeedbackProvider(Stub):
|
|
"""GestureFeedbackProvider — 手势反馈提供器"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._vibrate_enabled = True
|
|
self._sound_enabled = False
|
|
self._visual_enabled = False
|
|
|
|
def provideFeedback(self, feedback: GestureFeedback):
|
|
pass
|
|
|
|
def isVibrateEnabled(self) -> bool:
|
|
return self._vibrate_enabled
|
|
|
|
def setVibrateEnabled(self, enabled: bool):
|
|
self._vibrate_enabled = enabled
|
|
|
|
def isSoundEnabled(self) -> bool:
|
|
return self._sound_enabled
|
|
|
|
def setSoundEnabled(self, enabled: bool):
|
|
self._sound_enabled = enabled
|
|
|
|
def isVisualEnabled(self) -> bool:
|
|
return self._visual_enabled
|
|
|
|
def setVisualEnabled(self, enabled: bool):
|
|
self._visual_enabled = enabled
|
|
|
|
def toString(self) -> str:
|
|
return f"GestureFeedbackProvider(vibrate={self._vibrate_enabled})"
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# GestureRecognizer
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class GestureRecognizer(Stub):
|
|
"""GestureRecognizer — 手势识别器基类"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._listeners: List[GestureInputListener] = []
|
|
self._configuration = GestureConfiguration()
|
|
self._enabled = True
|
|
self._current_stroke: Optional[GestureStroke] = None
|
|
|
|
def addListener(self, listener: GestureInputListener):
|
|
self._listeners.append(listener)
|
|
|
|
def removeListener(self, listener: GestureInputListener):
|
|
self._listeners = [l for l in self._listeners if l is not listener]
|
|
|
|
def setEnabled(self, enabled: bool):
|
|
self._enabled = enabled
|
|
|
|
def isEnabled(self) -> bool:
|
|
return self._enabled
|
|
|
|
def getConfiguration(self) -> GestureConfiguration:
|
|
return self._configuration
|
|
|
|
def setConfiguration(self, config: GestureConfiguration):
|
|
self._configuration = config
|
|
|
|
def reset(self):
|
|
self._current_stroke = None
|
|
|
|
def processTouchEvent(self, x: float, y: float,
|
|
action: int, pointerId: int = 0,
|
|
pressure: float = 1.0) -> Optional[GestureEvent]:
|
|
return None
|
|
|
|
def toString(self) -> str:
|
|
return f"GestureRecognizer@{hex(id(self))}"
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# TapGesture / DoubleTapGesture / LongPressGesture
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class TapGesture(GestureRecognizer):
|
|
"""TapGesture — 点击手势识别器"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._touch_down_x = 0.0
|
|
self._touch_down_y = 0.0
|
|
self._touch_down_time = 0
|
|
self._touch_down_id = -1
|
|
|
|
def processTouchEvent(self, x: float, y: float,
|
|
action: int, pointerId: int = 0,
|
|
pressure: float = 1.0) -> Optional[GestureEvent]:
|
|
if not self._enabled:
|
|
return None
|
|
now = int(time.time() * 1000)
|
|
if action == 0: # DOWN
|
|
self._touch_down_x = x
|
|
self._touch_down_y = y
|
|
self._touch_down_time = now
|
|
self._touch_down_id = pointerId
|
|
elif action == 1: # UP
|
|
if pointerId != self._touch_down_id:
|
|
return None
|
|
dt = now - self._touch_down_time
|
|
dx = x - self._touch_down_x
|
|
dy = y - self._touch_down_y
|
|
dist = math.sqrt(dx * dx + dy * dy)
|
|
if dt <= self._configuration.getTapTimeoutMs() and \
|
|
dist <= 30.0:
|
|
return GestureEvent(
|
|
eventType=GESTURE_EVENT_TYPE_TAP,
|
|
position=(x, y),
|
|
fingerCount=1,
|
|
state=GESTURE_STATE_COMPLETED,
|
|
timestamp=now,
|
|
)
|
|
return None
|
|
|
|
|
|
class DoubleTapGesture(GestureRecognizer):
|
|
"""DoubleTapGesture — 双击手势识别器"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._first_tap_time = 0
|
|
self._first_tap_x = 0.0
|
|
self._first_tap_y = 0.0
|
|
self._touch_down_count = 0
|
|
self._touch_down_id = -1
|
|
|
|
def processTouchEvent(self, x: float, y: float,
|
|
action: int, pointerId: int = 0,
|
|
pressure: float = 1.0) -> Optional[GestureEvent]:
|
|
if not self._enabled:
|
|
return None
|
|
now = int(time.time() * 1000)
|
|
if action == 1: # UP
|
|
self._touch_down_count += 1
|
|
if self._touch_down_count == 1:
|
|
self._first_tap_time = now
|
|
self._first_tap_x = x
|
|
self._first_tap_y = y
|
|
self._touch_down_id = pointerId
|
|
elif self._touch_down_count == 2:
|
|
dt = now - self._first_tap_time
|
|
dx = x - self._first_tap_x
|
|
dy = y - self._first_tap_y
|
|
dist = math.sqrt(dx * dx + dy * dy)
|
|
if dt <= self._configuration.getDoubleTapTimeoutMs() and \
|
|
dist <= 40.0:
|
|
self._touch_down_count = 0
|
|
return GestureEvent(
|
|
eventType=GESTURE_EVENT_TYPE_DOUBLE_TAP,
|
|
position=(x, y),
|
|
fingerCount=1,
|
|
state=GESTURE_STATE_COMPLETED,
|
|
timestamp=now,
|
|
)
|
|
self._touch_down_count = 1
|
|
self._first_tap_time = now
|
|
self._first_tap_x = x
|
|
self._first_tap_y = y
|
|
return None
|
|
|
|
|
|
class LongPressGesture(GestureRecognizer):
|
|
"""LongPressGesture — 长按手势识别器"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._press_start_x = 0.0
|
|
self._press_start_y = 0.0
|
|
self._press_start_time = 0
|
|
self._is_pressing = False
|
|
self._has_fired = False
|
|
|
|
def processTouchEvent(self, x: float, y: float,
|
|
action: int, pointerId: int = 0,
|
|
pressure: float = 1.0) -> Optional[GestureEvent]:
|
|
if not self._enabled:
|
|
return None
|
|
now = int(time.time() * 1000)
|
|
if action == 0:
|
|
self._press_start_x = x
|
|
self._press_start_y = y
|
|
self._press_start_time = now
|
|
self._is_pressing = True
|
|
self._has_fired = False
|
|
elif action == 1:
|
|
if self._is_pressing and not self._has_fired:
|
|
dt = now - self._press_start_time
|
|
if dt >= self._configuration.getLongPressTimeoutMs():
|
|
self._has_fired = True
|
|
self._is_pressing = False
|
|
return GestureEvent(
|
|
eventType=GESTURE_EVENT_TYPE_LONG_PRESS,
|
|
position=(self._press_start_x, self._press_start_y),
|
|
state=GESTURE_STATE_COMPLETED,
|
|
timestamp=now,
|
|
)
|
|
self._is_pressing = False
|
|
return None
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# SwipeGesture / ScrollGesture / FlingGesture
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class SwipeGesture(GestureRecognizer):
|
|
"""SwipeGesture — 滑动手势识别器"""
|
|
|
|
def __init__(self, direction: int = GESTURE_DIRECTION_ANY):
|
|
super().__init__()
|
|
self._direction = direction
|
|
self._start_x = 0.0
|
|
self._start_y = 0.0
|
|
self._start_time = 0
|
|
self._is_tracking = False
|
|
|
|
def processTouchEvent(self, x: float, y: float,
|
|
action: int, pointerId: int = 0,
|
|
pressure: float = 1.0) -> Optional[GestureEvent]:
|
|
if not self._enabled:
|
|
return None
|
|
now = int(time.time() * 1000)
|
|
if action == 0:
|
|
self._start_x = x
|
|
self._start_y = y
|
|
self._start_time = now
|
|
self._is_tracking = True
|
|
elif action == 1 and self._is_tracking:
|
|
dx = x - self._start_x
|
|
dy = y - self._start_y
|
|
dist = math.sqrt(dx * dx + dy * dy)
|
|
min_dist = self._configuration.getSwipeMinDistance()
|
|
max_dist = self._configuration.getSwipeMaxDistance()
|
|
if min_dist <= dist <= max_dist:
|
|
direction = self._compute_direction(dx, dy)
|
|
if self._direction == GESTURE_DIRECTION_ANY or \
|
|
self._direction == direction:
|
|
self._is_tracking = False
|
|
return GestureEvent(
|
|
eventType=GESTURE_EVENT_TYPE_SWIPE,
|
|
position=(x, y),
|
|
direction=direction,
|
|
stroke=GestureStroke([
|
|
GesturePoint(self._start_x, self._start_y, self._start_time),
|
|
GesturePoint(x, y, now),
|
|
]),
|
|
state=GESTURE_STATE_COMPLETED,
|
|
timestamp=now,
|
|
)
|
|
self._is_tracking = False
|
|
return None
|
|
|
|
def _compute_direction(self, dx: float, dy: float) -> int:
|
|
angle = math.degrees(math.atan2(dy, dx))
|
|
if -45 <= angle <= 45:
|
|
return GESTURE_DIRECTION_RIGHT
|
|
if 45 < angle <= 135:
|
|
return GESTURE_DIRECTION_DOWN
|
|
if -135 <= angle < -45:
|
|
return GESTURE_DIRECTION_UP
|
|
return GESTURE_DIRECTION_LEFT
|
|
|
|
|
|
class ScrollGesture(GestureRecognizer):
|
|
"""ScrollGesture — 滚动手势识别器"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._start_x = 0.0
|
|
self._start_y = 0.0
|
|
self._last_x = 0.0
|
|
self._last_y = 0.0
|
|
self._is_scrolling = False
|
|
|
|
def processTouchEvent(self, x: float, y: float,
|
|
action: int, pointerId: int = 0,
|
|
pressure: float = 1.0) -> Optional[GestureEvent]:
|
|
if not self._enabled:
|
|
return None
|
|
if action == 0:
|
|
self._start_x = x
|
|
self._start_y = y
|
|
self._last_x = x
|
|
self._last_y = y
|
|
self._is_scrolling = False
|
|
elif action == 2: # MOVE
|
|
dx = x - self._last_x
|
|
dy = y - self._last_y
|
|
dist = math.sqrt(dx * dx + dy * dy)
|
|
if dist > 5.0:
|
|
self._is_scrolling = True
|
|
self._last_x = x
|
|
self._last_y = y
|
|
return GestureEvent(
|
|
eventType=GESTURE_EVENT_TYPE_SCROLL,
|
|
position=(x, y),
|
|
stroke=GestureStroke([
|
|
GesturePoint(self._start_x, self._start_y, 0),
|
|
GesturePoint(x, y, int(time.time() * 1000)),
|
|
]),
|
|
state=GESTURE_STATE_IN_PROGRESS,
|
|
)
|
|
elif action == 1 and self._is_scrolling:
|
|
self._is_scrolling = False
|
|
return None
|
|
|
|
|
|
class FlingGesture(GestureRecognizer):
|
|
"""FlingGesture — 甩动手势识别器"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._start_x = 0.0
|
|
self._start_y = 0.0
|
|
self._start_time = 0
|
|
self._is_tracking = False
|
|
|
|
def processTouchEvent(self, x: float, y: float,
|
|
action: int, pointerId: int = 0,
|
|
pressure: float = 1.0) -> Optional[GestureEvent]:
|
|
if not self._enabled:
|
|
return None
|
|
now = int(time.time() * 1000)
|
|
if action == 0:
|
|
self._start_x = x
|
|
self._start_y = y
|
|
self._start_time = now
|
|
self._is_tracking = True
|
|
elif action == 1 and self._is_tracking:
|
|
dt = now - self._start_time
|
|
if dt <= 0:
|
|
return None
|
|
dx = x - self._start_x
|
|
dy = y - self._start_y
|
|
dist = math.sqrt(dx * dx + dy * dy)
|
|
velocity = dist / dt * 1000.0
|
|
if velocity >= self._configuration.getFlingMinVelocity():
|
|
self._is_tracking = False
|
|
return GestureEvent(
|
|
eventType=GESTURE_EVENT_TYPE_FLING,
|
|
position=(x, y),
|
|
velocity=velocity,
|
|
state=GESTURE_STATE_COMPLETED,
|
|
timestamp=now,
|
|
)
|
|
self._is_tracking = False
|
|
return None
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# PinchGesture / DragGesture / MultiFingerGesture
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class PinchGesture(GestureRecognizer):
|
|
"""PinchGesture — 捏合手势识别器"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._initial_span = 0.0
|
|
self._current_span = 0.0
|
|
self._finger1_start: Optional[Tuple[float, float]] = None
|
|
self._finger2_start: Optional[Tuple[float, float]] = None
|
|
self._active = False
|
|
|
|
def processTouchEvent(self, x: float, y: float,
|
|
action: int, pointerId: int = 0,
|
|
pressure: float = 1.0) -> Optional[GestureEvent]:
|
|
return None
|
|
|
|
|
|
class DragGesture(GestureRecognizer):
|
|
"""DragGesture — 拖拽手势识别器"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._start_x = 0.0
|
|
self._start_y = 0.0
|
|
self._is_dragging = False
|
|
|
|
def processTouchEvent(self, x: float, y: float,
|
|
action: int, pointerId: int = 0,
|
|
pressure: float = 1.0) -> Optional[GestureEvent]:
|
|
if not self._enabled:
|
|
return None
|
|
if action == 0:
|
|
self._start_x = x
|
|
self._start_y = y
|
|
self._is_dragging = False
|
|
elif action == 2:
|
|
dx = x - self._start_x
|
|
dy = y - self._start_y
|
|
dist = math.sqrt(dx * dx + dy * dy)
|
|
if dist > 15.0:
|
|
self._is_dragging = True
|
|
return GestureEvent(
|
|
eventType=GESTURE_EVENT_TYPE_DRAG,
|
|
position=(x, y),
|
|
state=GESTURE_STATE_IN_PROGRESS,
|
|
)
|
|
elif action == 1 and self._is_dragging:
|
|
self._is_dragging = False
|
|
return None
|
|
|
|
|
|
class MultiFingerGesture(GestureRecognizer):
|
|
"""MultiFingerGesture — 多指手势识别器"""
|
|
|
|
def __init__(self, minFingers: int = 2):
|
|
super().__init__()
|
|
self._min_fingers = minFingers
|
|
self._active_pointers: Dict[int, Tuple[float, float]] = {}
|
|
|
|
def processTouchEvent(self, x: float, y: float,
|
|
action: int, pointerId: int = 0,
|
|
pressure: float = 1.0) -> Optional[GestureEvent]:
|
|
if not self._enabled:
|
|
return None
|
|
if action == 0:
|
|
self._active_pointers[pointerId] = (x, y)
|
|
elif action == 1:
|
|
self._active_pointers.pop(pointerId, None)
|
|
if len(self._active_pointers) >= self._min_fingers:
|
|
return GestureEvent(
|
|
eventType=GESTURE_EVENT_TYPE_MULTI_FINGER_TAP,
|
|
position=(x, y),
|
|
fingerCount=len(self._active_pointers),
|
|
state=GESTURE_STATE_COMPLETED,
|
|
)
|
|
elif action == 2:
|
|
self._active_pointers[pointerId] = (x, y)
|
|
return None
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# OrientationGesture
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class OrientationGesture(GestureRecognizer):
|
|
"""OrientationGesture — 方向手势识别器"""
|
|
|
|
def __init__(self, orientation: int = ORIENTATION_ANY):
|
|
super().__init__()
|
|
self._orientation = orientation
|
|
self._start_x = 0.0
|
|
self._start_y = 0.0
|
|
self._is_tracking = False
|
|
|
|
def processTouchEvent(self, x: float, y: float,
|
|
action: int, pointerId: int = 0,
|
|
pressure: float = 1.0) -> Optional[GestureEvent]:
|
|
if not self._enabled:
|
|
return None
|
|
if action == 0:
|
|
self._start_x = x
|
|
self._start_y = y
|
|
self._is_tracking = True
|
|
elif action == 1 and self._is_tracking:
|
|
dx = x - self._start_x
|
|
dy = y - self._start_y
|
|
orientation = self._compute_orientation(dx, dy)
|
|
if self._orientation == ORIENTATION_ANY or self._orientation == orientation:
|
|
self._is_tracking = False
|
|
return GestureEvent(
|
|
eventType=GESTURE_EVENT_TYPE_ORIENTATION,
|
|
position=(x, y),
|
|
state=GESTURE_STATE_COMPLETED,
|
|
)
|
|
self._is_tracking = False
|
|
return None
|
|
|
|
def _compute_orientation(self, dx: float, dy: float) -> int:
|
|
adx = abs(dx)
|
|
ady = abs(dy)
|
|
if adx < 10 and ady < 10:
|
|
return ORIENTATION_ANY
|
|
if adx > ady * 2:
|
|
return ORIENTATION_HORIZONTAL if dx > 0 else ORIENTATION_LEFT
|
|
elif ady > adx * 2:
|
|
return ORIENTATION_VERTICAL if dy > 0 else ORIENTATION_UP
|
|
elif dx > 0 and dy > 0:
|
|
return ORIENTATION_DIAGONAL_DOWN
|
|
elif dx > 0 and dy < 0:
|
|
return ORIENTATION_DIAGONAL_UP
|
|
elif dx < 0 and dy > 0:
|
|
return ORIENTATION_DIAGONAL_DOWN
|
|
return ORIENTATION_DIAGONAL_UP
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# GestureOverlayView (扩展)
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class GestureOverlayView(Stub):
|
|
"""GestureOverlayView — 手势叠加视图 (完整版)"""
|
|
|
|
GESTURE_STROKE_TYPE_SINGLE = 0
|
|
GESTURE_STROKE_TYPE_MULTIPLE = 1
|
|
|
|
ORIENTATION_HORIZONTAL = ORIENTATION_HORIZONTAL
|
|
ORIENTATION_VERTICAL = ORIENTATION_VERTICAL
|
|
|
|
def __init__(self, context: Any = None):
|
|
super().__init__()
|
|
self._context = context
|
|
self._current_stroke: Optional[GestureStroke] = None
|
|
self._gestures: List[GestureStroke] = []
|
|
self._stroke_type = GestureOverlayView.GESTURE_STROKE_TYPE_SINGLE
|
|
self._gesture_color = 0xFFFFFF00
|
|
self._gesture_length = 0.0
|
|
self._is_gesture_visible = True
|
|
self._events_enabled = True
|
|
self._performed_listeners: List[GesturePerformedListener] = []
|
|
self._drawing_listeners: List[GestureDrawingListener] = []
|
|
self._gesture_recognizer = GestureRecognizer()
|
|
|
|
def getCurrentStroke(self) -> Optional[GestureStroke]:
|
|
return self._current_stroke
|
|
|
|
def getGestures(self) -> List[GestureStroke]:
|
|
return list(self._gestures)
|
|
|
|
def setGestureColor(self, color: int):
|
|
self._gesture_color = color
|
|
|
|
def getGestureColor(self) -> int:
|
|
return self._gesture_color
|
|
|
|
def setGestureStrokeType(self, type_: int):
|
|
self._stroke_type = type_
|
|
|
|
def getGestureStrokeType(self) -> int:
|
|
return self._stroke_type
|
|
|
|
def setGestureStrokeLengthThreshold(self, length: float):
|
|
self._gesture_length = length
|
|
|
|
def getGestureStrokeLengthThreshold(self) -> float:
|
|
return self._gesture_length
|
|
|
|
def setGestureVisible(self, visible: bool):
|
|
self._is_gesture_visible = visible
|
|
|
|
def isGestureVisible(self) -> bool:
|
|
return self._is_gesture_visible
|
|
|
|
def setEventsEnabled(self, enabled: bool):
|
|
self._events_enabled = enabled
|
|
|
|
def isEventsEnabled(self) -> bool:
|
|
return self._events_enabled
|
|
|
|
def addOnGesturePerformedListener(self, listener: GesturePerformedListener):
|
|
self._performed_listeners.append(listener)
|
|
|
|
def removeOnGesturePerformedListener(self, listener: GesturePerformedListener):
|
|
self._performed_listeners = [l for l in self._performed_listeners if l is not listener]
|
|
|
|
def addOnGestureDrawingListener(self, listener: GestureDrawingListener):
|
|
self._drawing_listeners.append(listener)
|
|
|
|
def removeOnGestureDrawingListener(self, listener: GestureDrawingListener):
|
|
self._drawing_listeners = [l for l in self._drawing_listeners if l is not listener]
|
|
|
|
def clear(self):
|
|
self._gestures.clear()
|
|
self._current_stroke = None
|
|
|
|
def cancelGesture(self):
|
|
if self._current_stroke:
|
|
for l in self._drawing_listeners:
|
|
l.onGestureCancelled(self._current_stroke)
|
|
self._current_stroke = None
|
|
|
|
def toString(self) -> str:
|
|
return f"GestureOverlayView(strokes={len(self._gestures)})"
|
|
|
|
def equals(self, other: Any) -> bool:
|
|
return self is other
|
|
|
|
def hashCode(self) -> int:
|
|
return id(self) & 0x7FFFFFFF
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# GestureBuilderActivity
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class GestureBuilderActivity(Stub):
|
|
"""GestureBuilderActivity — 手势构建器活动"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._overlay = GestureOverlayView()
|
|
self._library: Optional["GestureLibraries"] = None
|
|
self._current_name: str = ""
|
|
|
|
def getGestureOverlayView(self) -> GestureOverlayView:
|
|
return self._overlay
|
|
|
|
def setGestureLibrary(self, library: "GestureLibraries"):
|
|
self._library = library
|
|
|
|
def getGestureLibrary(self) -> Optional["GestureLibraries"]:
|
|
return self._library
|
|
|
|
def setCurrentGestureName(self, name: str):
|
|
self._current_name = name
|
|
|
|
def getCurrentGestureName(self) -> str:
|
|
return self._current_name
|
|
|
|
def saveGesture(self):
|
|
if self._library and self._current_name and self._overlay.getCurrentStroke():
|
|
stroke = self._overlay.getCurrentStroke()
|
|
self._library.store(self._current_name, stroke)
|
|
self._library.save()
|
|
|
|
def toString(self) -> str:
|
|
return f"GestureBuilderActivity(name={self._current_name})"
|
|
|
|
def equals(self, other: Any) -> bool:
|
|
return self is other
|
|
|
|
def hashCode(self) -> int:
|
|
return id(self) & 0x7FFFFFFF
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# GestureLibraries
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class GestureLibraries(Stub):
|
|
"""GestureLibraries — 手势库"""
|
|
|
|
def __init__(self, storeType: int = GESTURE_LIBRARY_FILE,
|
|
path: str = "", loadOnInit: bool = True):
|
|
super().__init__()
|
|
self._store_type = storeType
|
|
self._path = path
|
|
self._gestures: Dict[str, List[GestureStroke]] = {}
|
|
self._loaded = False if loadOnInit else False
|
|
|
|
def load(self) -> bool:
|
|
self._loaded = True
|
|
return True
|
|
|
|
def save(self) -> bool:
|
|
return True
|
|
|
|
def isLoaded(self) -> bool:
|
|
return self._loaded
|
|
|
|
def getGestures(self, name: str) -> List[GestureStroke]:
|
|
return self._gestures.get(name, [])
|
|
|
|
def getGestureNames(self) -> List[str]:
|
|
return list(self._gestures.keys())
|
|
|
|
def store(self, name: str, stroke: GestureStroke):
|
|
if name not in self._gestures:
|
|
self._gestures[name] = []
|
|
self._gestures[name].append(stroke)
|
|
|
|
def remove(self, name: str):
|
|
self._gestures.pop(name, None)
|
|
|
|
def removeEntry(self, name: str, stroke: GestureStroke):
|
|
strokes = self._gestures.get(name)
|
|
if strokes:
|
|
try:
|
|
strokes.remove(stroke)
|
|
except ValueError:
|
|
pass
|
|
if not strokes:
|
|
self._gestures.pop(name, None)
|
|
|
|
def getCount(self) -> int:
|
|
return sum(len(s) for s in self._gestures.values())
|
|
|
|
@staticmethod
|
|
def fromFile(path: str) -> "GestureLibraries":
|
|
return GestureLibraries(storeType=GESTURE_LIBRARY_FILE, path=path)
|
|
|
|
@staticmethod
|
|
def fromResource(context: Any, resourceId: int) -> "GestureLibraries":
|
|
return GestureLibraries(storeType=GESTURE_LIBRARY_RESOURCE, path=str(resourceId))
|
|
|
|
@staticmethod
|
|
def fromRaw(context: Any, resourceId: int) -> "GestureLibraries":
|
|
return GestureLibraries(storeType=GESTURE_LIBRARY_RAW, path=str(resourceId))
|
|
|
|
@staticmethod
|
|
def fromPrivateFile(context: Any, name: str) -> "GestureLibraries":
|
|
return GestureLibraries(storeType=GESTURE_LIBRARY_PRIVATE, path=name)
|
|
|
|
def toString(self) -> str:
|
|
return f"GestureLibraries(type={self._store_type}, gestures={self.getCount()})"
|
|
|
|
def equals(self, other: Any) -> bool:
|
|
return self is other
|
|
|
|
def hashCode(self) -> int:
|
|
return id(self) & 0x7FFFFFFF |