From 37fccec6af20b7afaac303a039e83974384a797c Mon Sep 17 00:00:00 2001 From: tongtongstudio Date: Tue, 14 Jul 2026 14:25:58 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20flutter=E5=A2=9E=E5=8A=A0=E9=95=BF?= =?UTF-8?q?=E6=8C=89=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webrtc_controller_flutter/lib/main.dart | 2 + .../lib/utils/control_commands.dart | 8 +++ .../lib/widgets/remote_touch_view.dart | 54 +++++++++++++++++++ 3 files changed, 64 insertions(+) diff --git a/webrtc_controller_flutter/lib/main.dart b/webrtc_controller_flutter/lib/main.dart index 9b62707..82d5aed 100644 --- a/webrtc_controller_flutter/lib/main.dart +++ b/webrtc_controller_flutter/lib/main.dart @@ -202,6 +202,8 @@ class _ControllerHomeState extends State { ControlCommands.swipe(x1, y1, x2, y2, d)), onKey: (k, a) => _controller ?.sendControlCommand(ControlCommands.key(k, a)), + onLongPress: (x, y) => _controller + ?.sendControlCommand(ControlCommands.longPress(x, y)), ), ], ), diff --git a/webrtc_controller_flutter/lib/utils/control_commands.dart b/webrtc_controller_flutter/lib/utils/control_commands.dart index c133cdd..442b0f0 100644 --- a/webrtc_controller_flutter/lib/utils/control_commands.dart +++ b/webrtc_controller_flutter/lib/utils/control_commands.dart @@ -37,4 +37,12 @@ class ControlCommands { 'keyCode': keyCode, 'keyAction': action, }); + + /// 长按指令。坐标 x/y 为相对于屏幕的百分比(0.0 ~ 1.0)。 + /// 对应 Android 端 RemoteTouchView.createLongPressCommand。 + static String longPress(double x, double y) => jsonEncode({ + 'action': 'LONG_PRESS', + 'x': x, + 'y': y, + }); } diff --git a/webrtc_controller_flutter/lib/widgets/remote_touch_view.dart b/webrtc_controller_flutter/lib/widgets/remote_touch_view.dart index 7a15cc4..8faf396 100644 --- a/webrtc_controller_flutter/lib/widgets/remote_touch_view.dart +++ b/webrtc_controller_flutter/lib/widgets/remote_touch_view.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:math'; import 'package:flutter/material.dart'; @@ -10,6 +11,7 @@ import 'package:flutter/services.dart'; /// /// - 短时间内位移很小的抬起 -> 单击(TOUCH) /// - 否则 -> 滑动(SWIPE) +/// - 按住不动超过阈值 -> 长按(LONG_PRESS) /// 同时捕获物理按键事件(KEY),用于外接键盘 / 遥控器场景。 /// /// 该控件完全基于 Flutter 框架实现,无需任何原生代码,可运行于 @@ -24,12 +26,14 @@ class RemoteTouchView extends StatefulWidget { int durationMs, ) onSwipe; final void Function(int keyCode, int action) onKey; + final void Function(double x, double y) onLongPress; const RemoteTouchView({ super.key, required this.onTouch, required this.onSwipe, required this.onKey, + required this.onLongPress, }); @override @@ -38,7 +42,16 @@ class RemoteTouchView extends StatefulWidget { class _RemoteTouchViewState extends State { Offset? _startPosition; + Offset? _currentPosition; DateTime? _startTime; + bool _isLongPressed = false; + Timer? _longPressTimer; + + /// 长按触发阈值,对应 Android GestureDetector 默认的 LONG_PRESS_TIMEOUT(400ms)。 + static const _longPressTimeout = Duration(milliseconds: 400); + + /// 手指移动超过该像素阈值则视为滚动,取消长按(对应 Android touch slop)。 + static const _touchSlop = 10.0; double _toRelativeX(Offset local, Size size) => (local.dx / size.width).clamp(0.0, 1.0); @@ -46,6 +59,17 @@ class _RemoteTouchViewState extends State { double _toRelativeY(Offset local, Size size) => (local.dy / size.height).clamp(0.0, 1.0); + void _cancelLongPress() { + _longPressTimer?.cancel(); + _longPressTimer = null; + } + + @override + void dispose() { + _cancelLongPress(); + super.dispose(); + } + @override Widget build(BuildContext context) { return Focus( @@ -64,10 +88,39 @@ class _RemoteTouchViewState extends State { child: Listener( behavior: HitTestBehavior.opaque, onPointerDown: (event) { + _cancelLongPress(); + _isLongPressed = false; _startPosition = event.localPosition; + _currentPosition = event.localPosition; _startTime = DateTime.now(); + _longPressTimer = Timer(_longPressTimeout, () { + final size = context.size; + if (_currentPosition == null || size == null) return; + _isLongPressed = true; + final x = _toRelativeX(_currentPosition!, size); + final y = _toRelativeY(_currentPosition!, size); + widget.onLongPress(x, y); + }); + }, + onPointerMove: (event) { + _currentPosition = event.localPosition; + if (_longPressTimer != null && _startPosition != null) { + final dx = (event.localPosition.dx - _startPosition!.dx).abs(); + final dy = (event.localPosition.dy - _startPosition!.dy).abs(); + if (dx > _touchSlop || dy > _touchSlop) { + _cancelLongPress(); + } + } }, onPointerUp: (event) { + _cancelLongPress(); + // 长按已触发则不再派发单击 / 滑动事件(对应 Android 端 isLongPressed 逻辑)。 + if (_isLongPressed) { + _startPosition = null; + _currentPosition = null; + _startTime = null; + return; + } final size = context.size; if (_startPosition == null || size == null || _startTime == null) { return; @@ -87,6 +140,7 @@ class _RemoteTouchViewState extends State { widget.onSwipe(startX, startY, endX, endY, max(duration, 1)); } _startPosition = null; + _currentPosition = null; _startTime = null; }, child: const ColoredBox(