using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
public struct Padding
{
public int top, bottom, left, right;
}
public struct Cell
{
public int number;
public GameObject go;
}
public class macOsDocker : MonoBehaviour,IPointerMoveHandler,IPointerExitHandler
{
public ScrollRect scrollRect;
public List<Transform> listItems = new List<Transform>();
[Header("半轴的长度(世界坐标)")]
public float halfRange;
public float yMaxDis;
public float maxAddScale;
public RectTransform contentTrans;
public RectTransform childParentTrans;
private Padding offsetPadding;
public LayoutGroup layoutGroup;
public RectTransform viewportRectTransform;
private List<Cell> currentCells = new List<Cell>();
private List<MacOsCell> unRegularCellList = new List<MacOsCell>();
private List<GameObject> cachedCells = new List<GameObject>();
private int currentFirstCol;
private int currentLastCol;
private float cellX;
private float spacingX;
[Header("不够的时候用什么预制体创建")]
public GameObject cellPrefab;
[Header("模拟多少个总数数量")]
public int totalCount;
public int FirstCol
{
get
{
return GetColByX(-contentTrans.anchoredPosition.x - offsetPadding.left);
}
}
private GameObject pendingDestroyGo;
private void GetListItems()
{
listItems.Clear();
for (int i = 0; i < transform.childCount; i++)
{
Transform child = transform.GetChild(i);
if(child.gameObject.activeSelf)
{
listItems.Add(child);
}
}
}
public int GetColByX(float scrollLeftX)
{
if (unRegularCellList.Count <= 0)
{
var col = (int)(scrollLeftX / (cellX + spacingX));
return Mathf.Clamp(col, 0, totalCount - 1);
}
else
{
var col = (int)(scrollLeftX / (cellX + spacingX));
var begin = unRegularCellList[0].index;
if (col <= begin)
{
return Mathf.Clamp(col,0,totalCount-1);
}
else
{
var beginX = begin*(cellX+spacingX);
for (int i = 0; i < unRegularCellList.Count; i++)
{
var cell = unRegularCellList[i];
beginX += (cell.GetRealWidth()+spacingX);
if (beginX > scrollLeftX)
{
return begin + i;
}
}
col = begin + unRegularCellList.Count;
var remainCol = (int)((scrollLeftX - beginX) / (cellX + spacingX));
col += remainCol;
return Mathf.Clamp(col,0,totalCount-1);
}
}
}
public int LastCol
{
get
{
return GetColByX(-contentTrans.anchoredPosition.x + ViewportWidth - offsetPadding.left);
}
}
public float ContentWidth
{
get => contentTrans.rect.width;
private set => contentTrans.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, value);
}
public float ViewportWidth => viewportRectTransform.rect.width;
private void Reset()
{
scrollRect = GetComponentInParent<ScrollRect>();
contentTrans = GetComponent<RectTransform>();
childParentTrans = contentTrans.GetChild(0) as RectTransform;
layoutGroup = GetComponent<LayoutGroup>();
viewportRectTransform = scrollRect.viewport;
}
private void Start()
{
var rect = cellPrefab.GetComponent<RectTransform>().rect;
cellX = rect.width;
spacingX = ((HorizontalOrVerticalLayoutGroup)layoutGroup).spacing;
scrollRect.onValueChanged.AddListener(OnScroll);
offsetPadding = new Padding
{
top = layoutGroup.padding.top,
bottom = layoutGroup.padding.bottom,
left = layoutGroup.padding.left,
right = layoutGroup.padding.right,
};
contentTrans.anchorMin = Vector2.up;
contentTrans.anchorMax = Vector2.up;
contentTrans.anchoredPosition = Vector2.zero;
ContentWidth = cellX * totalCount + spacingX * (totalCount - 1) + offsetPadding.left + offsetPadding.right;
pendingDestroyGo = new GameObject("[Cache Node]");
pendingDestroyGo.transform.SetParent(transform);
pendingDestroyGo.transform.localScale = Vector3.one;
pendingDestroyGo.SetActive(false);
//根据totalCount创建
FirstGenerate();
GetListItems();
}
private void FirstGenerate()
{
currentFirstCol = FirstCol;
currentLastCol = LastCol;
layoutGroup.padding.left = offsetPadding.left + (currentFirstCol == 0
? 0
: (int)(currentFirstCol * cellX + (currentFirstCol - 1) * spacingX));
layoutGroup.padding.right = offsetPadding.right + (int)((totalCount - LastCol - 1) * (cellX + spacingX));
for (var c = currentFirstCol; c <= currentLastCol; ++c)
{
var index = c;
if (index >= totalCount) continue;
GenerateCell(index);
}
}
private int GetFirstGreater(int index)
{
var start = 0;
var end = currentCells.Count;
while (start != end)
{
var middle = start + (end - start) / 2;
if (currentCells[middle].number <= index)
{
start = middle + 1;
}
else
{
end = middle;
}
}
return start;
}
private void GenerateCell(int index)
{
MacOsCell iCell;
GameObject instance = null;
if (cachedCells.Count>0)
{
instance = cachedCells[0];
instance.transform.SetParent(childParentTrans);
cachedCells.RemoveAt(0);
iCell = instance.GetComponent<MacOsCell>();
}
else
{
instance = Instantiate(cellPrefab, childParentTrans);
iCell = instance.GetComponent<MacOsCell>();
}
iCell.index = index;
var order = GetFirstGreater(index);
instance.transform.SetSiblingIndex(order);
var cell = new Cell { go = instance, number = index };
currentCells.Insert(order, cell);
iCell.gameObject.SetActive(true);
iCell.SetData();
}
private void DestroyCell(int index)
{
var order = GetFirstGreater(index - 1);
var cell = currentCells[order];
currentCells.RemoveAt(order);
cell.go.SetActive(false);
cell.go.transform.SetParent(pendingDestroyGo.transform);
cachedCells.Add(cell.go);
}
private void GenerateCol(int col,bool onLeft)
{
var index = col;
if (index >= totalCount) return;
GenerateCell(index);
if (onLeft) layoutGroup.padding.left -= (int)(cellX + spacingX);
else layoutGroup.padding.right -= (int)(cellX + spacingX);
}
private void DestroyCol(int col, bool onLeft)
{
var index = col;
if (index >= totalCount) return;
DestroyCell(index);
if (onLeft) layoutGroup.padding.left += (int)(cellX + spacingX);
else layoutGroup.padding.right += (int)(cellX + spacingX);
}
private bool isDraging = false;
private void RecalcContentWidthBaseRegularCellList()
{
int beginIndex = currentCells[0].number;
float width = beginIndex * cellX + (beginIndex - 1) * spacingX;
for (int i = 0; i < currentCells.Count; i++)
{
MacOsCell cell = currentCells[i].go.GetComponent<MacOsCell>();
width += cell.GetRealWidth();
RectTransform child = currentCells[i].go.transform.GetChild(0) as RectTransform;
if (child.anchoredPosition.y> 0)
{
unRegularCellList.Add(cell);
}
}
int remainCount = totalCount - beginIndex - currentCells.Count;
width += (remainCount * cellX + (remainCount - 1) * spacingX);
width = width + offsetPadding.left + offsetPadding.right;
ContentWidth = width;
}
private void OnBeginScroll()
{
isDraging = true;
RecalcContentWidthBaseRegularCellList();
}
private void OnEndScroll()
{
//Debug.LogError($"结束滑动");
isDraging = false;
GetListItems();
}
void LateUpdate()
{
if(isDraging)
{
if (Input.GetMouseButtonUp(0))
{
OnEndScroll();
}
}
}
private void OnScroll(Vector2 position)
{
if(!isDraging)
{
//开始滑动
OnBeginScroll();
}
GetListItems();
if (currentFirstCol > FirstCol)
{
// new left col
for (var col = currentFirstCol - 1; col >= FirstCol; --col)
{
GenerateCol(col, true);
}
currentFirstCol = FirstCol;
}
if (currentLastCol < LastCol)
{
// new right col
for (var col = currentLastCol + 1; col <= LastCol; ++col)
{
GenerateCol(col, false);
}
currentLastCol = LastCol;
}
if (currentFirstCol < FirstCol)
{
// left col invisible
for (var col = currentFirstCol; col < FirstCol; ++col)
{
DestroyCol(col, true);
}
currentFirstCol = FirstCol;
}
if (currentLastCol > LastCol)
{
// right col invisible
for (var col = currentLastCol; col > LastCol; --col)
{
DestroyCol(col, false);
}
currentLastCol = LastCol;
}
}
public void OnPointerExit(PointerEventData eventData)
{
for (int i = 0; i < listItems.Count; i++)
{
listItems[i].transform.localScale = Vector3.one;
RectTransform childRect = listItems[i].GetChild(0) as RectTransform;
childRect.anchoredPosition = new Vector3(childRect.anchoredPosition.x, 0, 0);
}
}
public void OnPointerMove(PointerEventData eventData)
{
float wPointx = eventData.pointerCurrentRaycast.worldPosition.x;
float leftX = wPointx - halfRange;
float rightX = wPointx + halfRange;
for (int i = 0; i < listItems.Count; i++)
{
var tran = listItems[i];
if (tran.position.x<leftX || tran.position.x>rightX)
{
tran.localScale = Vector3.one;
RectTransform childRect = tran.GetChild(0) as RectTransform;
childRect.anchoredPosition = new Vector3(childRect.anchoredPosition.x,0,0);
}
else
{
float rate = (tran.position.x - leftX) / (halfRange * 2);
float sinValue = Mathf.Sin(rate*Mathf.PI);
float rate2 = 1 + sinValue * maxAddScale;
tran.localScale = Vector3.one * rate2;
RectTransform childRect = tran.GetChild(0) as RectTransform;
childRect.anchoredPosition = new Vector3(childRect.anchoredPosition.x, sinValue*yMaxDis, 0);
}
}
RectTransform rect = transform as RectTransform;
LayoutRebuilder.ForceRebuildLayoutImmediate(rect);
RecalcContentWidthBaseRegularCellList();
}
}
要支持10000个数据,必须要使用无尽的重用item的循环列表,但是由于有缩放关系item是不规则尺寸,真实尺寸是item size*缩放,当锚点在左上角的时候滑动scroll rect就是滑动content,content在左侧的x坐标就是相反数就是往左滑动的距离,距离/(cell Size)就是跳过的item数量可以求出当前列表最左边对应数据的index的起点多少,同理求出终点,然后设置数据,由于跟随鼠标的不规则尺寸问题,content的size要滑动的时候要实时计算,拖动是监听scroll rect的onvaluechanged,ondrag和onpointmove是两个事件根据事件上报可以孩子没有实现可以交给父亲处理。
预览视频:https://www.bilibili.com/video/BV1z7vUeEE7t/?vd_source=67191c5099647637079847e343544b3f