204 lines
5.9 KiB
C#
204 lines
5.9 KiB
C#
using System;
|
|
using System.Collections;
|
|
using UnityEngine;
|
|
using UnityEngine.EventSystems;
|
|
using UnityEngine.UI;
|
|
|
|
/// <summary>
|
|
/// 滑动列表扩展(吸附功能)
|
|
/// </summary>
|
|
public sealed class ScrollRect_Adsorb : MonoBehaviour, IBeginDragHandler, IEndDragHandler
|
|
{
|
|
/// <summary>
|
|
/// 吸附的速度阈值
|
|
/// </summary>
|
|
private const float SNAP_VELOCITY_THRESHOLD = 50f;
|
|
/// <summary>
|
|
/// 吸附的平滑速度
|
|
/// </summary>
|
|
private const float SNAP_SPEED = 50f;
|
|
|
|
/// <summary>
|
|
/// 吸附目标
|
|
/// </summary>
|
|
public Transform TsSnapTarget;
|
|
private ScrollRect scrollRect;
|
|
private RectTransform rtsContentPanel;
|
|
/// <summary>
|
|
/// 当前拖动状态
|
|
/// </summary>
|
|
private bool isDragging = false;
|
|
/// <summary>
|
|
/// 是否正在吸附
|
|
/// </summary>
|
|
private bool isSnapping = false;
|
|
private bool isSnap = false;
|
|
private int curIndex;
|
|
|
|
/// <summary>
|
|
/// 索引变化
|
|
/// </summary>
|
|
public Action<int> IndexChange;
|
|
|
|
private void Awake()
|
|
{
|
|
scrollRect = GetComponent<ScrollRect>();
|
|
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
|
|
/// <summary>
|
|
/// 将指定索引的 cell 移动到 TsSnapTarget 的位置(平滑过渡)
|
|
/// </summary>
|
|
/// <param name="index">cell 的索引</param>
|
|
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;
|
|
}
|
|
} |