lib/ui/aliplayer_play_control_widget.dart (210 lines of code) (raw):

// Copyright © 2025 Alibaba Cloud. All rights reserved. // // Author: keria // Date: 2025/2/11 // Brief: 播放器控制控件 import 'dart:async'; import 'package:flutter/material.dart'; /// 播放器控制控件 /// /// Player Control Widget class AliPlayerPlayControlWidget extends StatefulWidget { /// Whether to auto-hide the control final bool autoHide; /// 回调函数:通知外部控件是否可见 final ValueChanged<bool>? onVisibilityChanged; /// Callbacks for various gestures final VoidCallback? onSingleTap; final VoidCallback? onDoubleTap; final VoidCallback? onLongPressStart; final VoidCallback? onLongPressEnd; final ValueChanged<double>? onHorizontalDragUpdate; final ValueChanged<double>? onHorizontalDragEnd; final ValueChanged<double>? onLeftVerticalDragUpdate; final ValueChanged<double>? onLeftVerticalDragEnd; final ValueChanged<double>? onRightVerticalDragUpdate; final ValueChanged<double>? onRightVerticalDragEnd; const AliPlayerPlayControlWidget({ super.key, this.autoHide = true, this.onVisibilityChanged, this.onSingleTap, this.onDoubleTap, this.onLongPressStart, this.onLongPressEnd, this.onHorizontalDragUpdate, this.onHorizontalDragEnd, this.onLeftVerticalDragUpdate, this.onLeftVerticalDragEnd, this.onRightVerticalDragUpdate, this.onRightVerticalDragEnd, }); @override State<AliPlayerPlayControlWidget> createState() => _AliPlayerPlayControlWidgetState(); } class _AliPlayerPlayControlWidgetState extends State<AliPlayerPlayControlWidget> { /// 自动隐藏时间间隔 /// /// Auto-hide duration as a static constant static const Duration _autoHideDuration = Duration(seconds: 3); /// 控件是否可见 /// /// Whether the control is visible bool _isVisible = false; /// 定时器 /// /// Timer for auto-hide Timer? _hideTimer; /// 手势检测的起点 Offset? _startPosition; /// 上一次触发回调的值(用于去抖动) double? _lastHorizontalValue; double? _lastLeftVerticalValue; double? _lastRightVerticalValue; /// 缓存父容器尺寸以优化性能 /// /// Cache the parent container size to optimize performance. Size? _cachedContainerSize; /// 当前拖动是否为左侧 /// /// Whether the current drag is on the left side. bool? _isLeftSide; /// 滑动灵敏度阈值(像素) /// /// Minimum distance (in pixels) required to trigger a drag update. static const double _dragSensitivityThreshold = 10.0; /// 百分比变化的灵敏度阈值 /// /// Minimum percentage change required to trigger a callback. static const double _percentChangeThreshold = 0.02; /// 获取父容器尺寸 /// /// Get the size of the parent container. Size _getParentContainerSize(BuildContext context) { if (_cachedContainerSize == null) { final RenderBox renderBox = context.findRenderObject() as RenderBox; _cachedContainerSize = renderBox.size; } return _cachedContainerSize!; } /// 切换控件可见性 /// /// Toggle the visibility of the control view void _toggleVisibility() { _isVisible = !_isVisible; // 通知外部可见性变化 _notifyVisibilityChanged(); if (_isVisible && widget.autoHide) { _resetHideTimer(); } else { _cancelHideTimer(); } } /// 隐藏控件 /// /// Hide the control view void _hide() { _isVisible = false; // 通知外部可见性变化 _notifyVisibilityChanged(); _cancelHideTimer(); } /// 重置定时器 /// /// Reset the hide timer void _resetHideTimer() { // Cancel any previous timer _cancelHideTimer(); if (widget.autoHide) { _hideTimer = Timer(_autoHideDuration, _hide); } } /// 取消定时器 /// /// Cancel the hide timer void _cancelHideTimer() { _hideTimer?.cancel(); } /// 通知外部可见性变化 /// /// Notify the parent about the visibility change void _notifyVisibilityChanged() { // 调用回调函数 widget.onVisibilityChanged?.call(_isVisible); } @override void dispose() { // Ensure to cancel any active timer on dispose _cancelHideTimer(); super.dispose(); } @override Widget build(BuildContext context) { return GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { _toggleVisibility(); widget.onSingleTap?.call(); }, onDoubleTap: widget.onDoubleTap != null ? () { _hide(); widget.onDoubleTap?.call(); } : null, onLongPressStart: widget.onLongPressStart != null ? (_) { _hide(); widget.onLongPressStart?.call(); } : null, onLongPressEnd: widget.onLongPressEnd != null ? (_) { _hide(); widget.onLongPressEnd?.call(); } : null, onHorizontalDragStart: widget.onHorizontalDragUpdate != null || widget.onHorizontalDragEnd != null ? _onHorizontalDragStart : null, onHorizontalDragUpdate: widget.onHorizontalDragUpdate != null ? _onHorizontalDragUpdate : null, onHorizontalDragEnd: widget.onHorizontalDragEnd != null ? _onHorizontalDragEnd : null, onVerticalDragStart: (widget.onLeftVerticalDragUpdate != null || widget.onLeftVerticalDragEnd != null || widget.onRightVerticalDragUpdate != null || widget.onRightVerticalDragEnd != null) ? _onVerticalDragStart : null, onVerticalDragUpdate: (widget.onLeftVerticalDragUpdate != null || widget.onRightVerticalDragUpdate != null) ? _onVerticalDragUpdate : null, onVerticalDragEnd: (widget.onLeftVerticalDragEnd != null || widget.onRightVerticalDragEnd != null) ? _onVerticalDragEnd : null, child: Container(), ); } /// 水平拖动开始 void _onHorizontalDragStart(DragStartDetails details) { _startPosition = details.globalPosition; _lastHorizontalValue = null; // 重置上次值 } /// 水平拖动更新 void _onHorizontalDragUpdate(DragUpdateDetails details) { if (_startPosition == null) return; final Size containerSize = _getParentContainerSize(context); // 计算水平移动的距离 double deltaX = details.globalPosition.dx - _startPosition!.dx; // 如果滑动距离未达到灵敏度阈值,则不触发回调 if (deltaX.abs() < _dragSensitivityThreshold) return; // 将 deltaX 转换为相对于容器宽度的百分比(范围:-1 到 1) double seekDelta = deltaX / containerSize.width; double roundedSeekDelta = double.parse(seekDelta.toStringAsFixed(2)); // 如果百分比变化未达到灵敏度阈值,则不触发回调 if (_lastHorizontalValue == null || (roundedSeekDelta - _lastHorizontalValue!).abs() >= _percentChangeThreshold) { widget.onHorizontalDragUpdate?.call(roundedSeekDelta); _lastHorizontalValue = roundedSeekDelta; } } /// 水平拖动结束 void _onHorizontalDragEnd(DragEndDetails details) { _startPosition = null; // 如果有最后的值,传递给回调函数 if (_lastHorizontalValue != null) { widget.onHorizontalDragEnd?.call(_lastHorizontalValue!); } // 重置上次值 _lastHorizontalValue = null; } /// 垂直拖动开始 void _onVerticalDragStart(DragStartDetails details) { _startPosition = details.globalPosition; // 根据拖动起点判断左右侧 double screenWidth = MediaQuery.of(context).size.width; _isLeftSide = details.globalPosition.dx < screenWidth / 2; // 重置左侧和右侧上次值 _lastLeftVerticalValue = null; _lastRightVerticalValue = null; } /// 垂直拖动更新 void _onVerticalDragUpdate(DragUpdateDetails details) { if (_startPosition == null || _isLeftSide == null) return; // 判断是左侧还是右侧 final Size containerSize = _getParentContainerSize(context); // 计算垂直移动的距离,并取反以调整方向 double deltaY = -(details.globalPosition.dy - _startPosition!.dy); // 如果滑动距离未达到灵敏度阈值,则不触发回调 if (deltaY.abs() < _dragSensitivityThreshold) return; // 将 deltaY 转换为相对于容器高度的百分比(范围:-1 到 1) double deltaPercent = deltaY / containerSize.height; double roundedDeltaPercent = double.parse(deltaPercent.toStringAsFixed(2)); // 如果百分比变化未达到灵敏度阈值,则不触发回调 if (_isLeftSide!) { // 左侧垂直拖动 if (_lastLeftVerticalValue == null || (roundedDeltaPercent - _lastLeftVerticalValue!).abs() >= _percentChangeThreshold) { widget.onLeftVerticalDragUpdate?.call(roundedDeltaPercent); _lastLeftVerticalValue = roundedDeltaPercent; } } else { // 右侧垂直拖动 if (_lastRightVerticalValue == null || (roundedDeltaPercent - _lastRightVerticalValue!).abs() >= _percentChangeThreshold) { widget.onRightVerticalDragUpdate?.call(roundedDeltaPercent); _lastRightVerticalValue = roundedDeltaPercent; } } // 更新起点 _startPosition = details.globalPosition; } /// 垂直拖动结束 void _onVerticalDragEnd(DragEndDetails details) { if (_isLeftSide == null) return; if (_isLeftSide!) { // 如果有最后的值,传递给回调函数 if (_lastLeftVerticalValue != null) { widget.onLeftVerticalDragEnd?.call(_lastLeftVerticalValue!); } // 重置左侧上次值 _lastLeftVerticalValue = null; } else { // 如果有最后的值,传递给回调函数 if (_lastRightVerticalValue != null) { widget.onRightVerticalDragEnd?.call(_lastRightVerticalValue!); } // 重置右侧上次值 _lastRightVerticalValue = null; } _startPosition = null; _isLeftSide = null; } }