295 lines
7.9 KiB
C#
295 lines
7.9 KiB
C#
|
|
using System;
|
||
|
|
using System.Collections.Generic;
|
||
|
|
using Cysharp.Threading.Tasks;
|
||
|
|
using UnityEngine;
|
||
|
|
using UnityEngine.EventSystems;
|
||
|
|
using UnityEngine.UI;
|
||
|
|
using DG.Tweening;
|
||
|
|
|
||
|
|
[RequireComponent(typeof(ScrollRect))]
|
||
|
|
public class UIScrollFocusHelper: MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
|
||
|
|
{
|
||
|
|
public GameObject itemTemplate;
|
||
|
|
public Transform focusItemTrans;
|
||
|
|
|
||
|
|
private ScrollRect _scrollRect;
|
||
|
|
private GameObject _content;
|
||
|
|
private bool _isVertical;
|
||
|
|
private List<GameObject> _itemList;
|
||
|
|
|
||
|
|
private Delegate _focusItemFunc;
|
||
|
|
private Delegate _unFocusItemFunc;
|
||
|
|
|
||
|
|
private int _curIndex;
|
||
|
|
private List<float> _childPosXList;
|
||
|
|
private float _centerPosX;
|
||
|
|
private float _spacing;
|
||
|
|
|
||
|
|
private Vector2 _lastFrameContentPos;
|
||
|
|
private RectTransform _contentRectTrans;
|
||
|
|
|
||
|
|
private int _maxIndex;
|
||
|
|
private int _minIndex;
|
||
|
|
|
||
|
|
private bool _isPlayingMove;
|
||
|
|
public int CurIndex => _curIndex;
|
||
|
|
|
||
|
|
#region DragHandler
|
||
|
|
|
||
|
|
public void OnBeginDrag(PointerEventData eventData)
|
||
|
|
{
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
public void OnDrag(PointerEventData eventData)
|
||
|
|
{
|
||
|
|
if (_itemList == null || _itemList.Count <= 0)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
_SetCurIndexFocus();
|
||
|
|
}
|
||
|
|
|
||
|
|
public void OnEndDrag(PointerEventData eventData)
|
||
|
|
{
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
#endregion
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
#region UnityEvent Function
|
||
|
|
|
||
|
|
private void Awake()
|
||
|
|
{
|
||
|
|
_scrollRect = gameObject.GetComponent<ScrollRect>();
|
||
|
|
if (!_scrollRect)
|
||
|
|
{
|
||
|
|
DebugUtil.LogError("UIScrollFocusHelper Error!!! 请检查节点是否绑定ScrollRect组件!!! ");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
_content = _scrollRect.transform.Find("Viewport/Content").gameObject;
|
||
|
|
|
||
|
|
bool isVertical = true;
|
||
|
|
if (_scrollRect.horizontal)
|
||
|
|
{
|
||
|
|
if (_scrollRect.vertical)
|
||
|
|
{
|
||
|
|
DebugUtil.LogError("UIScrollFocusHelper Error!!! 仅支持ScrollRect单方向拖动!!! ");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
isVertical = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
_isVertical = isVertical;
|
||
|
|
|
||
|
|
_contentRectTrans = _content.GetComponent<RectTransform>();
|
||
|
|
|
||
|
|
var layoutGroup = _GetContentLayoutGroup();
|
||
|
|
if (layoutGroup)
|
||
|
|
{
|
||
|
|
if (_isVertical)
|
||
|
|
{
|
||
|
|
//layoutGroup = (VerticalLayoutGroup)layoutGroup;
|
||
|
|
var lay = layoutGroup as VerticalLayoutGroup;
|
||
|
|
if (lay)
|
||
|
|
{
|
||
|
|
_spacing = lay.spacing;
|
||
|
|
}
|
||
|
|
_centerPosX = _scrollRect.GetComponent<RectTransform>().rect.height * 0.5f;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
//layoutGroup = (HorizontalLayoutGroup)layoutGroup;
|
||
|
|
var lay = layoutGroup as HorizontalLayoutGroup;
|
||
|
|
if (lay)
|
||
|
|
{
|
||
|
|
_spacing = lay.spacing;
|
||
|
|
}
|
||
|
|
_centerPosX = _scrollRect.GetComponent<RectTransform>().rect.width * 0.5f;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
_scrollRect.onValueChanged.AddListener(_RefreshCurIndex);
|
||
|
|
_isPlayingMove = false;
|
||
|
|
}
|
||
|
|
#endregion
|
||
|
|
|
||
|
|
private LayoutGroup _GetContentLayoutGroup()
|
||
|
|
{
|
||
|
|
if (!_content)
|
||
|
|
{
|
||
|
|
DebugUtil.LogError("UIScrollFocusHelper Error!!! 请检查ScrollRect的Content节点!!! ");
|
||
|
|
}
|
||
|
|
|
||
|
|
var layoutGroup = _content.GetComponent<LayoutGroup>();
|
||
|
|
return layoutGroup;
|
||
|
|
}
|
||
|
|
|
||
|
|
private void _RefreshCurIndex(Vector2 pos)
|
||
|
|
{
|
||
|
|
if (_itemList == null || _itemList.Count <= 0)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
_SetCurIndexFocus();
|
||
|
|
}
|
||
|
|
|
||
|
|
private void _SetCurIndexFocus()
|
||
|
|
{
|
||
|
|
var curIdx = FindClosestIndex();
|
||
|
|
if (_curIndex != curIdx)
|
||
|
|
{
|
||
|
|
_curIndex = curIdx;
|
||
|
|
var item = _itemList[curIdx];
|
||
|
|
if (_focusItemFunc != null)
|
||
|
|
{
|
||
|
|
if (_focusItemFunc is Action<GameObject> action)
|
||
|
|
{
|
||
|
|
action.Invoke(item);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (_unFocusItemFunc != null)
|
||
|
|
{
|
||
|
|
for (int i = 0; i < _itemList.Count; i++)
|
||
|
|
{
|
||
|
|
if (i != curIdx)
|
||
|
|
{
|
||
|
|
if (_unFocusItemFunc is Action<GameObject> unFocusAction)
|
||
|
|
{
|
||
|
|
unFocusAction.Invoke(_itemList[i]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void _SetCachePosData()
|
||
|
|
{
|
||
|
|
if (_itemList == null || _itemList.Count <= 0)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (_childPosXList == null)
|
||
|
|
{
|
||
|
|
_childPosXList = new List<float>();
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
_childPosXList.Clear();
|
||
|
|
}
|
||
|
|
|
||
|
|
var childPosX = itemTemplate.GetComponent<RectTransform>().rect.width * 0.5f;
|
||
|
|
_childPosXList.Add(childPosX);
|
||
|
|
for (int i = 1; i < _itemList.Count; i++)
|
||
|
|
{
|
||
|
|
childPosX = childPosX + itemTemplate.GetComponent<RectTransform>().rect.width + _spacing;
|
||
|
|
_childPosXList.Add(childPosX);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private int FindClosestIndex()
|
||
|
|
{
|
||
|
|
int retIndex = 0;
|
||
|
|
var contentPosX = _contentRectTrans.localPosition.x;
|
||
|
|
var curCenterPosX = _centerPosX - contentPosX;
|
||
|
|
|
||
|
|
var minDis = itemTemplate.GetComponent<RectTransform>().rect.width;
|
||
|
|
for(int i = 0; i < _childPosXList.Count; i++)
|
||
|
|
{
|
||
|
|
var distance = Mathf.Abs(curCenterPosX - _childPosXList[i]);
|
||
|
|
if (distance < minDis)
|
||
|
|
{
|
||
|
|
minDis = distance;
|
||
|
|
retIndex = i;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return retIndex;
|
||
|
|
}
|
||
|
|
|
||
|
|
private async void _DoMoveContent(bool isRight)
|
||
|
|
{
|
||
|
|
if (isRight)
|
||
|
|
{
|
||
|
|
_isPlayingMove = true;
|
||
|
|
var curIndex = _curIndex + 1;
|
||
|
|
if (curIndex < _maxIndex)
|
||
|
|
{
|
||
|
|
//计算相邻两个item之间的距离
|
||
|
|
var oldPosX = _childPosXList[_curIndex];
|
||
|
|
_curIndex++;
|
||
|
|
var newPosX = _childPosXList[_curIndex];
|
||
|
|
var distance = Mathf.Abs(oldPosX - newPosX);
|
||
|
|
|
||
|
|
//播放移动动画
|
||
|
|
var curPos = _contentRectTrans.localPosition;
|
||
|
|
var childPosX = curPos.x - distance;
|
||
|
|
var newPos = new Vector3(childPosX, curPos.y, curPos.z);
|
||
|
|
_contentRectTrans.DOLocalMove(newPos, 0.75f);
|
||
|
|
UniTask.Delay(750);
|
||
|
|
_isPlayingMove = false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
var curIndex = _curIndex - 1;
|
||
|
|
if (curIndex > _minIndex)
|
||
|
|
{
|
||
|
|
//计算相邻两个item之间的距离
|
||
|
|
var oldPosX = _childPosXList[_curIndex];
|
||
|
|
_curIndex--;
|
||
|
|
var newPosX = _childPosXList[_curIndex];
|
||
|
|
var distance = Mathf.Abs(oldPosX - newPosX);
|
||
|
|
|
||
|
|
//播放移动动画
|
||
|
|
var curPos = _contentRectTrans.localPosition;
|
||
|
|
var childPosX = curPos.x + distance;
|
||
|
|
var newPos = new Vector3(childPosX, curPos.y, curPos.z);
|
||
|
|
_contentRectTrans.DOLocalMove(newPos, 0.75f);
|
||
|
|
UniTask.Delay(750);
|
||
|
|
_isPlayingMove = false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#region API
|
||
|
|
|
||
|
|
public void Init()
|
||
|
|
{
|
||
|
|
_SetCachePosData();
|
||
|
|
_SetCurIndexFocus();
|
||
|
|
}
|
||
|
|
public void SetData(List<GameObject> list, int maxIndex, int minIndex = 0)
|
||
|
|
{
|
||
|
|
_itemList = list;
|
||
|
|
_maxIndex = maxIndex;
|
||
|
|
_minIndex = minIndex;
|
||
|
|
}
|
||
|
|
|
||
|
|
public void SetFocusItemFunc(Action<GameObject> action)
|
||
|
|
{
|
||
|
|
_focusItemFunc = action;
|
||
|
|
}
|
||
|
|
|
||
|
|
public void SetUnFocusItemFunc(Action<GameObject> action)
|
||
|
|
{
|
||
|
|
_unFocusItemFunc = action;
|
||
|
|
}
|
||
|
|
|
||
|
|
public void ChangeIndex(bool isAdd)
|
||
|
|
{
|
||
|
|
if (_isPlayingMove)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
_DoMoveContent(isAdd);
|
||
|
|
}
|
||
|
|
|
||
|
|
#endregion
|
||
|
|
}
|