美术需要一个可以无限滑动的列表 然后当前选中的节点需要放大这样的需求.
自己花了点时间研究了下.代码如下
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
public class LoopList : MonoBehaviour, IEndDragHandler, IDragHandler, IBeginDragHandler
{
public RectTransform item;
public RectTransform content;
[Tooltip("初始化时选中的下标")]
public int ViewStartPos = 0;
/// <summary>
/// 速度
/// </summary>
[Tooltip("列表滑动的速度"), Range(0, 1)]
public float MoveSpeed = 0.2f;
/// <summary>
/// 大小的缩放倍数
/// </summary>
[Tooltip("Item的缩放比例 列表中间的Item会被改变大小 就是根据这个的值来的")]
public float Scaling = 1.2f;
/// <summary>
/// 单个Item移动到目标位置需要的时间
/// </summary>
[Tooltip("单个Item的移动时间 从他的原始位置移动到一个位置时需要的时间")]
public float SingleMoveItemTargetTime = 0.5f;
/// <summary>
/// 移动列表时,移动到下一个Item时的移动距离
/// </summary>
[Tooltip("移动列表时,移动到下一个Item时的移动距离")]
public int CanMoveItemOffsetX = 70;
/// <summary>
/// false的情况上下滑动
/// </summary>
[Tooltip("是否是水平滑动")]
public bool IsHorizontal = true;
/// <summary>
/// item的创建数量
/// </summary>
private int CreatCount;
/// <summary>
/// item的总数量
/// </summary>
private int Count;
private List<Item> ItemList = new List<Item>();
/// <summary>创建item后外部初始化item事件</summary>
Action<GameObject, int, int> CallBackList;
/// <summary>玩家滑动到item的回调事件</summary>
Action<GameObject, int, int> CallBackMoveTargetEnd;
private RectTransform Rect;
int LeftIndex;
int RightIndex;
int CenterObjListIndex = 0;
/// <summary>最中心的X数值</summary>
float CenterXPos;
/// <summary>开始缩放的左右X坐标</summary>
float LeftXPos;
float RightXPos;
/// <summary>生成Item宽度的一半用于计算放大系数</summary>
float Offset;
float Epsilon = 0.001f;
/// <summary>创建物体的宽度</summary>
private float ItemSize;
/// <summary>右侧边界位置</summary>
private Vector2 LastPos;
public void Init(int count, Action<GameObject, int, int> callBackList = null, Action<GameObject, int, int> moveTargetEnd = null)
{
Rect = GetComponent<RectTransform>();
Count = count;
CallBackList = callBackList;
CallBackMoveTargetEnd = moveTargetEnd;
CreatItem();
}
bool isCanEndDrag;
private Vector3 dragPos;
private Vector3 beginDragPos;
public void OnBeginDrag(PointerEventData eventData)
{
isCanEndDrag = true;
dragPos = eventData.position;
beginDragPos = eventData.position;
}
public void OnDrag(PointerEventData eventData)
{
if (isMoveing)
{
StopAutoMove();
}
float xMoveDis;
if (IsHorizontal)
xMoveDis = dragPos.x - eventData.position.x;
else
xMoveDis = dragPos.y - eventData.position.y;
MoveList(xMoveDis);
dragPos = eventData.position;
}
public void OnEndDrag(PointerEventData eventData)
{
if (!isCanEndDrag)
{
return;
}
int index = -1;
int objIndex = -1;
float minDis = CenterXPos;
for (int i = 0; i < CreatCount; i++)
{
Item _item = ItemList[i];
float x = GetItemPos(_item);
float dis = Mathf.Abs(x - CenterXPos);
if (dis < minDis)
{
minDis = dis;
index = _item.index;
objIndex = i;
}
}
if (objIndex == CenterObjListIndex)
{
float distance = IsHorizontal ? (eventData.position.x - beginDragPos.x) * MoveSpeed : (eventData.position.y - beginDragPos.y) * MoveSpeed;
if (Mathf.Abs(distance) > CanMoveItemOffsetX && CanMoveItemOffsetX != 0)
{
objIndex = distance > 0 ? objIndex - 1 : objIndex + 1;
index = 0;
}
}
if (objIndex < 0)
{
objIndex = CreatCount - 1;
}
if (objIndex >= CreatCount)
{
objIndex = 0;
}
if (index != -1)
{
MoveToIndex(ItemList[objIndex].index, objIndex);
}
isCanEndDrag = false;
}
/// <summary>
/// 刷新列表 当时数量发生改变时执行这个
/// </summary>
public void ManaRefresh(int count, int moveToIndex = -1, Action<GameObject, int, int> callBack = null)
{
Count = count;
int centerIndex = moveToIndex != -1 ? moveToIndex : ItemList[CenterObjListIndex].index;
float xMin = 0;
float xMax = 0;
for (int i = 0; i < CreatCount; i++)
{
Item _item = ItemList[i];
int offset = centerIndex + (i - 2);
int index = offset;
if (offset <= 0)
{
index = Count + offset;
}
else if (offset > Count)
{
index = offset - Count;
}
_item.index = index;
_item.go.name = i.ToString();
_item.rect.anchoredPosition = new Vector2(offset + ItemSize * i, 0);
_item.go.transform.localScale = Vector3.one;
if (xMin == 0 || _item.rect.anchoredPosition.x < xMin)
{
LeftIndex = _item.index;
xMin = _item.rect.anchoredPosition.x;
}
if (xMax == 0 || _item.rect.anchoredPosition.x > xMax)
{
RightIndex = _item.index;
xMax = _item.rect.anchoredPosition.x;
}
CallBackList?.Invoke(_item.go, index, i);
}
CaluteScaleOffset(true);
callBack?.Invoke(ItemList[CenterObjListIndex].go, ItemList[CenterObjListIndex].index, CenterObjListIndex);
}
public void Refresh(int index = 0)
{
for (int i = 0; i < CreatCount; i++)
{
Item _item = ItemList[i];
if (index != 0 || index == _item.index)
{
CallBackList?.Invoke(_item.go, _item.index, i);
}
}
}
/// <summary>
/// 移动到指定下标
/// </summary>
public void MoveToIndex(int index, int objListIndex = -1)
{
if (isMoveing)
{
return;
}
index = index > Count ? Count : index;
Item _item = ItemList[CenterObjListIndex];
float moveDir = 1;
int rightDisCount = (index - _item.index + Count) % Count;
int leftDisCount = (_item.index - index + Count) % Count;
int moveItemCount = rightDisCount;
if (leftDisCount < rightDisCount)
{
moveDir = -1;
moveItemCount = leftDisCount;
}
if (index == -1)
{
moveDir = -1;
moveItemCount = 1;
}
else if (index == -2)
{
moveDir = 1;
moveItemCount = 1;
}
float pos = CenterXPos - GetItemPos(_item);
float moveDistance = moveItemCount * ItemSize * moveDir - pos;
if (objListIndex >= 0)
{
moveDistance = GetItemPos(ItemList[objListIndex]) - CenterXPos;
}
float dis = Mathf.Abs(pos);
float scaleOffset = Mathf.Abs(_item.rect.transform.localScale.x - Scaling);
if ((_item.index == index && scaleOffset < Epsilon && dis < Epsilon) || Mathf.Abs(moveDistance) < Epsilon)
{
CallBackMoveTargetEnd?.Invoke(_item.go, _item.index, _item.index);
return;
}
float moveTargetTime = SingleMoveItemTargetTime * moveItemCount;
StartAutoMove(moveDistance, moveTargetTime);
}
private bool isMoveing = false;
private float TargetOffsetX;
private float MoveTargetTime;
private float ElapsedTime;
private float MoveDuration;
private float MovedX;
private void Update()
{
if (MoveTargetTime == 0)
return;
if (ElapsedTime < MoveTargetTime)
{
isMoveing = true;
ElapsedTime += Time.deltaTime;
float t = Mathf.Clamp01(ElapsedTime / MoveTargetTime);
MoveDuration = Mathf.Lerp(0, TargetOffsetX, t);
MoveList(MoveDuration - MovedX);
MovedX = MoveDuration;
}
else
{
StopAutoMove();
CallBackMoveTargetEnd?.Invoke(ItemList[CenterObjListIndex].go, ItemList[CenterObjListIndex].index, CenterObjListIndex);
}
}
private void StartAutoMove(float targetOffsetX, float moveTargetTime)
{
TargetOffsetX = targetOffsetX / MoveSpeed;
if (moveTargetTime == 0 && targetOffsetX != 0)
MoveTargetTime = SingleMoveItemTargetTime;
else
MoveTargetTime = moveTargetTime;
}
private void StopAutoMove()
{
TargetOffsetX = 0;
MoveTargetTime = 0;
ElapsedTime = 0;
MoveDuration = 0;
MovedX = 0;
isMoveing = false;
}
//移动列表
void MoveList(float x)
{
x = -x * MoveSpeed;
if (x == 0)
{
return;
}
bool refreshItem = false;
if (IsHorizontal)
{
for (int i = 0; i < CreatCount; i++)
{
Item _item = ItemList[i];
Vector2 pos = new Vector2(_item.rect.anchoredPosition.x + x, 0);
if (pos.x < 0)
{
//把它放到最右边 (防止单次偏移过大进行额外计算 如 -4 这种值 直接放到该处会导致节点偏移)
pos += LastPos;
int index = (RightIndex + 1) % Count;
if (++LeftIndex == Count)
LeftIndex = 0;
RightIndex = index;
_item.index = index;
refreshItem = true;
_item.go.gameObject.SetActive(true);
int centerObjIndex = CenterObjListIndex + 1;
centerObjIndex = centerObjIndex > (CreatCount - 1) ? 0 : centerObjIndex;
CenterObjListIndex = centerObjIndex;
}
else if (pos.x > LastPos.x)
{
//把它放到最左边
pos -= LastPos;
int index = LeftIndex - 1 < 0 ? Count - 1 : --LeftIndex;
if (--RightIndex < 0)
RightIndex = Count - 1;
LeftIndex = index;
_item.index = index;
refreshItem = true;
_item.go.gameObject.SetActive(true);
int centerObjIndex = CenterObjListIndex - 1;
centerObjIndex = centerObjIndex < 0 ? CreatCount - 1 : centerObjIndex;
CenterObjListIndex = centerObjIndex;
}
_item.rect.anchoredPosition = pos;
if (refreshItem)
{
refreshItem = false;
CallBackList?.Invoke(_item.go, _item.index, i);
}
}
}
else
{
for (int i = 0; i < CreatCount; i++)
{
Item _item = ItemList[i];
Vector2 pos = new Vector2(0, _item.rect.anchoredPosition.y + x);
if (pos.y < 0)
{
//把它放到最上边
pos += LastPos;
int index = (RightIndex + 1) % Count;
if (++LeftIndex == Count)
LeftIndex = 0;
RightIndex = index;
_item.index = index;
refreshItem = true;
_item.go.gameObject.SetActive(true);
int centerObjIndex = CenterObjListIndex + 1;
centerObjIndex = centerObjIndex > (CreatCount - 1) ? 0 : centerObjIndex;
CenterObjListIndex = centerObjIndex;
}
else if (pos.y > LastPos.y)
{
//把它放到最下边
pos -= LastPos;
int index = LeftIndex - 1 < 0 ? Count - 1 : --LeftIndex;
if (--RightIndex < 0)
RightIndex = Count - 1;
LeftIndex = index;
_item.index = index;
refreshItem = true;
_item.go.gameObject.SetActive(true);
int centerObjIndex = CenterObjListIndex - 1;
centerObjIndex = centerObjIndex < 0 ? CreatCount - 1 : centerObjIndex;
CenterObjListIndex = centerObjIndex;
}
_item.rect.anchoredPosition = pos;
if (refreshItem)
{
refreshItem = false;
CallBackList?.Invoke(_item.go, _item.index, i);
}
}
}
CaluteScaleOffset();
}
/// <summary>
/// 立即移动到指定位置 没有过程
/// </summary>
/// <param name="index"></param>
public void MoveToIndexImmediately(int index)
{
int startIndex = index - CreatCount / 2;
if (startIndex < 0)
startIndex += Count;
for (int i = 0; i < ItemList.Count; i++)
{
int _index = (startIndex + i) % Count;
var _item = ItemList[i];
_item.rect.anchoredPosition = IsHorizontal ? new Vector2(Offset + ItemSize * i, 0) : new Vector2(0, Offset + ItemSize * i); ;
_item.index = _index;
CallBackList?.Invoke(_item.go, _item.index, i);
}
LeftIndex = ItemList[0].index;
RightIndex = ItemList[CreatCount - 1].index;
CaluteScaleOffset(true);
}
/// <summary>
/// 计算缩放
/// </summary>
private void CaluteScaleOffset(bool isInit = false)
{
for (int i = 0; i < CreatCount; i++)
{
Item _item = ItemList[i];
float x = GetItemPos(_item);
float dis;
float offset;
if (x >= CenterXPos)
{
if (RightXPos - x <= 0)
{
dis = 0;
}
else
{
offset = RightXPos - x;
dis = offset / Offset;
}
}
else
{
if (x - LeftXPos <= 0)
{
dis = 0;
}
else
{
offset = x - LeftXPos;
dis = offset / Offset;
}
}
Vector3 newScale = Vector3.one + (Vector3.one * Scaling - Vector3.one) * dis;
_item.rect.localScale = newScale;
float offsetDis = Mathf.Abs(1 - dis);
if (offsetDis < Epsilon || dis == 1)
{
if (isInit)
{
CallBackMoveTargetEnd?.Invoke(_item.go, _item.index, i + 1);
}
CenterObjListIndex = i;
}
}
}
void CreatItem()
{
if (Count == 0)
{
item.gameObject.SetActive(false);
return;
}
ItemSize = IsHorizontal ? item.rect.width : item.rect.height;
int count = (int)(IsHorizontal ? Rect.rect.width / ItemSize : Rect.rect.height / ItemSize);
if (count > Count)
count = Count;
CreatCount = (count % 2 != 0) ? count : count + 1;
CreatCount += 2;
ItemList.Clear();
int startIndex = ViewStartPos - CreatCount / 2;
if (startIndex < 0)
startIndex += Count;
Vector2 centerPos = new Vector2(0.5f, 0.5f);
content.anchorMin = centerPos;
content.anchorMax = centerPos;
content.pivot = centerPos;
if (IsHorizontal)
content.sizeDelta = new Vector2(ItemSize * CreatCount, Rect.sizeDelta.y);
else
content.sizeDelta = new Vector2(Rect.sizeDelta.x, ItemSize * CreatCount);
content.anchoredPosition = Vector2.zero;
Vector2 anchors;
if (IsHorizontal)
{
LastPos = new Vector2(ItemSize * CreatCount, 0);
anchors = new Vector2(0, 0.5f);
}
else
{
LastPos = new Vector2(0, ItemSize * CreatCount);
anchors = new Vector2(0.5f, 0);
}
Offset = ItemSize / 2;
CenterXPos = ItemSize * (CreatCount * 0.5f);
LeftXPos = CenterXPos - Offset;
RightXPos = CenterXPos + Offset;
RectTransform rect = item.GetComponent<RectTransform>();
rect.anchorMin = anchors;
rect.anchorMax = anchors;
rect.pivot = centerPos;
for (int i = 0; i < CreatCount; i++)
{
Transform children = i < content.transform.childCount ? content.transform.GetChild(i) : null;
GameObject go = children != null ? children.gameObject : GameObject.Instantiate(item.gameObject);
RectTransform goRect = go.GetComponent<RectTransform>();
go.transform.SetParent(content.transform);
goRect.anchoredPosition = IsHorizontal ? new Vector2(Offset + ItemSize * i, 0) : new Vector2(0, Offset + ItemSize * i);
go.name = i.ToString();
int index = (startIndex + i) % Count;
Item _item = new Item()
{
go = go,
rect = goRect,
index = index
};
CallBackList?.Invoke(go, _item.index, i);
ItemList.Add(_item);
}
LeftIndex = ItemList[0].index;
RightIndex = ItemList[CreatCount - 1].index;
CaluteScaleOffset(true);
}
private float GetItemPos(Item item)
{
return IsHorizontal ? item.rect.anchoredPosition.x : item.rect.anchoredPosition.y;
}
class Item
{
public GameObject go;
public RectTransform rect;
public int index;
}
}
使用方法为 创建一个Scroll Rect 然后把ViewPort拖出来. Scroll Rect就可以删除了.然后给ViewProt添加该脚本并把Content拖到参数栏中.最后把需要生成的item也拖到参数栏的Item中,后续的拖动手感可以根据提供的参数自行调整.
如果使用上有问题,或者觉得有更好的方式也欢迎大家来讨论
PS:需要在代码中其他地方调用下该组件的Init方法