主要实现的是无限滚动功能(重点实现开始的自动添加与超出边界外的自动删除)如图gif.。
脚本的挂载与预制体的拖放如下图所示:
LoopScrollView脚本如下:
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public enum LoopScrollViewType { Horizontal, Vertical } public class LoopScrollView : MonoBehaviour { #region 字段 //这是一个预制体,通过对它(GameObject)实例化来创建它的子物体 public GameObject childItemPrefab; public LoopScrollViewType scrollViewType = LoopScrollViewType.Vertical; private GridLayoutGroup contentLayoutGroup; private ContentSizeFitter sizeFitter; private RectTransform content; private DataApdater<LoopDataItem> dataApdater; #endregion #region Unity回调 //对字段进行初始化 private void Awake() { Init(); } private void Start() { //开始起到排序的作用 contentLayoutGroup.enabled = true; sizeFitter.enabled = true; //添加一个子节点 OnAddHead(); //禁用,在0.1秒后开始禁用 Invoke("EnableFalseGrid", 0.1f); } #endregion #region 方法 //初始化 public void Init() { content = transform.Find( "Viewport/Content").GetComponent<RectTransform>(); if (content == null) { throw new System.Exception(" content 初始化失败"); } contentLayoutGroup = content.GetComponent<GridLayoutGroup>(); if (contentLayoutGroup == null) { throw new System.Exception(" contentLayoutGroup 初始化失败"); } //判断是否获取到,若没有抛出异常 sizeFitter = content.GetComponent<ContentSizeFitter >(); if (sizeFitter == null) { throw new System.Exception(" sizeFitter 初始化失败"); } dataApdater = new DataApdater<LoopDataItem>(); //-----------------------------------测试模拟的数据------------------------------------------- List<LoopDataItem> loopDataItems = new List<LoopDataItem>(); for (int i = 0; i < 100; i++) { loopDataItems.Add(new LoopDataItem(i)); } dataApdater.InitData(loopDataItems); //初始化数据,将loopDataItems传递过去 //-------------------------------------------------------------------------------------- } //获取一个子节点 public GameObject GetChildItem() { //此原理为 对象池原理 //查找有没有 被回收 的子节点 (active 为 false 就是有被回收的子节点 ???) for (int i = 0; i < content.childCount ; i++) //for循环遍历出所有的子节点,判断所有的子节点有没有被回收 { if (!content.GetChild(i).gameObject.activeSelf) //!取反 所以现在状态是隐藏状态 { content.GetChild(i).gameObject.SetActive(true); return content.GetChild(i).gameObject; } } //如果没有 创建一个 GameObject childItem = GameObject.Instantiate(childItemPrefab, content.transform); //实例化一个游戏物体 子物体是childItemPrefab 父物体是content.transform childItem.transform.localScale = Vector3.one; //简单设置数据 大小、位置 childItem.transform.localPosition = Vector3.zero; childItem.GetComponent<RectTransform>().anchorMin = new Vector2(0, 1); //设置锚点 childItem.GetComponent<RectTransform>().anchorMax = new Vector2(0, 1); childItem.GetComponent<RectTransform>().sizeDelta = contentLayoutGroup.cellSize; //子物体 设置宽高 LoopItem loopItem = childItem.AddComponent<LoopItem>(); loopItem.onAddHead += this.OnAddHead; //??? loopItem.onRemoveHead += this.OnRemoveHead; loopItem.onAddLast += this.OnAddLast; loopItem.onRemoveLast += this.OnRemoveLast; loopItem.SetLoopScrollViewType(this.scrollViewType); return childItem ; } //在上面添加一个物体 public void OnAddHead() { LoopDataItem loopDataItem = dataApdater.GetHeaderData(); //获取同步数据 if ( loopDataItem != null ) { Debug.Log("添加头"); Transform first = FindFirst(); GameObject obj = GetChildItem(); //获取到一个子节点 obj.transform.SetAsFirstSibling(); //把它放在第一个位置 SetData(obj, loopDataItem); //设置数据 //动态设置位置 if (first != null) { switch (scrollViewType) { case LoopScrollViewType.Horizontal: obj.transform.localPosition = first.localPosition - new Vector3(contentLayoutGroup.cellSize.x + contentLayoutGroup.spacing.x, 0 , 0); break; case LoopScrollViewType.Vertical: obj.transform.localPosition = first.localPosition + new Vector3(0, contentLayoutGroup.cellSize.y + contentLayoutGroup.spacing.y, 0); break; } } } } //移除当前最上面的物体 public void OnRemoveHead() { Debug.Log("移除头"); if (dataApdater.RemoveHeadData()) { Transform first = FindFirst(); if (first != null) { first.gameObject.SetActive(false); } } } //在最后加一个物体 public void OnAddLast() { LoopDataItem loopDataItem = dataApdater.GetLastData(); //获取尾部数据 if (loopDataItem != null) { Debug.Log("添加尾"); Transform last = FindLast(); GameObject obj = GetChildItem(); //获取到一个子节点 obj.transform.SetAsLastSibling(); //把它放在第一个位置 SetData(obj, loopDataItem); //设置数据 switch (scrollViewType) { case LoopScrollViewType.Horizontal: if (last != null) //动态设置位置 { obj.transform.localPosition = last.localPosition + new Vector3(contentLayoutGroup.cellSize.x + contentLayoutGroup.spacing.x, 0 , 0); } //要不要增加高度 if (IsNeedAddContentHeight(obj.transform)) { //对高度进行增加 content.sizeDelta += new Vector2(contentLayoutGroup.cellSize.x + contentLayoutGroup.spacing.x,0 ); //高度加间距 } break; case LoopScrollViewType.Vertical: if (last != null) //动态设置位置 { obj.transform.localPosition = last.localPosition - new Vector3(0, contentLayoutGroup.cellSize.y + contentLayoutGroup.spacing.y, 0); } //要不要增加高度 if (IsNeedAddContentHeight(obj.transform)) { //对高度进行增加 content.sizeDelta += new Vector2(0, contentLayoutGroup.cellSize.y + contentLayoutGroup.spacing.y); //高度加间距 } break; } } } //移除最后一个物体 public void OnRemoveLast() { if ( dataApdater.RemoveHeadData()) { Debug.Log("移出尾"); 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; } //禁用 contentLayoutGroup sizeFitter 这两个组件 public void EnableFalseGrid() { contentLayoutGroup.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); switch (scrollViewType) { case LoopScrollViewType.Horizontal: if (rectCorners[3].x < contentCorners[3].x) { return true; } break; case LoopScrollViewType.Vertical: if (rectCorners[0].y < contentCorners[0].y) { return true; } break; } return false; } //设置数据 public void SetData(GameObject chileItem,LoopDataItem data) { chileItem.transform.Find("Text").GetComponent<Text>().text = data.id.ToString(); } #endregion }
???现存问题:
在往下拖动时,可以实现新项目的增加,但是将滚动条拖回到开头,消失的没有再出现。emmmm,不知道哪里出现了问题,不知道如何解决。
脚本:LoopItem有部分更新,在复制一次
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class LoopItem : MonoBehaviour { #region 字段 //通过一个方法,获取到边界的信息 private RectTransform rect; //rect 自己的显示区域 private RectTransform viewRect; //viewRect 是scrollview的显示区域 //声明四个边的坐标 private Vector3[] rectCorners; private Vector3[] viewCorners; private LoopScrollViewType scrollViewType; #endregion #region 事件 public Action onAddHead; public Action onRemoveHead; public Action onAddLast; public Action onRemoveLast; #endregion //对两个变量初始化 private void Awake() { rect = transform.GetComponent<RectTransform>(); //通过transf.GetComponent 获取到自己的显示区域 viewRect = transform.GetComponentInParent<ScrollRect>().GetComponent<RectTransform>(); rectCorners = new Vector3[4]; //初始化数组 viewCorners = new Vector3[4]; } void Update() { LinstenerCorners(); } //监听边界 public void LinstenerCorners() { //获取自身的边界 rect.GetWorldCorners(rectCorners); //获取显示区域的边界 viewRect.GetWorldCorners(viewCorners); if (IsFirst()) //如果是头 { switch (scrollViewType) { case LoopScrollViewType.Horizontal: if (rectCorners[3].x < viewCorners[0].x) { //把头节点给隐藏掉 if (onRemoveHead != null) { onRemoveHead(); } //如果它不为空,触发onRemoveHead } if (rectCorners[0].x > viewCorners[0].x) { //添加头节点 if (onAddHead != null) { onAddHead(); } } break; case LoopScrollViewType.Vertical: if (rectCorners[0].y > viewCorners[1].y) { //把头节点给隐藏掉 if (onRemoveHead != null) { onRemoveHead(); } //如果它不为空,触发onRemoveHead } if (rectCorners[1].y < viewCorners[0].y) { //添加头节点 if (onAddHead != null) { onAddHead(); } } break; } } if (IsLast()) //如果是尾 { switch (scrollViewType) { case LoopScrollViewType.Horizontal: //添加尾部 if (rectCorners[3].x < viewCorners[3].x) { if (onAddLast != null) { onAddLast(); } } //回收尾部 if (rectCorners[0].x > viewCorners[3].x) { if (onRemoveHead != null) { onRemoveHead(); } } break; case LoopScrollViewType.Vertical: //添加尾部 if (rectCorners[0].y > viewCorners[0].y) { if (onAddLast != null) { onAddLast(); } } //回收尾部 if (rectCorners[1].y < viewCorners[0].y) { if (onRemoveHead != null) { onRemoveHead(); } } break; } } } //判断是不是第一个 public bool IsFirst() { for (int i = 0; i < transform.parent.childCount; i++) //计算子物体的数量,头部从0开始找 { 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; } //设置LoopScrollViewd 的类型 public void SetLoopScrollViewType( LoopScrollViewType loopScrollViewType ) { this.scrollViewType = loopScrollViewType; } }
数据库搭建脚本:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class DataApdater<T> { #region 字段 //保存的所有数据 public List<T> allData = new List<T>(); //不知道什么类型 所以是泛型 //保存当前正在显示的数据 public LinkedList<T> currentShowData = new LinkedList<T>(); //LinkedList 链表 #endregion #region 方法 public T GetHeaderData() { //判断总数据的数量 if (allData.Count == 0) { return default(T); } //特殊情况 if ( currentShowData.Count == 0 ) { T header = allData[0]; currentShowData.AddFirst(header); return header; } //currentShowData 当前正在显示的第一个数据 T t = currentShowData.First.Value; int index = allData.IndexOf(t); if (index != 0) //判断数据不为空时 { T header = allData[index - 1]; currentShowData.AddFirst(header); //将数据加到头部 return header; } return default(T); //如果等于零,默认值为空 } public bool RemoveHeadData() { //移除 currentShowData 当前正在显示的第一个数据的第一个数据 if (currentShowData.Count == 0) { return false; } currentShowData.RemoveFirst(); return true; } public T GetLastData() { //判断总数据的数量 if (allData.Count == 0) { return default(T); } //特殊情况 if (currentShowData.Count == 0 || currentShowData.Count == 1) { T l = allData[0]; currentShowData.AddLast(l); return l; } //获取 currentShowData 最后一个的下一个 T last = currentShowData.Last.Value; int index = allData.IndexOf(last); if (index != allData.Count - 1) { T now_last = allData[index + 1]; currentShowData.AddLast(now_last); return now_last; } return default(T); } public bool RemoveLastData() { //移除 currentShowData 最后一个 if (currentShowData.Count == 0 || currentShowData.Count == 1) { return false; } currentShowData.RemoveLast(); return true; } #endregion #region 数据管理 public void InitData(T[] t) { allData.Clear(); currentShowData.Clear(); allData.AddRange(t); } public void InitData(List<T> t ) //重载函数 { InitData(t.ToArray()); } public void AddData(T[] t) //添加数据 { allData.AddRange(t); } public void AddData(List<T> t) { AddData(t.ToArray()); } #endregion }
调取数据库(好像是)的脚本:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class LoopDataItem { public int id; public LoopDataItem(int id) { this.id = id; } }
原文:https://www.cnblogs.com/gyjldlhiahia/p/14805785.html