import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; /// 远端触摸控制层(对应 Android 端 RemoteTouchView)。 /// /// 捕获指针(触摸/鼠标)事件,将其转换为相对于控件区域的百分比坐标 /// (0.0 ~ 1.0),以便在不同分辨率的设备间换算。 /// /// - 短时间内位移很小的抬起 -> 单击(TOUCH) /// - 否则 -> 滑动(SWIPE) /// 同时捕获物理按键事件(KEY),用于外接键盘 / 遥控器场景。 /// /// 该控件完全基于 Flutter 框架实现,无需任何原生代码,可运行于 /// Android、iOS 等平台。 class RemoteTouchView extends StatefulWidget { final void Function(double x, double y) onTouch; final void Function( double x1, double y1, double x2, double y2, int durationMs, ) onSwipe; final void Function(int keyCode, int action) onKey; const RemoteTouchView({ super.key, required this.onTouch, required this.onSwipe, required this.onKey, }); @override State createState() => _RemoteTouchViewState(); } class _RemoteTouchViewState extends State { Offset? _startPosition; DateTime? _startTime; double _toRelativeX(Offset local, Size size) => (local.dx / size.width).clamp(0.0, 1.0); double _toRelativeY(Offset local, Size size) => (local.dy / size.height).clamp(0.0, 1.0); @override Widget build(BuildContext context) { return Focus( autofocus: true, // 捕获物理按键(外接键盘 / 遥控器等)。 onKeyEvent: (node, event) { if (event is KeyDownEvent) { widget.onKey(event.logicalKey.keyId, 0); return KeyEventResult.handled; } else if (event is KeyUpEvent) { widget.onKey(event.logicalKey.keyId, 1); return KeyEventResult.handled; } return KeyEventResult.ignored; }, child: Listener( behavior: HitTestBehavior.opaque, onPointerDown: (event) { _startPosition = event.localPosition; _startTime = DateTime.now(); }, onPointerUp: (event) { final size = context.size; if (_startPosition == null || size == null || _startTime == null) { return; } final startX = _toRelativeX(_startPosition!, size); final startY = _toRelativeY(_startPosition!, size); final endX = _toRelativeX(event.localPosition, size); final endY = _toRelativeY(event.localPosition, size); final duration = DateTime.now().difference(_startTime!).inMilliseconds; final dx = (endX - startX).abs(); final dy = (endY - startY).abs(); if (duration < 200 && dx < 0.02 && dy < 0.02) { widget.onTouch(startX, startY); } else { widget.onSwipe(startX, startY, endX, endY, max(duration, 1)); } _startPosition = null; _startTime = null; }, child: const ColoredBox( color: Colors.transparent, child: SizedBox.expand(), ), ), ); } }