触摸输入子系统(CategoryTouch)
SDL 在支持触摸功能的平台上提供了完整的触摸输入处理能力,可管理多个触摸设备,并追踪每个设备上的多点触控(多手指触摸)操作。
核心交互方式
触摸操作主要通过事件系统传递:
SDL_EVENT_FINGER_DOWN:手指按下事件;SDL_EVENT_FINGER_MOTION:手指移动事件;SDL_EVENT_FINGER_UP:手指抬起事件。
同时也提供了查询触摸硬件信息的辅助函数。
虚拟鼠标事件
SDL 触摸系统默认会将触摸操作转换为虚拟鼠标事件,这一特性可让桌面应用无需大幅修改代码就能在触屏手机上运行。
- 若应用需要区分原生鼠标输入和触摸模拟的鼠标输入,需忽略
which字段为SDL_TOUCH_MOUSEID的鼠标事件; - 反之,若只需基础交互,可直接使用鼠标事件处理逻辑,无需额外适配触摸。
函数
- SDL_GetTouchDeviceName:根据触摸设备ID获取设备名称(如 "Touchscreen")
- SDL_GetTouchDevices:枚举系统中所有已连接的触摸设备(返回触摸设备ID列表及数量)
- SDL_GetTouchDeviceType:获取触摸设备的类型(如触摸屏、触摸板、手写板)
- SDL_GetTouchFingers:获取指定触摸设备上所有活动的手指信息(返回手指ID列表及数量)
数据类型
- SDL_FingerID:手指ID类型(标识触摸设备上的单个手指,多点触控时区分不同手指)
- SDL_TouchID:触摸设备ID类型(标识系统中的单个触摸设备)
结构体
- SDL_Finger:手指状态结构体(包含手指ID、归一化的触摸位置(x/y:0.0~1.0)、压力值(0.0~1.0))
枚举
- SDL_TouchDeviceType:触摸设备类型枚举(SDL_TOUCH_DEVICE_TYPE_DIRECT 触摸屏(直接触摸)、SDL_TOUCH_DEVICE_TYPE_INDIRECT_ABS 绝对坐标触摸板、SDL_TOUCH_DEVICE_TYPE_INDIRECT_REL 相对坐标触摸板)
宏
- SDL_MOUSE_TOUCHID:鼠标事件关联的触摸设备ID标识(用于反向关联鼠标事件到触摸设备)
- SDL_TOUCH_MOUSEID:触摸模拟鼠标事件的设备ID(用于区分原生鼠标和触摸模拟鼠标)
FreeBASIC 示例代码
' 引入 SDL 相关声明(需确保 FreeBASIC 已链接 SDL3 库)
#Include "SDL.bi"
' 辅助函数:打印触摸设备信息
Sub PrintTouchDeviceInfo(ByVal touchID As SDL_TouchID)
Dim As ZString Ptr devName = SDL_GetTouchDeviceName(touchID)
Dim As SDL_TouchDeviceType devType = SDL_GetTouchDeviceType(touchID)
Dim As String typeStr = ""
Select Case devType
Case SDL_TOUCH_DEVICE_TYPE_DIRECT: typeStr = "触摸屏(直接触摸)"
Case SDL_TOUCH_DEVICE_TYPE_INDIRECT_ABS: typeStr = "触摸板(绝对坐标)"
Case SDL_TOUCH_DEVICE_TYPE_INDIRECT_REL: typeStr = "触摸板(相对坐标)"
Case Else: typeStr = "未知设备类型"
End Select
SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "==== 触摸设备信息 ====")
SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "设备ID:%llu | 名称:%s | 类型:%s", touchID, devName, typeStr)
' 获取设备上的活动手指数量
Dim As Integer fingerCount = SDL_GetTouchFingers(touchID, NULL, 0)
SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "当前活动手指数量:%d", fingerCount)
If (devName <> NULL) Then SDL_free(devName)
End Sub
' 主程序
Dim As SDL_Window Ptr window = NULL
Dim As SDL_Renderer Ptr renderer = NULL
Dim As SDL_TouchID Ptr touchIDs = NULL
Dim As Integer touchCount = 0
Dim As SDL_Event evt
Dim As Boolean quit = False
' 存储触摸点状态(最多支持5点触控)
Type TouchPoint
active As Boolean
x As Double
y As Double
pressure As Double
End Type
Dim As TouchPoint touchPoints(0 To 4)
' 1. 初始化 SDL
If (SDL_Init(SDL_INIT_VIDEO) < 0) Then
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL 初始化失败:%s", SDL_GetError())
End 1
End If
' 2. 创建窗口(全屏模式更适合触摸演示)
window = SDL_CreateWindow("SDL 触摸输入示例", _
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, _
800, 600, SDL_WINDOW_SHOWN Or SDL_WINDOW_RESIZABLE)
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED Or SDL_RENDERER_PRESENTVSYNC)
If (window = NULL Or renderer = NULL) Then
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "创建窗口/渲染器失败:%s", SDL_GetError())
SDL_Quit()
End 1
End If
' 3. 枚举触摸设备
touchCount = SDL_GetTouchDevices(0, NULL)
If (touchCount > 0) Then
touchIDs = Callocate(touchCount * SizeOf(SDL_TouchID))
touchCount = SDL_GetTouchDevices(touchCount, touchIDs)
SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "检测到 %d 个触摸设备", touchCount)
' 打印第一个触摸设备信息
PrintTouchDeviceInfo(touchIDs[0])
Else
SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "系统未检测到触摸设备,仍可测试虚拟鼠标事件")
End If
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "=== SDL 触摸输入示例 ===")
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "触摸屏幕:显示触摸点 | 鼠标操作:区分原生/模拟 | ESC:退出")
' 4. 主循环
While (Not quit)
' 清屏(黑色背景)
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255)
SDL_RenderClear(renderer)
' 绘制触摸点
For i As Integer = 0 To 4
If (touchPoints(i).active) Then
' 获取窗口尺寸,将归一化坐标转换为像素坐标
Dim As Integer winW, winH
SDL_GetWindowSize(window, @winW, @winH)
Dim As Integer px = touchPoints(i).x * winW
Dim As Integer py = touchPoints(i).y * winH
Dim As Integer size = 50 + touchPoints(i).pressure * 50 ' 压力越大,触摸点越大
' 绘制彩色触摸点(不同手指不同颜色)
Select Case i
Case 0: SDL_SetRenderDrawColor(renderer, 255, 0, 0, 200) ' 红色
Case 1: SDL_SetRenderDrawColor(renderer, 0, 255, 0, 200) ' 绿色
Case 2: SDL_SetRenderDrawColor(renderer, 0, 0, 255, 200) ' 蓝色
Case 3: SDL_SetRenderDrawColor(renderer, 255, 255, 0, 200) ' 黄色
Case 4: SDL_SetRenderDrawColor(renderer, 255, 0, 255, 200) ' 紫色
End Select
' 绘制圆形触摸点(简化为矩形)
Dim As SDL_Rect touchRect = (px - size\2, py - size\2, size, size)
SDL_RenderFillRect(renderer, @touchRect)
End If
Next
' 刷新画面
SDL_RenderPresent(renderer)
' 处理事件队列
While (SDL_PollEvent(@evt))
Select Case evt.type
Case SDL_QUITEVENT
quit = True
' 键盘事件
Case SDL_EVENT_KEY_DOWN
If (evt.key.scancode = SDL_SCANCODE_ESCAPE) Then
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "按下 ESC,退出程序")
quit = True
End If
' 触摸事件处理
Case SDL_EVENT_FINGER_DOWN
SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, _
"手指按下 | 设备ID:%llu | 手指ID:%llu | 位置:(%.2f, %.2f) | 压力:%.2f", _
evt.tfinger.touchId, evt.tfinger.fingerId, _
evt.tfinger.x, evt.tfinger.y, evt.tfinger.pressure)
' 记录触摸点状态(简化:按手指ID索引存储)
Dim As Integer idx = evt.tfinger.fingerId Mod 5
touchPoints(idx).active = True
touchPoints(idx).x = evt.tfinger.x
touchPoints(idx).y = evt.tfinger.y
touchPoints(idx).pressure = evt.tfinger.pressure
Case SDL_EVENT_FINGER_MOTION
SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, _
"手指移动 | 设备ID:%llu | 手指ID:%llu | 位置:(%.2f, %.2f) | 相对偏移:(%.4f, %.4f)", _
evt.tfinger.touchId, evt.tfinger.fingerId, _
evt.tfinger.x, evt.tfinger.y, evt.tfinger.dx, evt.tfinger.dy)
' 更新触摸点位置
Dim As Integer idx = evt.tfinger.fingerId Mod 5
touchPoints(idx).x = evt.tfinger.x
touchPoints(idx).y = evt.tfinger.y
touchPoints(idx).pressure = evt.tfinger.pressure
Case SDL_EVENT_FINGER_UP
SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, _
"手指抬起 | 设备ID:%llu | 手指ID:%llu | 位置:(%.2f, %.2f)", _
evt.tfinger.touchId, evt.tfinger.fingerId, evt.tfinger.x, evt.tfinger.y)
' 清除触摸点状态
Dim As Integer idx = evt.tfinger.fingerId Mod 5
touchPoints(idx).active = False
' 区分原生鼠标和触摸模拟鼠标
Case SDL_EVENT_MOUSE_BUTTON_DOWN
If (evt.button.which = SDL_TOUCH_MOUSEID) Then
SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "鼠标点击(触摸模拟):位置(%d, %d)", evt.button.x, evt.button.y)
Else
SDL_LogInfo(SDL_LOG_CATEGORY_INPUT, "鼠标点击(原生):位置(%d, %d)", evt.button.x, evt.button.y)
End If
End Select
Wend
SDL_Delay(16) ' 约60FPS
Wend
' 5. 清理资源
If (touchIDs <> NULL) Then
Deallocate(touchIDs)
End If
SDL_DestroyRenderer(renderer)
SDL_DestroyWindow(window)
SDL_Quit()
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "程序正常退出")
End 0
核心知识点补充
-
触摸坐标归一化:
- SDL 触摸事件中的
x/y坐标为归一化值(0.0 ~ 1.0),对应触摸设备的左上角到右下角; - 需转换为窗口像素坐标时,公式:
像素坐标 = 归一化坐标 × 窗口尺寸; - 压力值(pressure)同样为 0.0 ~ 1.0,仅部分触摸设备支持(如电容屏)。
- SDL 触摸事件中的
-
多点触控处理:
- 每个手指有唯一的
SDL_FingerID,可通过该ID区分不同手指的操作; - 示例中简化为最多支持5点触控,实际可根据需求扩展;
- 常见多点触控手势(缩放、旋转)需通过多个手指的位置变化计算实现。
- 每个手指有唯一的
-
跨平台注意事项:
- 桌面平台(Windows/macOS)的触摸板会被识别为
SDL_TOUCH_DEVICE_TYPE_INDIRECT_ABS; - 移动平台(Android/iOS)的触摸屏会被识别为
SDL_TOUCH_DEVICE_TYPE_DIRECT; - 虚拟鼠标事件在移动平台默认启用,桌面平台可通过 SDL 提示关闭。
- 桌面平台(Windows/macOS)的触摸板会被识别为
总结
- 核心特性:
- SDL 触摸系统提供标准化的多点触控事件,无需关注底层平台差异;
- 虚拟鼠标事件机制大幅降低跨平台适配成本;
- 通过
SDL_TOUCH_MOUSEID可灵活区分原生鼠标和触摸模拟输入;
- 使用场景:
- 简单适配:直接使用鼠标事件处理触摸输入;
- 精细控制:监听
SDL_EVENT_FINGER_*事件,处理多点触控、压力感应等高级功能;
- 关键接口:
- 设备枚举:
SDL_GetTouchDevices/SDL_GetTouchDeviceName; - 事件处理:
SDL_EVENT_FINGER_DOWN/MOTION/UP; - 输入区分:
SDL_TOUCH_MOUSEID宏。
- 设备枚举:
评论一下?