using System.Collections;
|
using System.Collections.Generic;
|
using UnityEngine;
|
using UnityEngine.EventSystems;
|
using System;
|
using Cysharp.Threading.Tasks;
|
using System.Diagnostics;
|
|
[DisallowMultipleComponent]
|
|
public class CyclicScroll : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
|
{
|
[SerializeField] RectTransform m_Content;
|
public RectTransform content { get { return m_Content; } }
|
|
[SerializeField] protected BoundOffset m_BoundOffset;
|
[SerializeField] protected Vector2 m_CellSize = new Vector2(100, 100);
|
public Vector2 cellSize { get { return m_CellSize; } }
|
|
[SerializeField] protected Vector2 m_Spacing;
|
public Vector2 spacing { get { return m_Spacing; } }
|
|
[SerializeField] Align m_Align = Align.Top;
|
public Align align { get { return m_Align; } }
|
|
[SerializeField] bool m_ElasticityOn = true;
|
public bool elasticityOn { get { return m_ElasticityOn; } }
|
|
[SerializeField] private float m_Elasticity = 0.1f;
|
public float elasticity { get { return m_Elasticity; } }
|
|
[SerializeField] private float m_DecelerationRate = 0.135f;
|
public float decelerationRate { get { return m_DecelerationRate; } }
|
|
[SerializeField] string m_ChildPrefabName = string.Empty;
|
[SerializeField] string m_ChildDisplayName = string.Empty;
|
[SerializeField] int m_ChildCount = 5;
|
|
public float normalizedPosition {
|
get {
|
if (content == null)
|
{
|
return 0f;
|
}
|
|
var offset = (dataMaxOffset - dataMinOffset);
|
switch (align)
|
{
|
case Align.Top:
|
case Align.Bottom:
|
return Mathf.Clamp01(Mathf.Abs((content.anchoredPosition.y - dataMinOffset.y) / offset.y));
|
case Align.Left:
|
case Align.Right:
|
return Mathf.Clamp01(Mathf.Abs((content.anchoredPosition.x - dataMinOffset.x) / offset.x));
|
default:
|
return 0f;
|
}
|
}
|
set {
|
if (content == null)
|
{
|
return;
|
}
|
|
value = Mathf.Clamp01(value);
|
var normalize = 0f;
|
var offset = (dataMaxOffset - dataMinOffset);
|
switch (align)
|
{
|
case Align.Top:
|
case Align.Bottom:
|
normalize = (content.anchoredPosition.y - dataMinOffset.y) / offset.y;
|
if (Mathf.Abs(normalize - value) > 0.0001f)
|
{
|
targetOffset = dataMinOffset + new Vector2(0, dataMaxOffset.y * value);
|
velocity = 0f;
|
autoLerp = true;
|
refAutoLerpPosition = Vector2.zero;
|
}
|
break;
|
case Align.Left:
|
case Align.Right:
|
normalize = (content.anchoredPosition.x - dataMinOffset.x) / offset.x;
|
if (Mathf.Abs(normalize - value) > 0.0001f)
|
{
|
targetOffset = dataMinOffset + new Vector2(dataMaxOffset.x * value, 0);
|
velocity = 0f;
|
autoLerp = true;
|
refAutoLerpPosition = Vector2.zero;
|
}
|
break;
|
}
|
}
|
}
|
|
protected Vector2 dataMaxOffset = Vector2.zero;
|
protected Vector2 dataMinOffset = Vector2.zero;
|
|
public bool autoLerp { get; private set; }
|
|
Vector2 targetOffset = Vector2.zero;
|
Vector2 refAutoLerpPosition = Vector2.zero;
|
|
protected IList datas;
|
protected List<ScrollItem> infiniteItems = new List<ScrollItem>();
|
List<ScrollItem> tempList = new List<ScrollItem>();
|
|
Vector2 startMousePosition = Vector2.zero;
|
Vector2 startContentPosition = Vector2.zero;
|
Vector2 prevPosition = Vector2.zero;
|
|
protected float velocity = 0f;
|
protected bool dragging = false;
|
|
public int dataCount {
|
get {
|
return datas == null ? 0 : datas.Count;
|
}
|
}
|
bool moveNextable {
|
get {
|
return hostIndex < dataCount - 1;
|
}
|
}
|
bool moveLastable {
|
get {
|
return preIndex > 0;
|
}
|
}
|
|
public RectTransform rectTransform { get { return this.transform as RectTransform; } }
|
|
private Vector2 maxOffset { get { return rectTransform.GetMaxReferencePosition(rectTransform); } }
|
private Vector2 minOffset { get { return rectTransform.GetMinReferencePosition(rectTransform); } }
|
|
protected int preIndex {
|
get; set;
|
}
|
|
protected int hostIndex {
|
get; set;
|
}
|
|
public void LuaInit(int[] indexs, bool stepByStep)
|
{
|
Init(new List<int>(indexs), stepByStep);
|
}
|
|
public virtual void Init<T>(List<T> _datas, bool _stepByStep = false)
|
{
|
if (_datas == null)
|
{
|
this.SetActive(false);
|
return;
|
}
|
|
datas = _datas;
|
|
ReArrange();
|
FillBatchData(0);
|
|
if (_stepByStep)
|
{
|
for (int i = 0; i < infiniteItems.Count; i++)
|
{
|
var infiniteItem = infiniteItems[i];
|
if (infiniteItem != null)
|
{
|
infiniteItem.SetActive(false);
|
}
|
}
|
}
|
|
this.SetActive(true);
|
|
dataMinOffset = content.anchoredPosition;
|
var totalOffset = dataCount == 0 ? Vector2.zero : cellSize * dataCount + spacing * (dataCount - 1)
|
+ new Vector2(m_BoundOffset.left + m_BoundOffset.right, m_BoundOffset.top + m_BoundOffset.bottom);
|
|
var longer = Mathf.Abs(totalOffset.x) > Mathf.Abs(rectTransform.rect.width);
|
var higher = Mathf.Abs(totalOffset.y) > Mathf.Abs(rectTransform.rect.height);
|
switch (align)
|
{
|
case Align.Left:
|
dataMaxOffset = longer ? dataMinOffset - new Vector2(totalOffset.x - rectTransform.rect.width, 0) : dataMinOffset;
|
break;
|
case Align.Right:
|
dataMaxOffset = longer ? dataMinOffset + new Vector2(totalOffset.x - rectTransform.rect.width, 0) : dataMinOffset;
|
break;
|
case Align.Top:
|
dataMaxOffset = higher ? dataMinOffset + new Vector2(0, totalOffset.y - rectTransform.rect.height) : dataMinOffset;
|
break;
|
case Align.Bottom:
|
dataMaxOffset = higher ? dataMinOffset - new Vector2(0, totalOffset.y - rectTransform.rect.height) : dataMinOffset;
|
break;
|
}
|
|
if (_stepByStep)
|
{
|
Co_StepByStepAppear();
|
// StartCoroutine("Co_StepByStepAppear");
|
}
|
}
|
|
public void Dispose()
|
{
|
velocity = 0f;
|
StopAllCoroutines();
|
for (int i = 0; i < infiniteItems.Count; i++)
|
{
|
var infiniteItem = infiniteItems[i];
|
if (infiniteItem != null)
|
{
|
infiniteItem.Dispose();
|
}
|
}
|
}
|
|
public void HideAll()
|
{
|
StopAllCoroutines();
|
for (int i = 0; i < content.childCount; i++)
|
{
|
content.GetChild(i).SetActive(false);
|
}
|
}
|
|
private async UniTask Co_StepByStepAppear()
|
{
|
for (int i = 0; i < infiniteItems.Count; i++)
|
{
|
var infiniteItem = infiniteItems[i];
|
if (infiniteItem != null && i < datas.Count)
|
{
|
infiniteItem.SetActive(true);
|
infiniteItem.OpeningShow();
|
await UniTask.Delay(1000);
|
}
|
}
|
}
|
|
public void MoveToCenter(int _dataIndex)
|
{
|
if (_dataIndex < 0 || _dataIndex >= dataCount)
|
{
|
return;
|
}
|
|
switch (align)
|
{
|
case Align.Left:
|
var leftOffsetX = Mathf.Clamp(m_BoundOffset.left + (_dataIndex + 0.5f) * cellSize.x + _dataIndex * spacing.x - rectTransform.rect.width * 0.5f, 0, Mathf.Abs(dataMaxOffset.x));
|
targetOffset = new Vector2(dataMinOffset.x - leftOffsetX, 0);
|
break;
|
case Align.Right:
|
var rightOffsetX = Mathf.Clamp(m_BoundOffset.right + (_dataIndex + 0.5f) * cellSize.x + _dataIndex * spacing.x - rectTransform.rect.width * 0.5f, 0, Mathf.Abs(dataMaxOffset.x));
|
targetOffset = new Vector2(dataMinOffset.x + rightOffsetX, 0);
|
break;
|
case Align.Top:
|
var topOffsetY = Mathf.Clamp(m_BoundOffset.top + (_dataIndex + 0.5f) * cellSize.y + _dataIndex * spacing.y - rectTransform.rect.height * 0.5f, 0, Mathf.Abs(dataMaxOffset.y));
|
targetOffset = new Vector2(0, dataMinOffset.y + topOffsetY);
|
break;
|
case Align.Bottom:
|
var bottomOffsetY = Mathf.Clamp(m_BoundOffset.bottom + (_dataIndex + 0.5f) * cellSize.y + _dataIndex * spacing.y - rectTransform.rect.height * 0.5f, 0, Mathf.Abs(dataMaxOffset.y));
|
targetOffset = new Vector2(0, dataMinOffset.y - bottomOffsetY);
|
break;
|
}
|
|
autoLerp = true;
|
}
|
|
#region Drag Process
|
|
public void OnBeginDrag(PointerEventData eventData)
|
{
|
if (autoLerp || eventData.button != PointerEventData.InputButton.Left)
|
{
|
return;
|
}
|
|
startMousePosition = Vector2.zero;
|
RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, eventData.position, eventData.pressEventCamera, out startMousePosition);
|
prevPosition = startContentPosition = content.anchoredPosition;
|
velocity = 0f;
|
dragging = true;
|
}
|
|
public void OnDrag(PointerEventData eventData)
|
{
|
// if (vnxbqy.UI.NewBieCenter.Instance.inGuiding)
|
// {
|
// return;
|
// }
|
|
if (autoLerp || eventData.button != PointerEventData.InputButton.Left)
|
{
|
return;
|
}
|
|
var localMouse = new Vector2();
|
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, eventData.position, eventData.pressEventCamera, out localMouse))
|
{
|
var pointerDelta = localMouse - startMousePosition;
|
var position = startContentPosition + pointerDelta;
|
SetContentAnchoredPosition(position, align);
|
}
|
}
|
|
public void OnEndDrag(PointerEventData eventData)
|
{
|
if (autoLerp || eventData.button != PointerEventData.InputButton.Left)
|
{
|
return;
|
}
|
|
dragging = false;
|
}
|
#endregion
|
|
private void Update()
|
{
|
if (autoLerp && Vector2.Distance(content.anchoredPosition, targetOffset) > 0.1f)
|
{
|
var newPosition = Vector2.SmoothDamp(content.anchoredPosition, targetOffset, ref refAutoLerpPosition, m_Elasticity, Mathf.Infinity, Time.unscaledDeltaTime);
|
SetContentAnchoredPosition(newPosition, align);
|
}
|
else
|
{
|
autoLerp = false;
|
}
|
}
|
|
protected virtual void LateUpdate()
|
{
|
if (content == null)
|
{
|
return;
|
}
|
|
var deltaTime = Time.unscaledDeltaTime;
|
var offset = CalculateOffset(align);
|
|
if (!dragging && (velocity != 0f || Vector2.SqrMagnitude(offset - Vector2.zero) > 0.1f))
|
{
|
Vector2 position = content.anchoredPosition;
|
if (Vector2.SqrMagnitude(offset - Vector2.zero) > 0.1f)
|
{
|
float speed = velocity;
|
switch (align)
|
{
|
case Align.Left:
|
case Align.Right:
|
position[0] = Mathf.SmoothDamp(content.anchoredPosition[0], content.anchoredPosition[0] - offset.x, ref speed, m_Elasticity, Mathf.Infinity, deltaTime);
|
break;
|
case Align.Bottom:
|
case Align.Top:
|
position[1] = Mathf.SmoothDamp(content.anchoredPosition[1], content.anchoredPosition[1] - offset.y, ref speed, m_Elasticity, Mathf.Infinity, deltaTime);
|
break;
|
}
|
|
if (Mathf.Abs(speed) < 1)
|
{
|
speed = 0f;
|
}
|
velocity = speed;
|
}
|
else
|
{
|
velocity *= Mathf.Pow(m_DecelerationRate, deltaTime);
|
if (Mathf.Abs(velocity) < 50)
|
{
|
velocity = 0;
|
}
|
switch (align)
|
{
|
case Align.Left:
|
case Align.Right:
|
position[0] += velocity * deltaTime;
|
break;
|
case Align.Bottom:
|
case Align.Top:
|
position[1] += velocity * deltaTime;
|
break;
|
}
|
|
}
|
|
SetContentAnchoredPosition(position, align);
|
}
|
|
if (dragging /*&& content.anchoredPosition != prevPosition*/)
|
{
|
var newVelocity = 0f;
|
switch (align)
|
{
|
case Align.Left:
|
case Align.Right:
|
newVelocity = (content.anchoredPosition[0] - prevPosition[0]) / deltaTime;
|
break;
|
case Align.Bottom:
|
case Align.Top:
|
newVelocity = (content.anchoredPosition[1] - prevPosition[1]) / deltaTime;
|
break;
|
}
|
|
velocity = Mathf.Lerp(velocity, newVelocity, deltaTime * 10);
|
prevPosition = content.anchoredPosition;
|
}
|
|
}
|
|
protected virtual void ProcessMoveNext()
|
{
|
ScrollItem lastRect = null;
|
ScrollItem item = null;
|
|
tempList.Clear();
|
for (int i = 0; i < infiniteItems.Count; i++)
|
{
|
tempList.Add(infiniteItems[i]);
|
}
|
if (tempList.Count <= 0)
|
{
|
return;
|
}
|
lastRect = tempList[tempList.Count - 1];
|
for (int i = 0; i < tempList.Count; i++)
|
{
|
item = tempList[i];
|
var able = moveNextable && TestMoveNext(align, item);
|
if (able)
|
{
|
infiniteItems.Remove(item);
|
infiniteItems.Add(item);
|
var offset = CalculateElementOffset(align);
|
item.rectTransform.anchoredPosition = lastRect.rectTransform.anchoredPosition + offset;
|
lastRect = item;
|
|
hostIndex++;
|
preIndex++;
|
item.Dispose();
|
item.LuaDispose();
|
item.Display(datas[hostIndex]);
|
item.LuaDisplay(hostIndex);
|
}
|
else
|
{
|
break;
|
}
|
}
|
}
|
|
protected virtual void ProcessMoveLast()
|
{
|
ScrollItem firstRect = null;
|
ScrollItem item = null;
|
|
tempList.Clear();
|
for (int i = 0; i < infiniteItems.Count; i++)
|
{
|
tempList.Add(infiniteItems[i]);
|
}
|
|
firstRect = tempList[0];
|
for (int i = tempList.Count - 1; i >= 0; i--)
|
{
|
item = tempList[i];
|
var able = moveLastable && TestMoveLast(align, item);
|
if (able)
|
{
|
infiniteItems.Remove(item);
|
infiniteItems.Insert(0, item);
|
var offset = CalculateElementOffset(align);
|
item.rectTransform.anchoredPosition = firstRect.rectTransform.anchoredPosition - offset;
|
firstRect = item;
|
hostIndex--;
|
preIndex--;
|
item.Dispose();
|
item.LuaDispose();
|
item.Display(datas[preIndex]);
|
item.LuaDisplay(preIndex);
|
}
|
else
|
{
|
break;
|
}
|
}
|
}
|
|
private void SetContentAnchoredPosition(Vector2 _position, Align _align)
|
{
|
if (_position == content.anchoredPosition)
|
{
|
return;
|
}
|
|
if (!elasticityOn)
|
{
|
_position.y = Mathf.Clamp(_position.y, dataMinOffset.y, dataMaxOffset.y);
|
_position.x = Mathf.Clamp(_position.x, dataMinOffset.x, dataMaxOffset.x);
|
}
|
|
var offset = _position - content.anchoredPosition;
|
|
switch (_align)
|
{
|
case Align.Left:
|
case Align.Right:
|
_position[1] = content.anchoredPosition[1];
|
content.anchoredPosition = _position;
|
break;
|
case Align.Top:
|
case Align.Bottom:
|
_position[0] = content.anchoredPosition[0];
|
content.anchoredPosition = _position;
|
break;
|
}
|
|
var moveNext = (_align == Align.Left && offset.x < 0f) || (_align == Align.Right && offset.x > 0f) ||
|
(_align == Align.Top && offset.y > 0f) || (_align == Align.Bottom && offset.y < 0f);
|
|
if (moveNext)
|
{
|
ProcessMoveNext();
|
}
|
else
|
{
|
ProcessMoveLast();
|
}
|
}
|
|
private Vector2 CalculateOffset(Align _align)
|
{
|
var offset = Vector2.zero;
|
switch (_align)
|
{
|
case Align.Left:
|
if (content.anchoredPosition.x > dataMinOffset.x)
|
{
|
offset = content.anchoredPosition - dataMinOffset;
|
}
|
else if (content.anchoredPosition.x < dataMaxOffset.x)
|
{
|
offset = content.anchoredPosition - dataMaxOffset;
|
}
|
break;
|
case Align.Right:
|
if (content.anchoredPosition.x < dataMinOffset.x)
|
{
|
offset = content.anchoredPosition - dataMinOffset;
|
}
|
else if (content.anchoredPosition.x > dataMaxOffset.x)
|
{
|
offset = content.anchoredPosition - dataMaxOffset;
|
}
|
break;
|
case Align.Bottom:
|
if (content.anchoredPosition.y > dataMinOffset.y)
|
{
|
offset = content.anchoredPosition - dataMinOffset;
|
}
|
else if (content.anchoredPosition.y < dataMaxOffset.y)
|
{
|
offset = content.anchoredPosition - dataMaxOffset;
|
}
|
break;
|
case Align.Top:
|
if (content.anchoredPosition.y < dataMinOffset.y)
|
{
|
offset = content.anchoredPosition - dataMinOffset;
|
}
|
else if (content.anchoredPosition.y > dataMaxOffset.y)
|
{
|
offset = content.anchoredPosition - dataMaxOffset;
|
}
|
break;
|
}
|
|
return offset;
|
}
|
|
[ContextMenu("Arrange")]
|
public virtual void ReArrange()
|
{
|
velocity = 0f;
|
autoLerp = false;
|
CreateElements();
|
ElementsMatch();
|
Arrange(align);
|
}
|
|
private void CreateElements()
|
{
|
var items = this.content.GetComponentsInChildren<ScrollItem>(true);
|
if (items.Length < m_ChildCount)
|
{
|
var dif = m_ChildCount - items.Length;
|
if (!string.IsNullOrEmpty(m_ChildPrefabName) && !string.IsNullOrEmpty(m_ChildDisplayName))
|
{
|
for (var i = 0; i < dif; i++)
|
{
|
var instance = UIUtility.CreateWidget(m_ChildPrefabName, m_ChildDisplayName);
|
instance.transform.SetParentEx(this.m_Content, Vector3.zero, Quaternion.identity, Vector3.one);
|
}
|
}
|
}
|
}
|
|
private void Arrange(Align _align)
|
{
|
var head = infiniteItems[0];
|
|
var offset1 = Vector2.zero;
|
switch (_align)
|
{
|
case Align.Left:
|
offset1 = new Vector2(-content.rect.width * 0.5f + head.rectTransform.rect.width * 0.5f + m_BoundOffset.left, 0);
|
break;
|
case Align.Right:
|
offset1 = new Vector2(content.rect.width * 0.5f - head.rectTransform.rect.width * 0.5f - m_BoundOffset.right, 0);
|
break;
|
case Align.Top:
|
offset1 = new Vector2(0f, content.rect.height * 0.5f - head.rectTransform.rect.height * 0.5f - m_BoundOffset.top);
|
break;
|
case Align.Bottom:
|
offset1 = new Vector2(0f, -content.rect.height * 0.5f + head.rectTransform.rect.height * 0.5f + m_BoundOffset.bottom);
|
break;
|
}
|
|
head.rectTransform.anchoredPosition = content.anchoredPosition + offset1;
|
|
var offset2 = CalculateElementOffset(_align);
|
for (int i = 1; i < infiniteItems.Count; i++)
|
{
|
var item = infiniteItems[i];
|
item.rectTransform.anchoredPosition = head.rectTransform.anchoredPosition + offset2 * i;
|
}
|
}
|
|
private void ElementsMatch()
|
{
|
if (content == null)
|
{
|
UnityEngine.Debug.Log("Content 不能为空!");
|
return;
|
}
|
|
infiniteItems.Clear();
|
for (int i = 0; i < content.childCount; i++)
|
{
|
var infiniteItem = content.GetChild(i).GetComponent<ScrollItem>();
|
if (infiniteItem != null)
|
{
|
infiniteItems.Add(infiniteItem);
|
infiniteItem.rectTransform.sizeDelta = cellSize;
|
infiniteItem.rectTransform.anchorMax = Vector2.one * 0.5f;
|
infiniteItem.rectTransform.anchorMin = Vector2.one * 0.5f;
|
infiniteItem.rectTransform.pivot = Vector2.one * 0.5f;
|
}
|
}
|
|
content.anchorMax = content.anchorMin = content.pivot = Vector2.one * 0.5f;
|
content.sizeDelta = rectTransform.sizeDelta;
|
content.anchoredPosition = Vector2.zero;
|
}
|
|
private void FillBatchData(int _startIndex)
|
{
|
int min = Mathf.Min(infiniteItems.Count, dataCount);
|
preIndex = Mathf.Clamp(_startIndex, 0, dataCount - min);
|
hostIndex = preIndex + min - 1;
|
|
for (int i = 0; i < infiniteItems.Count; i++)
|
{
|
var item = infiniteItems[i];
|
if (i <= hostIndex - preIndex)
|
{
|
item.SetActive(true);
|
item.Display(datas[preIndex + i]);
|
item.LuaDisplay(preIndex + i);
|
}
|
else
|
{
|
item.SetActive(false);
|
item.Dispose();
|
item.LuaDispose();
|
}
|
}
|
}
|
|
private Vector2 CalculateElementOffset(Align _align)
|
{
|
switch (_align)
|
{
|
case Align.Left:
|
return new Vector2(cellSize.x + spacing.x, 0);
|
case Align.Right:
|
return new Vector2(-cellSize.x - spacing.x, 0);
|
case Align.Top:
|
return new Vector2(0, -cellSize.y - spacing.y);
|
case Align.Bottom:
|
return new Vector2(0, cellSize.y + spacing.y);
|
default:
|
return Vector2.zero;
|
}
|
}
|
|
private bool TestMoveNext(Align _align, ScrollItem _item)
|
{
|
var itemMinPosition = _item.rectTransform.GetMinReferencePosition(rectTransform);
|
var itemMaxPosition = _item.rectTransform.GetMaxReferencePosition(rectTransform);
|
|
switch (_align)
|
{
|
case Align.Left:
|
return itemMaxPosition.x < minOffset.x;
|
case Align.Bottom:
|
return itemMaxPosition.y < minOffset.y;
|
case Align.Right:
|
return itemMinPosition.x > maxOffset.x;
|
case Align.Top:
|
return itemMinPosition.y > maxOffset.y;
|
default:
|
return false;
|
}
|
}
|
|
private bool TestMoveLast(Align _align, ScrollItem _item)
|
{
|
var itemMinPosition = _item.rectTransform.GetMinReferencePosition(rectTransform);
|
var itemMaxPosition = _item.rectTransform.GetMaxReferencePosition(rectTransform);
|
|
switch (_align)
|
{
|
case Align.Left:
|
return itemMinPosition.x > maxOffset.x;
|
case Align.Bottom:
|
return itemMinPosition.y > maxOffset.y;
|
case Align.Right:
|
return itemMaxPosition.x < minOffset.x;
|
case Align.Top:
|
return itemMaxPosition.y < minOffset.y;
|
default:
|
return false;
|
}
|
}
|
|
public enum Align
|
{
|
Left,
|
Right,
|
Top,
|
Bottom,
|
}
|
|
[Serializable]
|
public struct BoundOffset
|
{
|
public float left;
|
public float right;
|
public float top;
|
public float bottom;
|
}
|
|
}
|