using System; using System.Collections; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; /// /// 滑动列表扩展(吸附功能) /// public sealed class ScrollRect_Adsorb : MonoBehaviour, IBeginDragHandler, IEndDragHandler { /// /// 吸附的速度阈值 /// private const float SNAP_VELOCITY_THRESHOLD = 50f; /// /// 吸附的平滑速度 /// private const float SNAP_SPEED = 50f; /// /// 吸附目标 /// public Transform TsSnapTarget; private ScrollRect scrollRect; private RectTransform rtsContentPanel; /// /// 当前拖动状态 /// private bool isDragging = false; /// /// 是否正在吸附 /// private bool isSnapping = false; private bool isSnap = false; private int curIndex; /// /// 索引变化 /// public Action IndexChange; private void Awake() { scrollRect = GetComponent(); rtsContentPanel = scrollRect.content; isDragging = false; curIndex = 0; } private void Update() { CheckChange(); // 如果用户没有在拖动,且滚动速度已低于阈值,则开始吸附 if (isSnap &&!isDragging && !isSnapping && scrollRect.velocity != Vector2.zero && Mathf.Abs(scrollRect.velocity.y) < SNAP_VELOCITY_THRESHOLD) { isSnap = false; if (Vector2.zero != scrollRect.velocity) scrollRect.velocity = Vector2.zero; // 立即停止惯性滚动 StartCoroutine(SnapToPosition()); } // 如果用户开始拖动,则停止当前的吸附过程 else if (isDragging && isSnapping) { StopAllCoroutines(); isSnapping = false; } } #region API /// /// 将指定索引的 cell 移动到 TsSnapTarget 的位置(平滑过渡) /// /// cell 的索引 public void SnapToIndex(int index) { if (null == rtsContentPanel) return; if (null == TsSnapTarget) return; if (index < 0 || index >= rtsContentPanel.childCount) return; if (isSnapping) { StopAllCoroutines(); isSnapping = false; } StartCoroutine(SnapToIndexCoroutine(index)); } #endregion private void CheckChange() { if (Vector2.zero == scrollRect.velocity) return; if (null == TsSnapTarget) return; Transform tsSelect = null; float minYDis = float.MaxValue; int index = -1; float targetLocalY = scrollRect.viewport.InverseTransformPoint(TsSnapTarget.position).y; for (int i = 0; i < rtsContentPanel.childCount; ++i) { Transform tsCell = rtsContentPanel.GetChild(i); float cellLocalY = scrollRect.viewport.InverseTransformPoint(tsCell.position).y; float dis = cellLocalY - targetLocalY; if (Mathf.Abs(dis) < Mathf.Abs(minYDis)) { minYDis = dis; tsSelect = tsCell; index = i; } } if (null != tsSelect) { curIndex = index; IndexChange?.Invoke(curIndex); } } private IEnumerator SnapToIndexCoroutine(int index) { isSnapping = true; Transform tsCell = rtsContentPanel.GetChild(index); float targetLocalY = scrollRect.viewport.InverseTransformPoint(TsSnapTarget.position).y; float cellLocalY = scrollRect.viewport.InverseTransformPoint(tsCell.position).y; float dis = cellLocalY - targetLocalY; float dt = 0f; Vector2 targetPosition = rtsContentPanel.anchoredPosition + new Vector2(0f, -dis); while (dt < 0.2f && Vector2.Distance(rtsContentPanel.anchoredPosition, targetPosition) > 0.1f) { dt += Time.deltaTime; rtsContentPanel.anchoredPosition = Vector2.Lerp(rtsContentPanel.anchoredPosition, targetPosition, Time.deltaTime * SNAP_SPEED); yield return null; } rtsContentPanel.anchoredPosition = targetPosition; isSnapping = false; } void IBeginDragHandler.OnBeginDrag(PointerEventData eventData) { isDragging = true; isSnap = true; } void IEndDragHandler.OnEndDrag(PointerEventData eventData) { isDragging = false; } IEnumerator SnapToPosition() { isSnapping = true; if (null == TsSnapTarget) { isSnapping = false; yield break; } Transform tsSelect = null; float minYDis = float.MaxValue; float targetLocalY = scrollRect.viewport.InverseTransformPoint(TsSnapTarget.position).y; for (int i = 0; i < rtsContentPanel.childCount; ++i) { Transform tsCell = rtsContentPanel.GetChild(i); float cellLocalY = scrollRect.viewport.InverseTransformPoint(tsCell.position).y; float dis = cellLocalY - targetLocalY; if (Mathf.Abs(dis) < Mathf.Abs(minYDis)) { minYDis = dis; tsSelect = tsCell; } } if (null != tsSelect) { float dt = 0f; Vector2 targetPosition = rtsContentPanel.anchoredPosition + new Vector2(0f, -minYDis); while (dt < 0.2f && Vector2.Distance(rtsContentPanel.anchoredPosition, targetPosition) > 0.1f) { dt += Time.deltaTime; rtsContentPanel.anchoredPosition = Vector2.Lerp(rtsContentPanel.anchoredPosition, targetPosition, Time.deltaTime * SNAP_SPEED); yield return null; } rtsContentPanel.anchoredPosition = targetPosition; } isSnapping = false; } }