尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Unity Slider拖拽事件监听优化:超越onValueChanged的精准交互方案

Unity Slider拖拽事件监听优化:超越onValueChanged的精准交互方案 1. 项目概述为什么说“只用onValueChanged”是个坑在Unity UI开发里Slider组件几乎是每个项目都会用到的控件从音量调节、血条显示到进度选择无处不在。而onValueChanged事件作为Unity官方文档里最显眼的那个回调自然成了我们监听滑块值变化的“第一选择”。但如果你和我一样在项目里踩过几次坑就会明白这个标题背后的深意——单纯依赖onValueChanged在很多交互场景下是远远不够的甚至会带来糟糕的用户体验和难以排查的Bug。想象一个典型的场景你正在开发一个音频编辑器用户需要拖拽一个滑块来精细调整某段音轨的音量。你信心满满地给Slider挂上了onValueChanged监听每变化一次就实时应用音量。看起来一切正常对吧但实际测试时问题来了当用户快速、连续地拖拽滑块时你的回调函数会被疯狂调用每秒可能触发几十甚至上百次。如果每次回调都去执行一个稍微耗时的操作比如更新音频引擎参数、向服务器发送请求、或者刷新一个复杂的UI那么结果就是界面卡顿、操作延迟甚至在某些情况下导致逻辑错乱。更关键的是你无法区分用户是“正在拖拽中”还是“已经松手结束了拖拽”。对于音频调节也许你希望只在拖拽结束时才最终提交并保存音量设置而不是在拖拽过程中就不断地写入配置文件或触发网络同步。这就是onValueChanged的局限性它只告诉你“值变了”但不告诉你“这个变化是怎么来的”以及“这个交互状态是什么”。它无法区分是代码赋值、鼠标点击跳跃还是连续的鼠标拖拽。而一个专业的、体验良好的滑块交互往往需要更精细的控制我们需要知道拖拽何时开始OnPointerDown、拖拽过程中OnDrag以及拖拽何时结束OnPointerUp或OnEndDrag。本指南要解决的正是这个痛点。我将带你绕过官方API的“舒适区”深入UI事件系统底层实现一套对Slider拖拽开始、进行、结束的精准监听方案并附上可直接复用的C#源码。这不仅仅是解决一个技术问题更是对UI交互逻辑严谨性的一次升级。2. 核心需求解析我们需要什么样的滑块监听在动手写代码之前我们必须先厘清一个完善的Slider监听方案究竟需要满足哪些核心需求。这决定了我们解决方案的设计方向。2.1onValueChanged的典型问题场景首先我们得承认onValueChanged在简单场景下是没问题的。比如一个静态的、只展示最终结果的设置项用户点一下滑块某个位置值跳过去触发一次回调完全够用。但一旦涉及连续交互和状态感知它就力不从心了。我总结了几类常见问题场景性能开销问题这是最直接的。在Update循环里或者快速拖拽时onValueChanged会高频触发。如果你的回调函数里做了任何非轻量级的操作如实例化对象、复杂计算、IO操作都会立即成为性能瓶颈。我曾在一个项目里因为滑块每动一下就去计算并刷新一个包含上百个元素的图表直接导致在移动设备上帧率暴跌。交互状态丢失问题这是逻辑层面的缺陷。很多业务逻辑依赖于交互的“阶段”。例如游戏中的灵敏度调节你可能希望玩家在拖拽过程中只是预览调整后的效果比如相机晃动幅度只有当他松开鼠标/手指时才正式保存这个设置到玩家偏好中。视频播放器的进度条在拖拽过程中你希望视频暂停并快速跳转预览帧当拖拽结束时视频才从新的时间点开始播放。如果只用onValueChanged你一拖拽视频就可能开始断断续续地播放和跳转体验极差。实时数据过滤比如一个价格区间滑块拖拽过程中实时显示符合当前区间的商品数量是可以的但如果你在每次值变化时都去请求一次数据库或执行一次全列表过滤那将是灾难。值变化来源混淆问题onValueChanged无法区分值的变化是来自用户交互还是来自你的脚本代码例如slider.value 0.5f;。在某些情况下你希望代码赋值时触发某些逻辑如初始化、重置而不触发另一些逻辑如播放音效、记录用户操作日志。混在一起会导致意想不到的行为。2.2 理想监听方案的功能清单基于以上问题一个理想的Slider扩展监听方案应该具备以下能力区分交互阶段能够明确触发“拖拽开始”、“拖拽进行中”、“拖拽结束”三个独立事件。事件触发精准“开始”事件应在用户鼠标按下或手指触摸到滑块的瞬间触发而非第一次值变化之后。“结束”事件应在用户释放输入设备的瞬间触发确保捕获到最终的交互意图。与onValueChanged兼容新的方案不应取代onValueChanged而是作为它的补充。我们依然需要知道值的变化但我们现在能知道这个变化发生在哪个交互阶段。易于集成和使用最好能以组件形式存在像使用原生UI组件一样在Inspector面板上拖拽赋值或者用代码便捷地添加监听。平台无关无论是PC端的鼠标操作还是移动端的触摸操作都应能正确响应。性能友好新的事件系统本身不应引入明显的性能开销。明确了目标我们就可以开始设计实现了。我们的思路是利用Unity UI系统底层的事件接口为Slider组件“赋能”让它具备更精细的事件感知能力。3. 实现方案设计扩展Slider而非替换实现这个功能有几种常见的思路我们来分析一下优劣。方案一暴力轮询法在Update里记录上一帧的slider.value如果发现变化了再结合Input.GetMouseButton之类的状态来判断是否是拖拽。这是最不推荐的方法它把逻辑耦合在每帧更新里不精确且浪费性能。方案二封装事件触发法创建一个自定义的Slider组件继承自原生的UnityEngine.UI.Slider。然后在这个子类中实现IPointerDownHandler,IDragHandler,IPointerUpHandler等接口。当这些接口方法被调用时触发我们自定义的OnSliderDragBegin,OnSliderDrag,OnSliderDragEnd事件。同时我们重写onValueChanged的调用方式使其能携带一个“是否在拖拽中”的状态标志。方案三事件监听器组合法不继承Slider而是创建一个独立的SliderDragEventListener组件。这个组件挂载到Slider所在的GameObject上它同样实现那些事件接口并对外暴露事件。这种方法更符合组合优于继承的原则耦合度更低可以灵活地添加到任何Slider甚至其他可拖拽UI元素上。我强烈推荐并采用方案三。理由如下非侵入性不需要修改或替换项目中原有的Slider组件。你可以在任何已有的Slider上直接添加这个监听器立即获得新功能对旧代码零影响。灵活性高这个监听器组件可以轻松地复用给Scrollbar或者其他自定义的可拖拽UI。职责单一Slider只负责管理和响应值的变化而交互状态监听由专门的组件负责符合单一职责原则。易于维护两个组件的逻辑是分离的出问题时更容易定位。接下来我们就按照方案三来一步步实现这个SliderDragEventListener。4. 核心代码实现与逐行解析下面就是完整的SliderDragEventListenerC#源码。我会将代码分成几个部分并逐块进行详细解析让你不仅能复制粘贴更能彻底理解每一行代码的意图。using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; /// summary /// 增强的Slider拖拽事件监听器。 /// 为Slider组件提供精准的拖拽开始、进行、结束事件弥补onValueChanged的不足。 /// 使用方法将此脚本挂载到带有Slider组件的GameObject上即可。 /// /summary [RequireComponent(typeof(Slider))] public class SliderDragEventListener : MonoBehaviour, IPointerDownHandler, IDragHandler, IPointerUpHandler, IEndDragHandler { // 序列化字段方便在Inspector中配置 [SerializeField] private Slider _targetSlider; // 自定义事件用于在Inspector中配置或通过代码监听 public UnityEvent onDragBegin; public UnityEvent onDrag; public UnityEvent onDragEnd; public UnityEventfloat onValueChangedDuringDrag; // 带参数的事件传递当前值 // 内部状态标志 private bool _isDragging false; private float _lastReportedValue; private void Awake() { // 自动获取Slider组件如果Inspector中没有手动指定 if (_targetSlider null) { _targetSlider GetComponentSlider(); } if (_targetSlider null) { Debug.LogError($SliderDragEventListener 需要挂载在带有 Slider 组件的GameObject上。当前物体: {gameObject.name}, this); enabled false; // 禁用组件避免后续错误 return; } // 初始化最后报告的值 _lastReportedValue _targetSlider.value; } /// summary /// 当指针鼠标/触摸在Slider上按下时调用。 /// 注意即使按下后没有立即移动值未变也视为拖拽开始。 /// /summary public void OnPointerDown(PointerEventData eventData) { // 通常直接按下滑块轨道或滑块柄都会开始拖拽。 // 这里我们直接标记拖拽开始并触发事件。 if (!_isDragging) { _isDragging true; onDragBegin?.Invoke(); // 在拖拽开始时也记录一次值变化确保初始状态被捕获 ReportValueChange(_targetSlider.value); } } /// summary /// 当在Slider上拖拽时每帧调用如果指针在移动。 /// 这是拖拽过程中的主要事件。 /// /summary public void OnDrag(PointerEventData eventData) { // OnDrag事件在拖拽过程中会持续触发即使值可能因为步长(wholeNumbers)限制而没有变化。 // 我们仍然触发onDrag事件因为它代表“拖拽动作”在进行中。 onDrag?.Invoke(); // 检查值是否实际发生了变化避免重复触发值变化事件。 if (!Mathf.Approximately(_targetSlider.value, _lastReportedValue)) { ReportValueChange(_targetSlider.value); } } /// summary /// 当指针从Slider上抬起时调用。 /// 这是拖拽结束最直接的信号。 /// /summary public void OnPointerUp(PointerEventData eventData) { HandleDragEnd(); } /// summary /// 当拖拽操作结束时调用在某些UI框架中可能与OnPointerUp略有不同但这里我们统一处理。 /// 作为OnPointerUp的备份确保结束事件被捕获。 /// /summary public void OnEndDrag(PointerEventData eventData) { HandleDragEnd(); } /// summary /// 统一处理拖拽结束逻辑 /// /summary private void HandleDragEnd() { if (_isDragging) { _isDragging false; // 在结束前确保报告最终的值因为最后一次OnDrag触发后值可能已经稳定 if (!Mathf.Approximately(_targetSlider.value, _lastReportedValue)) { ReportValueChange(_targetSlider.value); } onDragEnd?.Invoke(); } } /// summary /// 报告值变化并更新内部记录。 /// 这个方法只会在拖拽期间且值确实发生变化时被调用。 /// /summary private void ReportValueChange(float newValue) { _lastReportedValue newValue; onValueChangedDuringDrag?.Invoke(newValue); } /// summary /// 公共属性供外部查询当前是否处于拖拽状态。 /// /summary public bool IsDragging _isDragging; /// summary /// 在组件禁用或物体销毁时重置状态防止状态残留。 /// /summary private void OnDisable() { // 如果组件被禁用时仍在拖拽强制结束拖拽状态。 // 这可以防止比如在拖拽时弹出菜单禁用组件导致状态卡住。 if (_isDragging) { _isDragging false; onDragEnd?.Invoke(); // 谨慎触发确保监听者能处理意外结束。 } } }4.1 关键实现细节解析接口的选择 (IPointerDownHandler,IDragHandler, etc.)这些接口属于UnityEngine.EventSystems命名空间是Unity UI事件系统的基石。它们与Graphic Raycaster协同工作能够精准响应来自EventSystem的输入事件。同时实现OnPointerUp和OnEndDrag是为了确保兼容性。在某些复杂的UI层级或交互中两者可能稍有区别同时实现可以更可靠地捕获拖拽结束事件。[RequireComponent(typeof(Slider))]这个属性非常有用。当你把这个脚本挂到一个GameObject上时如果该物体没有Slider组件Unity会自动帮你添加一个。这避免了因忘记添加依赖组件而导致的运行时错误。UnityEvent与UnityEventfloatUnityEvent是Unity提供的序列化事件类最大的好处是支持在Inspector面板中可视化地添加监听函数。你可以像配置Button的onClick一样通过拖拽来绑定响应方法无需写一行代码。UnityEventfloat是它的泛型版本允许传递一个float参数即Slider的值。这让我们能在Inspector中直接配置一个接收float参数的方法。_isDragging状态管理这个布尔标志是整个组件的核心。它精确记录了“用户是否正在拖拽”这个状态。所有的事件触发逻辑都围绕它展开。在OnPointerDown中设置为true在OnPointerUp或OnEndDrag中设置为false。逻辑清晰。Mathf.Approximately用于浮点数比较这是Unity中比较两个浮点数是否“近似相等”的标准做法。由于浮点数精度问题直接使用比较可能会得到错误结果。Mathf.Approximately使用一个很小的容差epsilon进行比较更加安全可靠。?.Invoke()空条件运算符这是C# 6.0引入的语法糖。它会在调用Invoke()之前检查事件是否为null。如果没有任何监听者订阅这个事件它就是null直接调用会引发NullReferenceException。使用?.可以安全地调用如果没有监听者就什么都不做。OnDisable中的状态清理这是一个非常重要的细节也是很多开发者容易忽略的“坑”。想象一下用户正在拖拽滑块突然一个弹窗出现并禁用了滑块所在的整个面板从而也禁用了这个监听器组件。如果没有OnDisable中的清理逻辑_isDragging就会永远卡在true状态。当面板再次启用时逻辑就错乱了。因此在组件禁用时强制结束拖拽状态是一个健壮性设计。5. 实战应用在Inspector与代码中的使用有了组件我们来看看怎么用它。两种方式可视化配置和代码动态绑定。5.1 Inspector可视化配置适合快速原型、简单逻辑将上面的脚本保存为SliderDragEventListener.cs并放入你的项目。在场景中找到一个已有的Slider比如UI - Slider创建的那个。选中这个Slider所在的GameObject在Inspector面板点击“Add Component”搜索并添加SliderDragEventListener。添加成功后你会看到脚本组件上有四个折叠的事件列表On Drag Begin,On Drag,On Drag End,On Value Changed During Drag。点击每个事件下方的“”号可以将场景中任何拥有公有方法的GameObject拖入并选择对应的方法。举个例子你有一个AudioManager游戏对象上面有一个AudioManager脚本其中有公有方法SetVolumePreview(float vol)和SaveVolumeSetting(float vol)。你可以将AudioManager对象拖到On Value Changed During Drag的运行时列表里然后从函数选择下拉框中找到AudioManager.SetVolumePreview。这样拖拽过程中音量就会实时预览。再将同一个对象拖到On Drag End的列表里选择AudioManager.SaveVolumeSetting。这样只有当用户松开鼠标时音量设置才会被永久保存。这种方式无需编写任何连接代码非常适合UI逻辑与业务逻辑的快速挂钩。5.2 代码动态绑定适合复杂逻辑、程序化控制有时我们需要在运行时动态创建Slider或者需要更灵活的事件处理。这时就可以用代码来订阅事件。using UnityEngine; using UnityEngine.UI; public class VolumeController : MonoBehaviour { public Slider volumeSlider; private SliderDragEventListener _dragListener; void Start() { // 确保Slider上有我们的监听器组件 _dragListener volumeSlider.GetComponentSliderDragEventListener(); if (_dragListener null) { _dragListener volumeSlider.gameObject.AddComponentSliderDragEventListener(); } // 使用代码订阅事件 _dragListener.onDragBegin.AddListener(OnVolumeDragBegin); _dragListener.onDrag.AddListener(OnVolumeDragging); _dragListener.onDragEnd.AddListener(OnVolumeDragEnd); _dragListener.onValueChangedDuringDrag.AddListener(OnVolumeValueChanged); // 传统的onValueChanged仍然可以同时使用用于非拖拽引起的值变化如代码设置、点击跳跃 volumeSlider.onValueChanged.AddListener(OnVolumeChangedAnyTime); } void OnVolumeDragBegin() { Debug.Log(开始拖拽音量滑块); // 例如开始录制音量调节操作用于撤销/重做功能 } void OnVolumeDragging() { // 纯拖拽动作即使值没变也可能触发 // Debug.Log(正在拖拽...); } void OnVolumeValueChanged(float newVolume) { // 只有在拖拽过程中值实际变化了才会触发 Debug.Log($拖拽中音量变为: {newVolume}); AudioManager.Instance.PreviewVolume(newVolume); // 仅预览 } void OnVolumeDragEnd() { Debug.Log(结束拖拽音量滑块); float finalVolume volumeSlider.value; AudioManager.Instance.ApplyAndSaveVolume(finalVolume); // 应用并保存 AnalyticsManager.LogEvent(VolumeAdjusted, finalVolume); // 打点分析 } void OnVolumeChangedAnyTime(float volume) { // 这个回调会被任何值变化触发包括代码设置 volumeSlider.value xxx Debug.Log($音量值发生变化任何原因: {volume}); } void OnDestroy() { // 良好的习惯在物体销毁时移除监听防止内存泄漏 if (_dragListener ! null) { _dragListener.onDragBegin.RemoveListener(OnVolumeDragBegin); _dragListener.onDrag.RemoveListener(OnVolumeDragging); _dragListener.onDragEnd.RemoveListener(OnVolumeDragEnd); _dragListener.onValueChangedDuringDrag.RemoveListener(OnVolumeValueChanged); } if (volumeSlider ! null) { volumeSlider.onValueChanged.RemoveListener(OnVolumeChangedAnyTime); } } }通过代码绑定你可以获得最大的灵活性可以在事件处理函数中执行任何复杂的逻辑。6. 进阶技巧与避坑指南在实际项目中使用这套方案你可能会遇到一些特殊情况。下面是我总结的几个进阶技巧和常见坑点。6.1 处理“点击跳跃”与“拖拽”的区分我们的监听器在OnPointerDown时立即标记拖拽开始。这意味着即使用户只是点击滑块轨道让滑块跳跃到点击位置也会触发onDragBegin和紧接着的onDragEnd因为点击可以分解为按下和抬起。这符合大多数场景的认知点击跳跃也被视为一次快速的“拖拽”交互。但如果你需要严格区分“点击”和“拖拽”可以加入一个阈值判断。public class SliderDragEventListener : MonoBehaviour, IPointerDownHandler, IDragHandler, IPointerUpHandler { // ... 其他字段和事件 ... [SerializeField] private float _dragThreshold 5.0f; // 像素阈值 private Vector2 _pointerDownPosition; private bool _potentialClick false; public void OnPointerDown(PointerEventData eventData) { _pointerDownPosition eventData.position; _potentialClick true; // 暂时不触发onDragBegin } public void OnDrag(PointerEventData eventData) { // 检查移动距离是否超过阈值 if (_potentialClick Vector2.Distance(_pointerDownPosition, eventData.position) _dragThreshold) { _potentialClick false; _isDragging true; onDragBegin?.Invoke(); } if (_isDragging) { onDrag?.Invoke(); // ... 值变化检查 ... } } public void OnPointerUp(PointerEventData eventData) { if (_potentialClick) { // 移动距离很小视为点击跳跃 // 可以触发一个专门的 onClickJump 事件或者什么也不做 Debug.Log(点击跳跃); // 注意点击跳跃也会导致Slider值变化这个变化会由Slider自身的onValueChanged捕获。 } HandleDragEnd(); } // ... HandleDragEnd 需要处理 _isDragging 为 false 但 _potentialClick 为 true 的情况 ... }6.2 与UI ScrollRect等可拖拽区域的冲突如果你的Slider嵌套在一个ScrollRect滚动视图内部可能会遇到冲突当你尝试垂直拖拽滑块时不小心有水平移动可能会意外触发父级ScrollRect的滚动。这是因为Unity的事件系统存在“事件冒泡”机制。解决方案修改ScrollRect的拖拽阈值增大ScrollRect组件的Drag Threshold使其需要更大的移动距离才被判定为滚动给滑块操作留出空间。使用EventTrigger组件进行精细控制不推荐较复杂。更简单的实践在UI布局上尽量避免将需要精细水平拖拽的Slider放在一个垂直滚动的区域里或者反之。如果无法避免告知UI/UX设计师这种交互可能存在冲突从设计层面规避。6.3 移动端触摸输入的特殊处理在移动设备上触摸输入与鼠标输入在事件系统层面是被Unity统一处理的所以我们的脚本通常可以直接工作。但需要注意多指触摸我们的脚本没有处理多指触摸。如果两个手指同时操作一个滑块事件会变得混乱。对于需要多指操作的高级应用需要考虑更复杂的事件管理。但对于绝大多数滑块场景单指操作是标准。触摸反馈在移动端为滑块添加触觉反馈Haptic Feedback可以极大提升体验。你可以在OnPointerDown或OnDragEnd中调用Handheld.Vibrate()注意电量消耗和用户设置或使用第三方插件。6.4 性能优化避免每帧操作我们的OnDrag方法在拖拽期间每帧都会被调用只要指针在移动。虽然UnityEvent.Invoke()本身开销不大但如果你在事件监听函数中执行非常耗时的操作依然会成为性能问题。优化建议在onValueChangedDuringDrag的事件处理函数中避免在每帧都进行昂贵的计算或IO操作。可以考虑使用节流Throttling技术比如记录上次执行时间如果间隔太短如小于0.1秒就跳过本次操作或者将操作延迟到下一帧执行Coroutine或Invoke。对于实时预览类操作如调整参数实时更新画面确保其本身是轻量级的。如果必须进行重计算考虑使用一个标志位在OnDragEnd时才执行最终的全量计算。7. 完整示例构建一个专业的视频进度条让我们用一个综合例子把上面的所有知识点串起来实现一个体验良好的视频播放器进度条。需求显示当前播放进度。用户拖拽时视频暂停并快速跳转预览对应帧的画面缩略图或低清帧。用户松开时视频从新的时间点开始播放。在非交互情况下正常播放进度条自动平滑更新。实现步骤创建UI创建一个Slider将其Direction设为Left to Right。可以美化滑块柄和轨道。添加监听器为Slider所在的GameObject添加SliderDragEventListener组件。编写控制器脚本using UnityEngine; using UnityEngine.UI; using UnityEngine.Video; // 假设使用VideoPlayer public class VideoProgressController : MonoBehaviour { public VideoPlayer videoPlayer; public Slider progressSlider; public RawImage previewImage; // 用于显示拖拽时的预览帧 public float previewUpdateInterval 0.2f; // 预览更新间隔用于节流 private SliderDragEventListener _dragListener; private bool _wasPlayingBeforeDrag false; private float _lastPreviewTime -1f; void Start() { // 获取或添加监听器 _dragListener progressSlider.GetComponentSliderDragEventListener(); if (_dragListener null) _dragListener progressSlider.gameObject.AddComponentSliderDragEventListener(); // 订阅事件 _dragListener.onDragBegin.AddListener(OnProgressDragBegin); _dragListener.onValueChangedDuringDrag.AddListener(OnProgressDragValueChanged); _dragListener.onDragEnd.AddListener(OnProgressDragEnd); // 传统事件用于视频播放时自动更新进度条 progressSlider.onValueChanged.AddListener(OnProgressChangedByPlayback); // 视频播放事件更新进度条 videoPlayer.started OnVideoStarted; videoPlayer.time OnVideoTimeUpdate; // 注意这是一个简化实际应用需要更精确的更新方式 } void OnProgressDragBegin() { // 记录拖拽前的播放状态 _wasPlayingBeforeDrag videoPlayer.isPlaying; // 暂停视频播放 videoPlayer.Pause(); Debug.Log(开始拖拽进度条视频已暂停); } void OnProgressDragValueChanged(float sliderValue) { // 节流控制避免每帧都提取预览帧 if (Time.unscaledTime - _lastPreviewTime previewUpdateInterval) { return; } _lastPreviewTime Time.unscaledTime; // 将slider的value (0-1) 映射到视频时间 (0 - videoPlayer.length) double targetTime sliderValue * videoPlayer.length; // 快速跳转到目标时间不等待缓冲适合预览 videoPlayer.time targetTime; // 请求当前帧VideoPlayer可能需要几帧来更新 videoPlayer.Play(); videoPlayer.Pause(); // 立即暂停只显示这一帧 // 将当前视频帧显示到预览图 if (previewImage ! null videoPlayer.texture ! null) { previewImage.texture videoPlayer.texture; previewImage.gameObject.SetActive(true); // 显示预览图 } } void OnProgressDragEnd() { // 隐藏预览图 if (previewImage ! null) previewImage.gameObject.SetActive(false); // 获取Slider的最终值并跳转到精确时间 double finalTime progressSlider.value * videoPlayer.length; videoPlayer.time finalTime; // 恢复之前的播放状态 if (_wasPlayingBeforeDrag) { videoPlayer.Play(); } Debug.Log(结束拖拽跳转到时间: finalTime); } void OnProgressChangedByPlayback(float value) { // 这个回调会被视频播放更新进度条时触发。 // 我们需要判断如果当前是用户正在拖拽就应该忽略这个自动更新否则会打架。 if (_dragListener ! null _dragListener.IsDragging) { return; // 用户正在拖拽忽略程序设置的更新 } // 否则可以在这里执行一些非交互时的UI更新比如更新一个时间码文本 // UpdateTimeDisplay(value); } void OnVideoStarted(VideoPlayer source) { // 视频开始播放时可以重置一些状态 } void OnVideoTimeUpdate(VideoPlayer source, double newTime) { // 在视频播放时更新进度条的值 // 注意必须在主线程中设置UI if (!_dragListener.IsDragging) // 关键只有非拖拽状态才更新 { float progress (float)(newTime / source.length); progressSlider.SetValueWithoutNotify(progress); // 使用SetValueWithoutNotify避免触发onValueChanged事件循环 } } void OnDestroy() { // 清理事件订阅 if (_dragListener ! null) { _dragListener.onDragBegin.RemoveListener(OnProgressDragBegin); _dragListener.onValueChangedDuringDrag.RemoveListener(OnProgressDragValueChanged); _dragListener.onDragEnd.RemoveListener(OnProgressDragEnd); } if (videoPlayer ! null) { videoPlayer.started - OnVideoStarted; } } }这个例子展示了如何将我们的拖拽监听器与具体的业务逻辑视频控制紧密结合并处理了自动更新与手动拖拽的冲突是一个生产级别可用的方案。
返回列表