之前文章有,简单写一下
新建scroll view,,,取消水平滑动
将viewport-》content修改至如下面版,间距可以自己调
在content下新建image,调至合适大小,做成预制体,注意观察预制体的rectTransfrom有没有变动,有的话改一下
public void GetWorldCorners(Vector3[] fourCornersArray);
可以得到ui的recttransfrom的矩形区域的4个点坐标
transfrom.childCount
得到该物体下子物体的个数
gameObject.activeSelf
该物体是否激活
obj.transform.SetAsFirstSibling();
将obj物体设为父目录下的第一个
transform.localPosition
是相对于父物体的坐标
无限循环,比如我们100个item页面,就实例化100个物体,那性能肯定直线下降,item是image
结果:当1000个item页面,我们实例化的物体都是固定的
比如1个content里有5个item子页面,当往下滑的时候,在第5个下面创建第6个item,而第一个item在慢慢的被隐藏,当第一个被隐藏时,而是将第一个item作为第7个的item,而不是重新创建。
总之来说:头部隐藏,加尾部。尾部隐藏,加头部。(核心)
好的,你已经学会了,哈哈哈
在scrollView上挂载LoopScrollView脚本其它脚本不用挂载
loopItem脚本:当的item页面在content对应位置所做的4种操作,这个脚本是在子物体身上的
一张图:
LoopScrollView脚本:主要是4种操作的功能实现以及数初始化
DataAdapter脚本:获取数据和移除数据
LoopDataItem脚本:存数据
代码较多,量力而行
LoopItem.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class LoopItem : MonoBehaviour
{
private RectTransform rect;
private RectTransform parent;
private Vector3[] rectCorners;
private Vector3[] parentCorners;
public Action onAddHead;
public Action onRemoveHead;
public Action onAddEnd;
public Action onRemoveEnd;
// Start is called before the first frame update
void Start()
{
rect = transform.GetComponent<RectTransform>();
parent = transform.GetComponentInParent<ScrollRect>().GetComponent<RectTransform>();
if(parent==null)
{
throw new Exception("找不到父亲");
}
rectCorners = new Vector3[4];
parentCorners = new Vector3[4];
}
private void Update()
{
LinstenerCorner();
}
// Update is called once per frame
public void LinstenerCorner()
{
rect.GetWorldCorners(rectCorners);
parent.GetWorldCorners(parentCorners);
//头部
if(isFirst())
{
//添加
if(rectCorners[1].y < parentCorners[1].y)
{
//onaddhead不为空则调用
onAddHead?.Invoke();
}
//去除
if(rectCorners[0].y > parentCorners[1].y)
{
onRemoveHead?.Invoke();
}
}
//尾部
if(isLast())
{
//去除
if (rectCorners[1].y < parentCorners[0].y)
{
onRemoveEnd?.Invoke();
}
//添加
if (rectCorners[0].y > parentCorners[0].y)
{
onAddEnd?.Invoke();
}
}
}
public bool isFirst()
{
for (int i = 0; i < transform.parent.childCount; i++)
{
if(transform.parent.GetChild(i).gameObject.activeSelf)
{
//第一个是不是自己
if(transform.parent.GetChild(i)==transform)
return true;
break;
}
}
return false;
}
public bool isLast()
{
for (int i = transform.parent.childCount - 1; i >= 0; i--)
{
if (transform.parent.GetChild(i).gameObject.activeSelf)
{
//最后一个
if (transform.parent.GetChild(i) == transform)
return true;
break;
}
}
return false;
}
}
LoopScrollView.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class LoopScrollView : MonoBehaviour
{
public GameObject childItem;
private RectTransform content;
private GridLayoutGroup grid;
private ContentSizeFitter sizeFitter;
private DataAdapter<LoopDataItem> dataAdapter;
// Start is called before the first frame update
private void Awake()
{
content = transform.Find("Viewport/Content").GetComponent<RectTransform>();
if (content == null)
{
throw new System.Exception("没找到content");
}
grid = content.GetComponent<GridLayoutGroup>();
sizeFitter = content.GetComponent<ContentSizeFitter>();
dataAdapter = new DataAdapter<LoopDataItem>();
//测试数据
List<LoopDataItem> loopDatas = new List<LoopDataItem>();
for (int i = 0; i < 100; i++)
{
loopDatas.Add(new LoopDataItem(i));
}
dataAdapter.InitData(loopDatas);
}
void Start()
{
OnAddHead();
//延迟
Invoke("EnableFalseGrid", 0.1f);
}
// Update is called once per frame
void Update()
{
}
//获取子节点
public GameObject GetChildItem()
{
//有则显示
for (int i = 0; i < content.childCount; i++)
{
if(!content.GetChild(i).gameObject.activeSelf)
{
content.GetChild(i).gameObject.SetActive(true);
return content.GetChild(i).gameObject;
}
}
//没有则实例化,父物体为content下创建
GameObject obj = GameObject.Instantiate(childItem, content.transform);
LoopItem loopItem = obj.AddComponent<LoopItem>();
loopItem.onAddHead += OnAddHead;
loopItem.onRemoveHead += OnRemoveHead;
loopItem.onAddEnd += OnAddLast;
loopItem.onRemoveEnd += OnRemoveLast;
return obj;
}
public void OnAddHead()
{
LoopDataItem loopDataItem = dataAdapter.GetHeadData();
if(loopDataItem!=null)
{
Transform first = FindFirst();
GameObject obj = GetChildItem();
//设为子物体第一个
obj.transform.SetAsFirstSibling();
//设置数据
SetData(obj, loopDataItem);
if (first != null)
{
//print(first.localPosition);
obj.transform.localPosition = first.localPosition + new Vector3(0, grid.cellSize.y + grid.spacing.y, 0);
}
}
}
public void OnRemoveHead()
{
if(dataAdapter.RemoveHeadData())
{
Transform first = FindFirst();
if (first != null)
{
first.gameObject.SetActive(false);
}
}
}
public void OnAddLast()
{
LoopDataItem loopDataItem = dataAdapter.GetLastData();
if (loopDataItem != null)
{
Transform last = FindLast();
GameObject obj = GetChildItem();
//设为子物体第一个
obj.transform.SetAsLastSibling();
SetData(obj, loopDataItem);
if (last != null)
{
obj.transform.localPosition = last.localPosition - new Vector3(0, grid.cellSize.y + grid.spacing.y, 0);
}
//增加conten高度
if (IsNeedAddContentHeight(obj.transform))
{
content.sizeDelta += new Vector2(0, grid.cellSize.y + grid.spacing.y);
}
}
}
public void OnRemoveLast()
{
if(dataAdapter.RemoveLastData())
{
Transform last = FindLast();
if (last != null)
{
last.gameObject.SetActive(false);
}
}
}
public Transform FindFirst()
{
for (int i = 0; i < content.childCount; i++)
{
if(content.GetChild(i).gameObject.activeSelf)
{
return content.GetChild(i);
}
}
return null;
}
public Transform FindLast()
{
for (int i = content.childCount - 1; i >= 0; i--)
{
if (content.GetChild(i).gameObject.activeSelf)
{
return content.GetChild(i);
}
}
return null;
}
public void EnableFalseGrid()
{
grid.enabled = false;
sizeFitter.enabled = false;
}
//是否下拉提升content高度
public bool IsNeedAddContentHeight(Transform trans)
{
Vector3[] rectCorners = new Vector3[4];
Vector3[] contentCorners = new Vector3[4];
trans.GetComponent<RectTransform>().GetWorldCorners(rectCorners);
content.GetWorldCorners(contentCorners);
if(rectCorners[0].y<contentCorners[0].y)
{
return true;
}
return false;
}
//设置数据
public void SetData(GameObject obj,LoopDataItem data)
{
obj.transform.Find("Text").GetComponent<Text>().text = data.id.ToString();
}
}
DataAdapter.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DataAdapter<T>
{
private List<T> allContent=new List<T>();
private LinkedList<T> currentContent=new LinkedList<T>();
public void InitData(T[] t)
{
allContent.Clear();
currentContent.Clear();
allContent.AddRange(t);
}
public void InitData(List<T> t)
{
InitData(t.ToArray());
}
public void AddData(T[] t)
{
allContent.AddRange(t);
}
public void AddData(List<T> t)
{
AddData(t.ToArray());
}
public T GetHeadData()
{
if(allContent.Count==0)
{
return default;
}
if(currentContent.Count==0)
{
T head = allContent[0];
currentContent.AddFirst(head);
return head;
}
//获取当前第一个数据的上一个
T t = currentContent.First.Value;
int index = allContent.IndexOf(t);
if(index!=0)
{
T head = allContent[index - 1];
//加到当前显示数据中
currentContent.AddFirst(head);
return head;
}
return default(T);
}
//移除当前数据的第一个
public bool RemoveHeadData()
{
//等于1的时候也不能移除,不然移除后,界面没有东西就无法加头和加尾了
if(currentContent.Count!=0&¤tContent.Count!=1)
{
currentContent.RemoveFirst();
return true;
}
return false;
}
//获取当前数据最后一个的下一个
public T GetLastData()
{
if(allContent.Count==0)
{
return default;
}
if(currentContent.Count==0)
{
T last = allContent[0];
currentContent.AddLast(last);
return last;
}
T t = currentContent.Last.Value;
int index = allContent.IndexOf(t);
if(index!=allContent.Count-1)
{
T last = allContent[index + 1];
currentContent.AddLast(last);
return last;
}
return default;
}
//移除当前数据的最后一个
public bool RemoveLastData()
{
if(currentContent.Count!=0&¤tContent.Count!=1)
{
currentContent.RemoveLast();
return true;
}
return false;
}
}
LoopDataItem.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class LoopDataItem
{
public int id;
public LoopDataItem(int id)
{
this.id = id;
}
}