思路
如果直接通过Instantiate创建1万item,性能肯定是很糟糕的,所以我们可以只创建几个item,然后在列表拖动的时候修改其anchoredPosition,然后更新item的数据即可。比如:最多只需要展示3条数据,那么我们可以只创建4个item,当往上拖动的时候将最顶端的item位置变换到最底端去,如果是往下拖动,则将最底端的item位置变换到最顶端。通过这种方式可以实现1万条数据的展示,性能消耗降低。
创建ItemRender
首先肯定要为Item创建一个预制体,直接创建一个空的UI,锚点设置为左上角,轴点设置为(0,0)。
为其添加子物体:Image、Text、Button
为其写个脚本:ItemRender,这样就可以在脚本中对单独的item数据初始化了。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class ItemRender : MonoBehaviour
{
private Text m_oText;
private Button chooseBtn;
private string data;
private RectTransform rect;
public Vector2 ItemPosi
{
set
{
rect.anchoredPosition = value;
}
get
{
return rect.anchoredPosition;
}
}
private void Awake()
{
m_oText = this.transform.Find("Text").GetComponent<Text>();
chooseBtn = this.transform.Find("ChooseBtn").GetComponent<Button>();
chooseBtn.onClick.AddListener(OnChooseBtnClickEvent);
rect = this.GetComponent<RectTransform>();
}
// Start is called before the first frame update
void Start()
{
}
public void SetData(string data)
{
this.data = data;
m_oText.text = data;
}
private void OnChooseBtnClickEvent()
{
print(data);
}
}
创建ListView
我们不同ScrollRect,直接创建个空UI,添加组件Rect Mask 2D用于遮罩,Rect Mask 2D不会增加DrawCall,我们的UI主要是矩形,用Rect Mask 2D足够了。
编写脚本:LoopList
using System.Collections;
using System.Collections.Generic;
using System.Data.Common;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
public class LoopList : MonoBehaviour,IDragHandler
{
public float itemHeight;//高度
public float itemPadding;//间隔
public int dataCount;//数据长度
public int itemShowCount;//显示个数
public ItemRender itemRender;//模板
private ItemRender[] items;
private string[] datas;
private float minPosiY;
private float maxPosiY;
private float scrollNum;//拖动的距离(实时计算,用于表示当前的拖动进度)
private float maxScrollNum;//最大拖动距离
private void Awake()
{
items = new ItemRender[itemShowCount];
datas = new string[dataCount];
for(int i =0;i<dataCount;i++)
{
datas[i] = i.ToString();
}
maxScrollNum = dataCount * (itemHeight + itemPadding) - itemHeight * (itemShowCount -1);
}
// Start is called before the first frame update
void Start()
{
for(int i=0;i<itemShowCount;i++)
{
ItemRender item = Instantiate(itemRender,this.transform);
item.ItemPosi = new Vector2(0,-(i+1)*(itemHeight+itemPadding));
items[i] = item;
item.SetData(datas[i]);
}
minPosiY = items[0].ItemPosi.y + (itemHeight + itemPadding);
maxPosiY = items[items.Length - 1].ItemPosi.y;
}
public void OnDrag(PointerEventData eventData)
{
//判断一下是否可以拖动
bool canDrag = (eventData.delta.y < 0 && scrollNum > 0) || (eventData.delta.y > 0 && scrollNum < maxScrollNum);
if(canDrag)
{
float fDeltaY = eventData.delta.y;
scrollNum += fDeltaY;
//限制一下拖动的范围,类似于scrollrect的不可回弹
if(scrollNum<0)
{
fDeltaY -= scrollNum;
scrollNum = 0;
}
else if (scrollNum > maxScrollNum)
{
fDeltaY -= scrollNum - maxScrollNum;
scrollNum = maxScrollNum;
}
for(int i =0;i<items.Length;i++)
{
items[i].ItemPosi += new Vector2(0,fDeltaY);
if (items[i].ItemPosi.y > minPosiY)
{
items[i].ItemPosi += new Vector2(0,-itemShowCount * (itemPadding + itemHeight));
}
else if(items[i].ItemPosi.y < maxPosiY)
{
items[i].ItemPosi += new Vector2(0, itemShowCount * (itemPadding + itemHeight));
}
/*假如item是处于一个高度为dataCount * (itemHeight + itemPadding)的Panel里面,那么通过-items[i].ItemPosi.y + scrollNum可以计算
出当前item的位置,然后除以(itemHeight + itemPadding)可以得出item的索引,因为是从0开始计数的,所以减1*/
int index = (int)Mathf.Round((-items[i].ItemPosi.y + scrollNum)/(itemHeight + itemPadding)) - 1;
if(index >=0 && index < dataCount)
{
items[i].SetData(datas[index]);
}
}
}
}
//跳转到某个索引所在的位置
public void ScrollToIndex(int index)
{
if(itemShowCount >= dataCount)
{
return;
}
//根据index的值,判断是否需要修改一下索引值(比如现在数据总数为50,index为49,显示的数据量为5,那么需要将index修改,47,才不会产生数据溢出)
if(dataCount - index < itemShowCount -1)
{
int offset = itemShowCount -(dataCount - index)-2;
index -= offset;
}
scrollNum = (index - 1) * (itemHeight + itemPadding);//更新一下scrollNum,便于记录滚动的位置
//刷新item
for (int i =0;i<itemShowCount;i++,index ++)
{
items[i].ItemPosi = new Vector2(0,-(i+1) * (itemHeight + itemPadding));
if(index -1 >=0 && index -1 <dataCount)
{
items[i].SetData(datas[index -1]);
}
}
}
}
实现跳转到某一行数据
给画布随便编写个脚本,主要是触发LoopList下的函数ScrollToIndex(int index)。
在画布下创建一个Button,绑定一下点击事件,图省事,我就直接在编辑器里面绑定了
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TestPanel : MonoBehaviour
{
public int scrollIndex = 50;
private LoopList loopList;
// Start is called before the first frame update
void Start()
{
loopList = this.transform.Find("LoopList").GetComponent<LoopList>();
}
public void GotoIndex()
{
loopList.ScrollToIndex(scrollIndex);
}
}
效果
1673430310594