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 _itemList; private Delegate _focusItemFunc; private Delegate _unFocusItemFunc; private int _curIndex; private List _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(); 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(); var layoutGroup = _GetContentLayoutGroup(); if (layoutGroup) { if (_isVertical) { //layoutGroup = (VerticalLayoutGroup)layoutGroup; var lay = layoutGroup as VerticalLayoutGroup; if (lay) { _spacing = lay.spacing; } _centerPosX = _scrollRect.GetComponent().rect.height * 0.5f; } else { //layoutGroup = (HorizontalLayoutGroup)layoutGroup; var lay = layoutGroup as HorizontalLayoutGroup; if (lay) { _spacing = lay.spacing; } _centerPosX = _scrollRect.GetComponent().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(); 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 action) { action.Invoke(item); } } if (_unFocusItemFunc != null) { for (int i = 0; i < _itemList.Count; i++) { if (i != curIdx) { if (_unFocusItemFunc is Action unFocusAction) { unFocusAction.Invoke(_itemList[i]); } } } } } } private void _SetCachePosData() { if (_itemList == null || _itemList.Count <= 0) { return; } if (_childPosXList == null) { _childPosXList = new List(); } else { _childPosXList.Clear(); } var childPosX = itemTemplate.GetComponent().rect.width * 0.5f; _childPosXList.Add(childPosX); for (int i = 1; i < _itemList.Count; i++) { childPosX = childPosX + itemTemplate.GetComponent().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().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 list, int maxIndex, int minIndex = 0) { _itemList = list; _maxIndex = maxIndex; _minIndex = minIndex; } public void SetFocusItemFunc(Action action) { _focusItemFunc = action; } public void SetUnFocusItemFunc(Action action) { _unFocusItemFunc = action; } public void ChangeIndex(bool isAdd) { if (_isPlayingMove) { return; } _DoMoveContent(isAdd); } #endregion }