using LuaInterface; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; using System.Collections.Generic; public class PageSliderManager : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler { [Header("页面设置")] [SerializeField] private Transform[] pageTitle; [SerializeField] private Animator[] pageAnimator; [SerializeField] private Transform[] pages; // 所有页面Transform数组 [SerializeField] private int currentPageIndex = 0; // 当前页面索引 [Header("滑动设置")] [SerializeField] private float snapSpeed = 15f; // 吸附速度 [SerializeField] private float snapThreshold = 0.3f; // 吸附阈值(页面宽度比例) [Header("UI设置")] [SerializeField] private Graphic dragArea; // 用于拖动的UI区域 [SerializeField] private bool requireExactClick = true; // 是否要求点击在Image上 // 私有变量 private float pageSpacing = 0f; // 页面间距 private bool isDragging = false; private bool dragValid = false; // 标记拖拽是否有效 // 拖动输入记录 private Vector3 dragStartPosition; // 拖动开始时的位置 private Vector2 pageDownPosition; // 页面按下时的位置 private float totalDragDistance = 0f; private Animator animDown; private Animator animLast; private Animator animNext; private EnumDistance disAnimMove = EnumDistance.None; private float animTimeDown; private float animTimeLast; private float animTimeNext; private int recordPage = -1; // 添加速度检测相关变量 private Vector3 lastDragPosition; private float dragSpeed; private float dragDirection; // 在私有变量部分添加 private JumpMode currentJumpMode = JumpMode.Animated; private int jumpTargetIndex = -1; private float jumpProgress = 0f; private float jumpDuration = 0.3f; // 跳转动画持续时间 private float jumpStartX = 0f; // 添加:记录跳转起始位置 private float jumpTargetX = 0f; // 添加:记录跳转目标位置 // 添加新的跳转模式枚举 private enum JumpMode { Direct, // 直接跳转 Animated // 动画跳转 } // 滑动状态 private enum SlideState { Idle, // 空闲 Dragging, // 拖动中 Snapping, // 吸附中 Momentum // 动量滑动 } private SlideState currentState = SlideState.Idle; // 添加动画完成标记 private bool isAnimationsComplete = true; private void Start() { CalculatePageSpacing(); // 如果没有设置拖拽区域,使用当前GameObject的Graphic组件 if (dragArea == null) { dragArea = GetComponent(); } // 如果需要,可以在这里添加拖拽区域的设置 if (dragArea != null) { dragArea.raycastTarget = true; } } public void SetTitleSpacing(LuaTable table) { Transform[] transTemp = new Transform[table.Length + 1]; pageAnimator = new Animator[table.Length + 1]; for (int i = 0; i < transTemp.Length; i++) { transTemp[i] = table.RawGetIndex(i); pageAnimator[i] = transTemp[i].GetChild(0).GetComponent(); } pageTitle = transTemp; } private void CalculatePageSpacing() { if (pages == null || pages.Length < 2) { pageSpacing = Screen.width; return; } Vector3 page1Pos = pages[0].localPosition; Vector3 page2Pos = pages[1].localPosition; pageSpacing = Mathf.Abs(page2Pos.x - page1Pos.x); if (pageSpacing < 1f) { pageSpacing = Screen.width; RearrangePages(); } } private void RearrangePages() { for (int i = 0; i < pages.Length; i++) { if (pages[i] == null) continue; float xPos = i * pageSpacing; pages[i].localPosition = new Vector3(xPos, 0, 0); } } private void Update() { // 处理直接跳转 if (currentJumpMode == JumpMode.Direct && jumpTargetIndex >= 0) { HandleDirectJump(); return; } if (currentState == SlideState.Snapping) { SmoothToTarget(); } if (currentState != SlideState.Dragging && currentState != SlideState.Snapping) { UpdateCurrentPageIndex(); } // 检查动画是否完成 CheckAnimationsComplete(); } // 处理直接跳转的方法 private void HandleDirectJump() { if (jumpProgress >= 1f) { // 跳转完成 currentJumpMode = JumpMode.Animated; jumpTargetIndex = -1; currentState = SlideState.Idle; // 确保最终位置准确 Vector3 tempNewPos = pages[0].localPosition; tempNewPos.x = jumpTargetX; pages[0].localPosition = tempNewPos; // 同步其他页面 for (int i = 1; i < pages.Length; i++) { if (pages[i] == null) continue; Vector3 pos = tempNewPos + new Vector3(i * pageSpacing, 0, 0); pages[i].localPosition = pos; } // 确保动画状态正确 EnsureFinalAnimationStates(); return; } // 更新进度 jumpProgress += Time.deltaTime / jumpDuration; jumpProgress = Mathf.Clamp01(jumpProgress); // 使用缓动函数进行平滑过渡 float t = EaseOutCubic(jumpProgress); float currentX = Mathf.Lerp(jumpStartX, jumpTargetX, t); // 应用新位置 Vector3 newPos = pages[0].localPosition; newPos.x = currentX; pages[0].localPosition = newPos; // 同步其他页面 for (int i = 1; i < pages.Length; i++) { if (pages[i] == null) continue; Vector3 pos = newPos + new Vector3(i * pageSpacing, 0, 0); pages[i].localPosition = pos; } } // 缓动函数:三次方缓出 private float EaseOutCubic(float t) { t -= 1; return t * t * t + 1; } // IBeginDragHandler接口实现 - 开始拖动 public void OnBeginDrag(PointerEventData eventData) { // 检查是否点击在有效区域内 if (requireExactClick) { // 检查点击是否在当前对象的RectTransform内 if (IsPointerOverGameObject(eventData)) { OnDragStart(eventData.position); } } else { // 如果不需要精确点击,直接开始拖拽 OnDragStart(eventData.position); } } // IDragHandler接口实现 - 拖动中 public void OnDrag(PointerEventData eventData) { if (!dragValid) return; HandleDragging(eventData.position); } // IEndDragHandler接口实现 - 结束拖动 public void OnEndDrag(PointerEventData eventData) { if (!dragValid) return; OnDragEnd(); dragValid = false; // 重置拖拽有效性 } // 检查指针是否在当前GameObject上 private bool IsPointerOverGameObject(PointerEventData eventData) { // 创建一个结果列表来接收射线检测结果 var results = new List(); // 执行射线检测 EventSystem.current.RaycastAll(eventData, results); // 检查是否点击到了当前对象 foreach (var result in results) { if (result.gameObject == gameObject) { return true; } } return false; } // 可选的:手动检查点击位置的方法 public void CheckPointerClick(Vector2 screenPosition) { // 检查点击是否在当前UI区域内 if (IsPointInRectTransform(screenPosition)) { OnDragStart(screenPosition); dragValid = true; } else { dragValid = false; } } // 检查点是否在RectTransform内 private bool IsPointInRectTransform(Vector2 screenPosition) { if (dragArea == null) return false; // 将屏幕坐标转换为本地坐标 RectTransform rectTransform = dragArea.rectTransform; Vector2 localPoint; RectTransformUtility.ScreenPointToLocalPointInRectangle( rectTransform, screenPosition, null, out localPoint); // 检查点是否在矩形内 return rectTransform.rect.Contains(localPoint); } // 可选的:添加点击事件处理,用于直接跳转页面 public void OnPageClick(int pageIndex) { if (currentState == SlideState.Idle) { GoToPage(pageIndex, false); } } private void OnDragStart(Vector2 dragPosition) { dragValid = true; isDragging = true; currentState = SlideState.Dragging; dragStartPosition = dragPosition; lastDragPosition = dragPosition; // 初始化最后拖动位置 // 记录第一个页面的初始位置 if (pages[0] != null) { pageDownPosition = pages[0].localPosition; } totalDragDistance = 0f; dragSpeed = 0f; dragDirection = 0f; if (currentPageIndex == 0) { animLast = null; animNext = pageAnimator[currentPageIndex + 1]; } else if (currentPageIndex == pages.Length - 1) { animLast = pageAnimator[currentPageIndex - 1]; animNext = null; } else { animLast = pageAnimator[currentPageIndex - 1]; animNext = pageAnimator[currentPageIndex + 1]; } animDown = pageAnimator[currentPageIndex]; recordPage = currentPageIndex; // 重置动画完成标记 isAnimationsComplete = false; } private void HandleDragging(Vector2 currentDragPosition) { if (currentState != SlideState.Dragging) return; // 计算拖动位移 Vector3 dragDelta = currentDragPosition - (Vector2)dragStartPosition; dragDelta.y = 0; // 计算滑动速度(每帧移动的像素距离) float deltaX = currentDragPosition.x - lastDragPosition.x; dragSpeed = Mathf.Abs(deltaX) / Time.deltaTime; dragDirection = Mathf.Sign(deltaX); lastDragPosition = currentDragPosition; // 直接设置第一个页面的位置 if (pages[0] != null) { Vector3 newPos = (Vector3)pageDownPosition + dragDelta; pages[0].localPosition = newPos; } // 同步更新其他页面的位置 for (int i = 1; i < pages.Length; i++) { if (pages[i] == null) continue; // 其他页面保持与第一个页面的相对位置 Vector3 originalRelativePos = new Vector3(i * pageSpacing, 0, 0); Vector3 newPos = pages[0].localPosition + originalRelativePos; pages[i].localPosition = newPos; } // 更新总拖动距离 totalDragDistance = dragDelta.x; // 限制边界 float minX = -(pages.Length - 1) * pageSpacing; float maxX = 0f; Vector3 clampedPos = pages[0].localPosition; clampedPos.x = Mathf.Clamp(clampedPos.x, minX, maxX); if (pages[0] != null) { pages[0].localPosition = clampedPos; // 同步其他页面 for (int i = 1; i < pages.Length; i++) { if (pages[i] == null) continue; Vector3 pos = clampedPos + new Vector3(i * pageSpacing, 0, 0); pages[i].localPosition = pos; } } // 计算拖动方向 if (deltaX > 0) { disAnimMove = EnumDistance.Right; } else if (deltaX < 0) { disAnimMove = EnumDistance.Left; } // 处理动画 if (animDown != null) { animTimeDown = dragDelta.x / pageSpacing; StopAtTime(animDown, "open", 1 - Mathf.Abs(animTimeDown)); } if (dragDelta.x >= 0) { if (animLast != null) { animTimeLast = dragDelta.x / pageSpacing; StopAtTime(animLast, "open", Mathf.Abs(animTimeLast)); } animTimeNext = dragDelta.x / pageSpacing; } else { if (animNext != null) { animTimeNext = dragDelta.x / pageSpacing; StopAtTime(animNext, "open", Mathf.Abs(animTimeNext)); } animTimeLast = dragDelta.x / pageSpacing; } } void StopAtTime(Animator anim, string stateName, float targetTime) { // 先播放到目标时间 anim.Play(stateName, 0, targetTime); // 然后将动画速度设为0 anim.speed = 0f; } private void OnDragEnd() { if (!dragValid) return; isDragging = false; // 计算当前页面位置 float currentX = pages[0] != null ? pages[0].localPosition.x : 0f; // 吸附判断逻辑 float dragPercent = Mathf.Abs(totalDragDistance) / pageSpacing; int targetIndex = currentPageIndex; // 初始化目标索引为当前页 // 判断是否是快速滑动 bool isQuickSwipe = dragSpeed > 500f; // 设置速度阈值,500像素/秒 if (dragPercent > snapThreshold) { // 超过阈值,切换到下一页或上一页 if (totalDragDistance < 0) // 向左拖动 { targetIndex = Mathf.Min(currentPageIndex + 1, pages.Length - 1); } else // 向右拖动 { targetIndex = Mathf.Max(currentPageIndex - 1, 0); } } else if (isQuickSwipe) { // 快速滑动,根据速度方向切换页面 if (dragDirection < 0) // 向左快速滑动 { targetIndex = Mathf.Min(currentPageIndex + 1, pages.Length - 1); } else if (dragDirection > 0) // 向右快速滑动 { targetIndex = Mathf.Max(currentPageIndex - 1, 0); } } else { // 未超过阈值且不是快速滑动,吸附到最近页面 float normalizedOffset = -currentX / pageSpacing; targetIndex = Mathf.RoundToInt(normalizedOffset); targetIndex = Mathf.Clamp(targetIndex, 0, pages.Length - 1); } // 判断是否需要切换页面 bool isSwitchingPage = targetIndex != currentPageIndex; // 处理动画 if (isQuickSwipe) { // 快速滑动时使用特殊处理 HandleQuickSwipeAnimation(targetIndex, isSwitchingPage); } else { // 正常速度时使用原始逻辑 HandleNormalAnimation(targetIndex, isSwitchingPage); } // 切换页面 if (targetIndex != currentPageIndex) { GoToPage(targetIndex, false); } else { // 如果目标页面与当前页面相同,直接吸附到当前页 currentState = SlideState.Snapping; } disAnimMove = EnumDistance.None; dragValid = false; // 拖拽结束,重置有效性 } // 处理正常速度的动画 private void HandleNormalAnimation(int targetIndex, bool isSwitchingPage) { if (disAnimMove == EnumDistance.Left) { if (animDown != null) { if (isSwitchingPage) { // 切换页面:当前页播放close动画 animDown.Play("close", -1, Mathf.Abs(animTimeDown)); } else { // 不切换页面:当前页回到open状态 animDown.Play("open", -1, 1 - Mathf.Abs(animTimeDown)); } animDown.speed = 1; } if (animLast != null) { if (animTimeLast > 0) { if (isSwitchingPage && targetIndex == recordPage - 1) { // 切换到上一页:上一页播放open动画 animLast.Play("open", -1, Mathf.Abs(animTimeLast)); } else { // 不切换到上一页:上一页播放close动画 animLast.Play("close", -1, 1 - Mathf.Abs(animTimeLast)); } } else { animLast.Play("close", -1, 1); } animLast.speed = 1; } if (animNext != null) { if (animTimeNext < 0) { if (isSwitchingPage && targetIndex == recordPage + 1) { // 切换到下一页:下一页播放open动画 animNext.Play("open", -1, Mathf.Abs(animTimeNext)); } else { // 不切换到下一页:下一页播放close动画 animNext.Play("close", -1, 1 - Mathf.Abs(animTimeNext)); } } else { animNext.Play("close", -1, 1); } animNext.speed = 1; } } else if (disAnimMove == EnumDistance.Right) { if (animDown != null) { if (isSwitchingPage) { // 切换页面:当前页播放close动画 animDown.Play("close", -1, Mathf.Abs(animTimeDown)); } else { // 不切换页面:当前页回到open状态 animDown.Play("open", -1, 1 - Mathf.Abs(animTimeDown)); } animDown.speed = 1; } if (animLast != null) { if (animTimeLast > 0) { if (isSwitchingPage && targetIndex == recordPage - 1) { // 切换到上一页:上一页播放open动画 animLast.Play("open", -1, Mathf.Abs(animTimeLast)); } else { // 不切换到上一页:上一页播放close动画 animLast.Play("close", -1, 1 - Mathf.Abs(animTimeLast)); } } else { animLast.Play("close", -1, 1); } animLast.speed = 1; } if (animNext != null) { if (animTimeNext < 0) { if (isSwitchingPage && targetIndex == recordPage + 1) { // 切换到下一页:下一页播放open动画 animNext.Play("open", -1, Mathf.Abs(animTimeNext)); } else { // 不切换到下一页:下一页播放close动画 animNext.Play("close", -1, 1 - Mathf.Abs(animTimeNext)); } } animNext.speed = 1; } } else { // 无明确方向时,按照原始代码的逻辑 if (animDown != null) { if (isSwitchingPage) { // 切换页面:当前页播放close动画 animDown.Play("close", -1, Mathf.Abs(animTimeDown)); } else { // 不切换页面:当前页回到open状态 animDown.Play("open", -1, 1 - Mathf.Abs(animTimeDown)); } animDown.speed = 1; } // 其他页面根据实际情况处理 if (isSwitchingPage) { // 目标页播放open动画 if (pageAnimator[targetIndex] != null) { // 根据方向计算开始时间 float startTime = 0f; if (targetIndex < recordPage && animLast != null) { startTime = Mathf.Abs(animTimeLast); } else if (targetIndex > recordPage && animNext != null) { startTime = Mathf.Abs(animTimeNext); } startTime = Mathf.Clamp01(startTime); pageAnimator[targetIndex].Play("open", -1, startTime); pageAnimator[targetIndex].speed = 1; } } // 其他非活动页面设置为close状态 for (int i = 0; i < pageAnimator.Length; i++) { if (pageAnimator[i] == null) continue; if (i == recordPage || i == targetIndex) continue; pageAnimator[i].Play("close", -1, 1); pageAnimator[i].speed = 0; // 不播放动画 } } } // 处理快速滑动的动画 private void HandleQuickSwipeAnimation(int targetIndex, bool isSwitchingPage) { // 快速滑动时,先确保所有动画都能平滑播放 if (isSwitchingPage) { // 当前页播放close动画 if (animDown != null) { float startTime = Mathf.Abs(animTimeDown); startTime = Mathf.Clamp01(startTime); animDown.Play("close", -1, startTime); animDown.speed = 1; } // 目标页播放open动画 if (pageAnimator[targetIndex] != null) { float startTime = 0f; if (targetIndex < recordPage && animLast != null) { startTime = Mathf.Abs(animTimeLast); } else if (targetIndex > recordPage && animNext != null) { startTime = Mathf.Abs(animTimeNext); } startTime = Mathf.Clamp01(startTime); pageAnimator[targetIndex].Play("open", -1, startTime); pageAnimator[targetIndex].speed = 1; } } else { // 不切换页面,回到原始状态 if (animDown != null) { animDown.Play("open", -1, 1 - Mathf.Abs(animTimeDown)); animDown.speed = 1; } } // 确保其他页面重置到正确状态 ResetAllAnimations(targetIndex, isSwitchingPage); } // 重置所有动画状态 private void ResetAllAnimations(int targetIndex, bool isSwitchingPage) { // 所有其他页面设置到close状态 for (int i = 0; i < pageAnimator.Length; i++) { if (pageAnimator[i] == null) continue; if (isSwitchingPage) { if (i == recordPage || i == targetIndex) continue; } else { if (i == recordPage) continue; } // 直接设置到close状态的结束帧,并停止播放 pageAnimator[i].Play("close", -1, 1); pageAnimator[i].speed = 0; } } // 检查动画是否完成 private void CheckAnimationsComplete() { if (isAnimationsComplete) return; bool allComplete = true; // 检查当前页面的动画状态 if (animDown != null && animDown.speed > 0) { AnimatorStateInfo stateInfo = animDown.GetCurrentAnimatorStateInfo(0); if (!stateInfo.IsName("close") && !stateInfo.IsName("open")) { allComplete = false; } } if (allComplete) { isAnimationsComplete = true; // 动画完成后,确保所有页面都处于正确的状态 EnsureFinalAnimationStates(); } } // 确保动画最终状态正确 private void EnsureFinalAnimationStates() { // 当前页面应该是open状态 if (pageAnimator[currentPageIndex] != null) { pageAnimator[currentPageIndex].Play("open", -1, 1); pageAnimator[currentPageIndex].speed = 0; } // 其他所有页面应该是close状态 for (int i = 0; i < pageAnimator.Length; i++) { if (i == currentPageIndex || pageAnimator[i] == null) continue; pageAnimator[i].Play("close", -1, 1); pageAnimator[i].speed = 0; } } private void SmoothToTarget() { if (pages[0] == null) return; // 计算当前第一个页面的目标位置 float targetX = -currentPageIndex * pageSpacing; float currentX = pages[0].localPosition.x; // 平滑移动 float t = Time.deltaTime * snapSpeed; float newX = Mathf.Lerp(currentX, targetX, t); // 应用新位置 Vector3 newPos = pages[0].localPosition; newPos.x = newX; pages[0].localPosition = newPos; // 同步其他页面 for (int i = 1; i < pages.Length; i++) { if (pages[i] == null) continue; Vector3 pos = newPos + new Vector3(i * pageSpacing, 0, 0); pages[i].localPosition = pos; } // 检查是否到达目标 if (Mathf.Abs(newX - targetX) < 0.5f) { // 直接设置精确位置 newPos.x = targetX; pages[0].localPosition = newPos; for (int i = 1; i < pages.Length; i++) { if (pages[i] == null) continue; Vector3 pos = newPos + new Vector3(i * pageSpacing, 0, 0); pages[i].localPosition = pos; } currentState = SlideState.Idle; // 位置到达后,确保动画状态正确 EnsureFinalAnimationStates(); } } private void UpdateCurrentPageIndex() { if (pages[0] == null) return; float currentX = pages[0].localPosition.x; float normalizedOffset = -currentX / pageSpacing + 0.5f; int newIndex = Mathf.FloorToInt(normalizedOffset); newIndex = Mathf.Clamp(newIndex, 0, pages.Length - 1); if (newIndex != currentPageIndex) { currentPageIndex = newIndex; } } public void GoToPage(int targetIndex, bool directJump = false) { if (targetIndex < 0 || targetIndex >= pages.Length) return; if (directJump) { // 使用直接跳转 JumpToPage(targetIndex); } else { // 使用原来的跳转逻辑 currentPageIndex = targetIndex; if (currentState != SlideState.Dragging) { // 立即设置位置 float targetX = -currentPageIndex * pageSpacing; for (int i = 0; i < pages.Length; i++) { if (pages[i] == null) continue; float xPos = targetX + i * pageSpacing; pages[i].localPosition = new Vector3(xPos, 0, 0); } currentState = SlideState.Idle; // 页面切换后,确保动画状态正确 EnsureFinalAnimationStates(); } else { // 如果在拖动中,设置吸附状态 currentState = SlideState.Snapping; } } } public void NextPage() { int nextIndex = Mathf.Min(currentPageIndex + 1, pages.Length - 1); if (nextIndex != currentPageIndex) { GoToPage(nextIndex, false); } } public void PreviousPage() { int prevIndex = Mathf.Max(currentPageIndex - 1, 0); if (prevIndex != currentPageIndex) { GoToPage(prevIndex, false); } } public void JumpToPage(int targetIndex) { if (targetIndex < 0 || targetIndex >= pages.Length) return; // 如果已经在目标页面,直接返回 if (targetIndex == currentPageIndex) return; // 如果正在拖动中,先停止拖动 if (currentState == SlideState.Dragging) { currentState = SlideState.Idle; isDragging = false; dragValid = false; } // 设置跳转参数 jumpTargetIndex = targetIndex; currentJumpMode = JumpMode.Direct; jumpProgress = 0f; // 关键修复:记录起始位置和目标位置 jumpStartX = pages[0].localPosition.x; jumpTargetX = -targetIndex * pageSpacing; // 记录跳转前的页面索引 int previousPageIndex = currentPageIndex; // 直接更新当前页面索引 currentPageIndex = targetIndex; // 停止所有当前动画 StopAllAnimations(); // 处理页面切换动画 HandleDirectJumpAnimation(previousPageIndex, targetIndex); } // 添加停止所有动画的方法 private void StopAllAnimations() { for (int i = 0; i < pageAnimator.Length; i++) { if (pageAnimator[i] != null) { pageAnimator[i].speed = 0; } } } // 处理直接跳转的动画 private void HandleDirectJumpAnimation(int fromIndex, int toIndex) { // 关闭所有页面的动画 for (int i = 0; i < pageAnimator.Length; i++) { if (pageAnimator[i] == null) continue; if (i == toIndex) { // 目标页面直接设置为open状态 pageAnimator[i].Play("open", -1, 1); } else if (i == fromIndex) { // 源页面直接设置为close状态 pageAnimator[i].Play("close", -1, 1); } else { // 其他页面保持原有状态(应该是close状态) // 这里我们不改变它们的状态,只是确保它们不播放动画 if (pageAnimator[i].GetCurrentAnimatorStateInfo(0).IsName("open")) { pageAnimator[i].Play("close", -1, 1); } } pageAnimator[i].speed = 0; } // 如果有标题动画,也进行处理 if (pageTitle != null && pageTitle.Length > 0) { // 这里可以添加标题动画的处理逻辑 // 例如:高亮目标页面的标题 } } enum EnumDistance { None, Left, Right } }