using UnityEngine; using UnityEngine.UI; using UnityEngine.EventSystems; using System; using System.Collections.Generic; using DG.Tweening; public class PanelScroll : ScrollRect { // [SerializeField] public float inertiaMaxTime = 0.5f; //限制惯性持续时间 // private float _scrolledTime = 0f; private Action _stopScrollCallback = null; private Vector2 _lastPosition = Vector2.zero; private float offsetX; private RectTransform _content; private Action StopScrollCallback; private Action RefreshItem; public bool loop = false; public int currentIndex; public int FirstIndex; public int CurDownIndex; private int minShowItemNum; List itemGoList = new List(); Vector2 startDeltaPos; Vector2 endDeltaDirection; bool isDrag = false; public bool isReversal = false; int minShowAdd = 2; Vector2 InverseTransformPointOffset; float startAdd = 0; protected override void Start() { base.Start(); RectTransform rectTransform = GetComponent(); var pivot = rectTransform.pivot - new Vector2(0.5f, 0.5f); var rect = rectTransform.rect.size; InverseTransformPointOffset = new Vector2(pivot.x * rect.x, pivot.y * rect.y); } public List Init(float _offsetX, GameObject _item, Action _OnBtnItem, List _ItemData, bool click = false, int _currentIndex = 0, int _minShowAdd = 2) where T : PanelItemBase { float spacing = 0; float addSizeDelta = 0; startAdd = 0; if (horizontal) { HorizontalLayoutGroup layoutGroup = content.GetComponent(); if (layoutGroup != null) { spacing = layoutGroup.spacing; addSizeDelta = layoutGroup.padding.horizontal; startAdd = layoutGroup.padding.left; layoutGroup.enabled = false; } } else { VerticalLayoutGroup layoutGroup = content.GetComponent(); if (layoutGroup != null) { spacing = layoutGroup.spacing; addSizeDelta = layoutGroup.padding.vertical; startAdd = layoutGroup.padding.top; layoutGroup.enabled = false; } } _offsetX += spacing; minShowAdd = _minShowAdd; if (_currentIndex >= _ItemData.Count) { _currentIndex = _ItemData.Count - 1; } int _showItemNum; int showDelta = _ItemData.Count; if (horizontal) minShowItemNum = Mathf.CeilToInt(GetComponent().rect.width / _offsetX) + minShowAdd; else minShowItemNum = Mathf.CeilToInt(GetComponent().rect.height / _offsetX) + minShowAdd; _showItemNum = minShowItemNum; if (loop) { showDelta = minShowItemNum; } else if (minShowItemNum > _ItemData.Count) { _showItemNum = showDelta; } movementType = loop ? MovementType.Unrestricted : MovementType.Elastic; offsetX = _offsetX; _item.gameObject.SetActive(false); _content = transform.Find("Viewport/Content").GetComponent(); _content.sizeDelta = horizontal ? new Vector2(showDelta * _offsetX + addSizeDelta, _content.sizeDelta.y) : new Vector2(_content.sizeDelta.x, showDelta * _offsetX + addSizeDelta); if (isReversal) { _content.anchoredPosition = horizontal ? new Vector2(loop ? -((_showItemNum - 1) / 2f - _currentIndex) * _offsetX : _currentIndex * _offsetX, _content.anchoredPosition.y) : new Vector2(_content.anchoredPosition.x, loop ? -(_currentIndex - (_showItemNum - 1) / 2f) * _offsetX : -_currentIndex * _offsetX); } else { _content.anchoredPosition = horizontal ? new Vector2(loop ? ((_showItemNum - 1) / 2f - _currentIndex) * _offsetX : -_currentIndex * _offsetX, _content.anchoredPosition.y) : new Vector2(_content.anchoredPosition.x, loop ? (_currentIndex - (_showItemNum - 1) / 2f) * _offsetX : _currentIndex * _offsetX); } List items = new List(); int count = itemGoList.Count; for (int i = 0; i < count; i++) { itemGoList[i].SetActive(false); } for (int i = 0; i < _showItemNum; i++) { GameObject go; if (count > i) { go = itemGoList[i].gameObject; } else { go = Instantiate(_item, _content); itemGoList.Add(go); } go.SetActive(true); if (isReversal) { go.transform.GetComponent().anchoredPosition = horizontal ? new Vector3(-(i + 0.5f) * _offsetX - startAdd, go.transform.GetComponent().anchoredPosition.y, 0) : new Vector3(go.transform.GetComponent().anchoredPosition.x, (i + 0.5f) * _offsetX + startAdd, 0); } else { go.transform.GetComponent().anchoredPosition = horizontal ? new Vector3((i + 0.5f) * _offsetX + startAdd, go.transform.GetComponent().anchoredPosition.y, 0) : new Vector3(go.transform.GetComponent().anchoredPosition.x, -(i + 0.5f) * _offsetX - startAdd, 0); } T t = go.GetComponent() ?? go.AddComponent(); items.Add(t); items[i].index = i; var i1 = i; items[i].OnBtnItemAction = () => { _OnBtnItem?.Invoke(items[i1]); }; items[i].SetDataAction = () => { int count = _ItemData.Count; items[i1].index %= count; if (items[i1].index < 0) { items[i1].index += count; } return _ItemData[items[i1].index]; }; } if (isReversal) { ReversalSetOnValueChanged(items, _ItemData); } else { SetOnValueChanged(items, _ItemData); } SetStopScrollCallback(click, items, _ItemData); RefreshItem = () => { foreach (var t in items) { t.SetData(); } }; currentIndex = _currentIndex; if (currentIndex > 1) { if (!loop) { if (currentIndex + _showItemNum > showDelta) { currentIndex = showDelta - _showItemNum; } currentIndex--; } //currentIndex -= minShowItemNum; for (int i = items.Count - 1; i >= 0; i--) { T t = items[i]; if (isReversal) { t.rectTransform.anchoredPosition = horizontal ? new Vector3(t.rectTransform.anchoredPosition.x - currentIndex * offsetX, t.rectTransform.anchoredPosition.y, 0) : new Vector3(t.rectTransform.anchoredPosition.x, t.rectTransform.anchoredPosition.y + currentIndex * offsetX, 0); } else { t.rectTransform.anchoredPosition = horizontal ? new Vector3(t.rectTransform.anchoredPosition.x + currentIndex * offsetX, t.rectTransform.anchoredPosition.y, 0) : new Vector3(t.rectTransform.anchoredPosition.x, t.rectTransform.anchoredPosition.y - currentIndex * offsetX, 0); } t.index += currentIndex; } } RefreshItem(); currentIndex = _currentIndex; if (click) { if (currentIndex > 0) { for (int i = 0; i < items.Count; i++) { if (items[i].index == _currentIndex) { _OnBtnItem?.Invoke(items[i]); return items; } } } if (items.Count > 0) { _OnBtnItem?.Invoke(items[0]); } } return items; } void ReversalSetOnValueChanged(List items, List _ItemData) where T : PanelItemBase { onValueChanged.RemoveAllListeners(); if (horizontal) { onValueChanged.AddListener(arg0 => { foreach (var t in items) { //transform相对于scrollRect的位置 var arg1 = InverseTransformPoint(t.transform.position); if (arg1.x < -(minShowItemNum + 1) / 2 * offsetX/* - offsetX*/) { if (loop || t.index - minShowItemNum >= 0) { t.rectTransform.anchoredPosition = new Vector3(t.rectTransform.anchoredPosition.x + minShowItemNum * offsetX, t.rectTransform.anchoredPosition.y, 0); t.OnChangIndex(-minShowItemNum); } } else if (arg1.x > (minShowItemNum + 1) / 2 * offsetX /*+ offsetX*/) { if (loop || t.index + minShowItemNum < _ItemData.Count) { t.rectTransform.anchoredPosition = new Vector3(t.rectTransform.anchoredPosition.x - minShowItemNum * offsetX, t.rectTransform.anchoredPosition.y, 0); t.OnChangIndex(minShowItemNum); } } arg1 = InverseTransformPoint(t.transform.position); t.OffsetX(arg1.x, offsetX); } }); } else { onValueChanged.AddListener(arg0 => { foreach (var t in items) { //transform相对于scrollRect的位置 var arg1 = InverseTransformPoint(t.transform.position); if (arg1.y < -(minShowItemNum + 1) / 2 * offsetX/* - offsetX*/) { if (loop || t.index + minShowItemNum < _ItemData.Count) { t.rectTransform.anchoredPosition = new Vector3(t.rectTransform.anchoredPosition.x, t.rectTransform.anchoredPosition.y + minShowItemNum * offsetX, 0); t.OnChangIndex(minShowItemNum); } } else if (arg1.y > (minShowItemNum + 1) / 2 * offsetX /*+ offsetX*/) { if (loop || t.index - minShowItemNum >= 0) { t.rectTransform.anchoredPosition = new Vector3(t.rectTransform.anchoredPosition.x, t.rectTransform.anchoredPosition.y - minShowItemNum * offsetX, 0); t.OnChangIndex(-minShowItemNum); } } arg1 = InverseTransformPoint(t.transform.position); t.OffsetX(arg1.y, offsetX); } }); } } void SetOnValueChanged(List items, List _ItemData) where T : PanelItemBase { onValueChanged.RemoveAllListeners(); if (horizontal) { onValueChanged.AddListener(arg0 => { var panelHeight = GetComponent().rect.width; var panelUpPos = panelHeight / 2f; var PanelDownPos = -panelHeight / 2f; foreach (var t in items) { //transform相对于scrollRect的位置 var arg1 = InverseTransformPoint(t.transform.position); //进入退出窗口判断 var itemUpPos = arg1.x + offsetX / 2f; var itemDownPos = arg1.x - offsetX / 2f; if (itemUpPos > PanelDownPos && CurDownIndex < t.index) CurDownIndex = t.index; if (itemUpPos < PanelDownPos && CurDownIndex >= t.index) CurDownIndex = Mathf.Clamp(t.index - 1, 0, _ItemData.Count - 1); if (itemDownPos < panelUpPos && FirstIndex > t.index) { FirstIndex = t.index; } if (itemDownPos > panelUpPos && FirstIndex <= t.index) FirstIndex = Mathf.Clamp(t.index + 1, 0, _ItemData.Count - 1); if (arg1.x < -(minShowItemNum + 1) / 2 * offsetX/* - offsetX*/) { if (loop || t.index + minShowItemNum < _ItemData.Count) { t.rectTransform.anchoredPosition = new Vector3(t.rectTransform.anchoredPosition.x + minShowItemNum * offsetX, t.rectTransform.anchoredPosition.y, 0); t.OnChangIndex(minShowItemNum); } } else if (arg1.x > (minShowItemNum + 1) / 2 * offsetX /*+ offsetX*/) { if (loop || t.index - minShowItemNum >= 0) { t.rectTransform.anchoredPosition = new Vector3(t.rectTransform.anchoredPosition.x - minShowItemNum * offsetX, t.rectTransform.anchoredPosition.y, 0); t.OnChangIndex(-minShowItemNum); } } arg1 = InverseTransformPoint(t.transform.position); t.OffsetX(arg1.x, offsetX); } }); } else { onValueChanged.AddListener(arg0 => { var panelHeight = GetComponent().rect.height; var panelUpPos = panelHeight / 2f; var PanelDownPos = -panelHeight / 2f; foreach (var t in items) { //transform相对于scrollRect的位置 var arg1 = InverseTransformPoint(t.transform.position); //进入退出窗口判断 var itemUpPos = arg1.y + offsetX / 2f; var itemDownPos = arg1.y - offsetX / 2f; if (itemUpPos > PanelDownPos && CurDownIndex < t.index) CurDownIndex = t.index; if (itemUpPos < PanelDownPos && CurDownIndex >= t.index) CurDownIndex = Mathf.Clamp(t.index - 1, 0, _ItemData.Count - 1); if (itemDownPos < panelUpPos && FirstIndex > t.index) { FirstIndex = t.index; } if (itemDownPos > panelUpPos && FirstIndex <= t.index) FirstIndex = Mathf.Clamp(t.index + 1, 0, _ItemData.Count - 1); //循环判断 if (arg1.y < -(minShowItemNum + 1) / 2 * offsetX /*- offsetX*/) { if (loop || t.index - minShowItemNum >= 0) { t.rectTransform.anchoredPosition = new Vector3(t.rectTransform.anchoredPosition.x, t.rectTransform.anchoredPosition.y + minShowItemNum * offsetX, 0); t.OnChangIndex(-minShowItemNum); } } else if (arg1.y > (minShowItemNum + 1) / 2 * offsetX /*+ offsetX*/) { if (loop || t.index + minShowItemNum < _ItemData.Count) { t.rectTransform.anchoredPosition = new Vector3(t.rectTransform.anchoredPosition.x, t.rectTransform.anchoredPosition.y - minShowItemNum * offsetX, 0); t.OnChangIndex(minShowItemNum); } } arg1 = InverseTransformPoint(t.transform.position); t.OffsetX(arg1.y, offsetX); } }); } } void SetStopScrollCallback(bool click, List items, List _ItemData) where T : PanelItemBase { if (click) { if (horizontal) { StopScrollCallback = () => { int count = items.Count; for (int i = 0; i < count; i++) { T t = items[i]; Vector3 pos = InverseTransformPoint(t.transform.position); if (pos.x > -offsetX / 2 - 0.01f && pos.x < offsetX / 2 + 0.01f) { if (isDrag) { if (endDeltaDirection.x > 1) { if (pos.x > 5) { if (i != 0) { if (loop || currentIndex > 0) { t = items[i - 1]; } } else if (loop || t.index - minShowItemNum / 2 >= 0) { t = items[^1]; } } } else if (endDeltaDirection.x < -1) { if (pos.x < -5) { if (i < count - 1) { if (loop || currentIndex < _ItemData.Count - 1) { t = items[i + 1]; } } else if (loop || t.index + minShowItemNum / 2 < _ItemData.Count) { t = items[0]; } } } } if (!loop) { content.DOLocalMoveX(-t.transform.localPosition.x + 0.5f * offsetX, 0.2f); } currentIndex = t.index; t.OnBtnItem(); break; } } isDrag = false; }; } else { StopScrollCallback = () => { int count = items.Count; for (int i = 0; i < count; i++) { T t = items[i]; Vector3 pos = InverseTransformPoint(t.transform.position); if (pos.y > -offsetX / 2 - 0.01f && pos.y < offsetX / 2 + 0.01f) { if (isDrag) { if (endDeltaDirection.y > 1) { if (pos.y > 10) { if (i < count - 1) { t = items[i + 1]; } else if (loop || t.index + minShowItemNum / 2 < _ItemData.Count) { t = items[0]; } } } else if (endDeltaDirection.y < -1) { if (pos.y < -10) { if (i != 0) { t = items[i - 1]; } else if (loop || t.index - minShowItemNum / 2 >= 0) { t = items[^1]; } } } } if (!loop) { content.DOLocalMoveY(-t.transform.localPosition.y - 0.5f * offsetX, 0.2f); } currentIndex = t.index; t.OnBtnItem(); break; } } isDrag = false; }; } } else { StopScrollCallback = null; } } //跳到目标位置 public void JumpToTarget(int index, float posOffset = 0f) { float offset = offsetX; if (isReversal) { offset = -offsetX; } if (loop) { if (index > currentIndex) { if (horizontal) { _content.DOLocalMoveX(_content.localPosition.x - (index - currentIndex) * offset + posOffset, 0.3f).OnComplete(() => { StopScrollCallback?.Invoke(); }); } else { _content.DOLocalMoveY(_content.localPosition.y + (index - currentIndex) * offset + posOffset, 0.3f).OnComplete(() => { StopScrollCallback?.Invoke(); }); } } else { if (horizontal) { _content.DOLocalMoveX(_content.localPosition.x + (currentIndex - index) * offset + posOffset, 0.3f).OnComplete(() => { StopScrollCallback?.Invoke(); }); } else { _content.DOLocalMoveY(_content.localPosition.y - (currentIndex - index) * offset + posOffset, 0.3f).OnComplete(() => { StopScrollCallback?.Invoke(); }); } } } else { index -= (int)((minShowItemNum - minShowAdd) / 2f); if (horizontal) { _content.DOLocalMoveX(-index * offset + posOffset, 0.5f).OnComplete(() => { StopScrollCallback?.Invoke(); }); } else { _content.DOLocalMoveY(index * offset + posOffset, 0.5f).OnComplete(() => { StopScrollCallback?.Invoke(); }); } } } public void Refresh() { RefreshItem?.Invoke(); } public override void OnBeginDrag(PointerEventData eventData) { base.OnBeginDrag(eventData); startDeltaPos = eventData.position; } public override void OnEndDrag(PointerEventData eventData) { base.OnEndDrag(eventData); endDeltaDirection = eventData.position - startDeltaPos; isDrag = true; _stopScrollCallback = StopScrollCallback; // _scrolledTime = 0f; //如果没有在移动 if (this.velocity.magnitude < 3) { if (_stopScrollCallback != null) { _stopScrollCallback(); _stopScrollCallback = null; } } } public override void OnDrag(PointerEventData eventData) { base.OnDrag(eventData); // _scrolledTime = 0f; } protected override void SetContentAnchoredPosition(Vector2 position) { if (Vector3.Distance(_lastPosition, position) < 3) { if (_stopScrollCallback != null) { _stopScrollCallback(); _stopScrollCallback = null; } return; } _lastPosition = position; base.SetContentAnchoredPosition(position); // _scrolledTime += Time.unscaledDeltaTime; } public Vector3 InverseTransformPoint(Vector3 position) { var point = transform.InverseTransformPoint(position); point.x += InverseTransformPointOffset.x; point.y += InverseTransformPointOffset.y; return point; } } public abstract class PanelItemBase : MonoBehaviour { private RectTransform _rectTransform; public Action OnBtnItemAction; public Func SetDataAction; public T data; public RectTransform rectTransform { get { if (_rectTransform == null) { _rectTransform = GetComponent(); } return _rectTransform; } } public int index = 0; public abstract void OnInit(); public virtual void OffsetX(float _offsetX, float width) { } public virtual void SetData() { data = SetDataAction(); OnInit(); } public virtual void OnBtnItem() { OnBtnItemAction?.Invoke(); } public void OnChangIndex(int _index) { index += _index; SetData(); } }