方法一:使用UGUI自带的scrollview组件
缺点:占内存,耗费性能
方法二:代码制作自己的滚动视图
一、LoopList 类
功能:
- 定义了滚动列表的相关属性,如
offSetY
(项之间的间隔)、_itemHeight
(单个项的高度)、_content
(滚动视图的内容容器)、_itemsList
(存储所有的LoopListItem
实例)和_modelsList
(存储外部数据模型LoopListItemModel
)。 - 在
Start
方法中进行了一系列初始化操作,包括模拟数据获取(GetModel
)、确定要显示的项数量(GetShowItemNum
)、实例化项(SpawnItem
)、设置内容大小(SetContentSize
)以及注册滚动事件监听器(transform.GetComponent<ScrollRect>().onValueChanged.AddListener(ValueChange)
)。 ValueChange
方法在滚动视图的值发生变化时被调用,遍历所有的LoopListItem
实例并调用它们的OnValueChange
方法以更新状态。GetShowItemNum
方法根据滚动视图的高度、项的高度和间隔计算要显示的项的数量。SpawnItem
方法根据给定的数量和项的预制体实例化LoopListItem
实例,并设置它们的初始状态和添加到列表中。GetData
方法根据索引从_modelsList
中返回相应的LoopListItemModel
,如果索引超出范围则返回一个新的默认模型。GetModel
方法从资源中加载所有的Sprite
,并为每个Sprite
创建一个LoopListItemModel
实例添加到_modelsList
中。SetContentSize
方法根据项的数量、高度和间隔设置滚动视图内容容器的大小。
分三个类的优点:
- 职责分离:
LoopList
类主要负责管理整个滚动列表的逻辑,包括数据获取、项的实例化、滚动事件处理和内容大小设置等。将这些功能集中在一个类中,使得滚动列表的整体逻辑更加清晰,易于维护和扩展。 - 可重用性:如果在其他地方需要使用类似的滚动列表功能,只需要复用
LoopList
类,并根据具体需求调整一些参数和数据来源即可。
二、LoopListItem 类
功能:
- 定义了单个项的相关属性,如
Rect
(项的矩形变换)、Icon
(项的图像)、Des
(项的文本描述)、_content
(父物体的内容容器)、_offSetY
(项之间的间隔)、_showItemNum
(要显示的项数量)、_id
(项的唯一标识)和_model
(项的数据模型)。 Init
方法用于初始化项的状态,包括设置父物体的内容容器、间隔、显示项数量,并调用ChangeId
方法进行进一步的初始化。AddGetDataListener
方法用于注册一个委托,以便在需要时获取数据模型。OnValueChange
方法在滚动视图的值发生变化时被调用,用于更新项的状态。它通过调用UpdateIdRange
和JudgeSelfId
方法来确定项的新状态,并根据需要调用ChangeId
方法进行更新。UpdateIdRange
方法根据内容容器的位置和项的高度、间隔计算项的起始和结束id
。JudgeSelfId
方法根据项的当前id
和计算得到的起始、结束id
来判断项是否需要更新状态,如果需要则调用ChangeId
方法。ChangeId
方法用于更新项的状态,包括设置id
、获取数据模型、更新图像和文本显示,以及设置项的位置。JudgeIdVaild
方法用于判断给定的id
是否有效,通过比较数据模型是否与默认模型不同来判断。SetPos
方法根据项的id
、高度和间隔设置项的位置。
分三个类的优点:
- 单一职责原则:
LoopListItem
类专注于单个项的逻辑,包括状态更新、位置计算、数据显示等。这样使得每个类的职责更加明确,代码更易于理解和维护。 - 可扩展性:如果需要对单个项进行更多的定制和扩展,可以在
LoopListItem
类中进行,而不会影响到滚动列表的整体逻辑。
三、LoopListItemModel 结构体
功能:
定义了一个数据模型,包含一个 Sprite
(图像)和一个 string
(描述),用于存储单个项的数据。同时提供了一个构造函数,方便创建具有特定图像和描述的 LoopListItemModel
实例。
分三个类的优点:
- 数据封装:将项的数据封装在一个单独的结构体中,使得数据的管理更加清晰和规范。可以方便地在不同的类中传递和使用这个数据模型,而不需要直接操作图像和文本等具体的数据。
- 可维护性:如果需要修改项的数据结构,只需要在
LoopListItemModel
结构体中进行修改,而不会影响到其他两个类的逻辑。
综上所述,将这三个类分开可以实现更好的代码组织、职责分离、可重用性和可维护性。每个类都专注于特定的功能,使得整个代码结构更加清晰,易于理解和扩展。
1.LoopList
LoopList
类主要负责管理整个滚动列表的逻辑
using System.Collections;
using System.Collections.Generic;
using System.Resources;
using UnityEngine;
using UnityEngine.UI;
public class LoopList : MonoBehaviour
{
//item上下之间的间隔
public float offSetY;
private float _itemHeight;
private RectTransform _content;
private List<LoopListItem> _itemsList;//永久存放item的数据
private List<LoopListItemModel> _modelsList;//用来存储外部进来的数据,把外部的图片当做精灵,给到item
void Start()
{
_itemsList=new List<LoopListItem>();
_modelsList = new List<LoopListItemModel>();
//模拟数据获取
GetModel();
_content = transform.Find("Viewport/Content").GetComponent<RectTransform>();
GameObject itemPrefab = Resources.Load<GameObject>("LoopListItem");
_itemHeight=itemPrefab.GetComponent<RectTransform>().rect.height;
int num = GetShowItemNum(_itemHeight,offSetY);
SpawnItem(num,itemPrefab);
SetContentSize();
//AddListener 方法用于注册一个事件处理程序(在这里是 ValueChange 方法),当 ScrollRect 的值发生变化时,该方法会被调用。
transform.GetComponent<ScrollRect>().onValueChanged.AddListener(ValueChange);
}
//ValueChange 是一个方法,当 onValueChanged 事件被触发时,它将被调用。
//ScrollRect 的 onValueChanged 事件定义了一个特定的委托类型,通常是 UnityAction<Vector2>。
//这意味着任何添加到这个事件的监听器(比如 ValueChange 方法)都必须接收一个 Vector2 类型的参数。
private void ValueChange(Vector2 data)
{
foreach(LoopListItem item in _itemsList)
{
item.OnValueChange();
}
}
private int GetShowItemNum(float itemHeight,float offsety)
{
float height=GetComponent<RectTransform>().rect.height;
return Mathf.CeilToInt(height/(_itemHeight+offsety))+1;
}
private void SpawnItem(int num,GameObject itemPrefab)
{
//临时变量用在父物体中存储生成的item
GameObject Temp = null;
//临时变量用在子物体本身的列表中存储item
LoopListItem itemTemp = null;
for (int i = 0; i < num; i++)
{
Temp=Instantiate(itemPrefab, _content);
itemTemp=Temp.AddComponent<LoopListItem>();
itemTemp.AddGetDataListener(GetData);
itemTemp.Init(i,offSetY,num);
_itemsList.Add(itemTemp);
}
}
private LoopListItemModel GetData(int index)
{
if (index<0||index>=_modelsList.Count)
{
return new LoopListItemModel();
}
return _modelsList[index];
}
//获得外部的模型资源的精灵当做item
private void GetModel()
{
foreach(var sprite in Resources.LoadAll<Sprite>("Icon"))
{
//使用构造函数生成一个新的实例
_modelsList.Add(new LoopListItemModel(sprite,sprite.name));
}
}
private void SetContentSize()
{
float y = _modelsList.Count * _itemHeight + (_modelsList.Count - 1) * offSetY;
_content.sizeDelta=new Vector2(_content.sizeDelta.x,y);
}
}
2.LoopListItem
LoopListItem
类专注于单个项的更新逻辑
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.UI;
public class LoopListItem : MonoBehaviour
{
private RectTransform _rect;
public RectTransform Rect
{
get
{
if ( _rect == null )
{
_rect = GetComponent<RectTransform>();
}
return _rect;
}
}
private Image _icon;
public Image Icon
{
get
{
if ( _icon == null )
{
//找到子物体下的image物体,获得它的image组件
_icon = transform.Find("Image").GetComponent<Image>();
}
return _icon;
}
}
private Text _des;
public Text Des
{
get
{
if ( _des == null )
{
//找到子物体下的text物体,获得它的text组件
_des = transform.Find("Text").GetComponent<Text>();
}
return _des;
}
}
//private int _startId,_endId;
private RectTransform _content;
public float _offSetY;
private int _showItemNum;
private int _id;
private LoopListItemModel _model;
public void Init(int id,float offsety,int showItemNum)
{
//获得_content物体的组件,用于后续计算id
_content=transform.parent.GetComponent<RectTransform>();
_offSetY = offsety;
_showItemNum = showItemNum;
ChangeId(id);
}
//允许外部方法通过 AddGetDataListener 方法传递一个符合 Func<int, LoopListItem> 签名的方法。
//通过 _getData 字段保存这个方法的引用,以便在需要时调用。
//定义委托
//只能在类的构造函数或其他方法中将该方法分配给 _getData 委托:
private Func<int, LoopListItemModel> _getData;
//这个方法用来接受委托,并且注册_getData委托
//间接调用委托
public void AddGetDataListener( Func<int, LoopListItemModel> getData )
{
_getData = getData;
}
public void OnValueChange()
{
int startId=0, endId=0;
UpdateIdRange(out startId,out endId);
JudgeSelfId(startId,endId);
}
private void UpdateIdRange(out int startId,out int endId)
{
startId =Mathf.FloorToInt( _content.anchoredPosition.y / (Rect.rect.height + _offSetY));
endId = startId + _showItemNum - 1;
}
private void JudgeSelfId(int _startId,int _endId)
{
if (_id<_startId)
{
ChangeId(_endId);
}
else if(_id>_endId)
{
ChangeId(_startId);
}
}
private void ChangeId(int id)
{
if (_id != id && JudgeIdVaild(id))
{
_id = id;
_model = _getData(id);
Icon.sprite = _model.Icon;
Des.text = _model.Describe;
SetPos();
}
}
private bool JudgeIdVaild(int id)
{
return !_getData(id).Equals(new LoopListItemModel());
}
private void SetPos()
{
Rect.anchoredPosition = new Vector2(0, -_id * (Rect.rect.height + _offSetY));
}
}
3.LoopListItemModel.cs
用于存储单个项的数据模型
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public struct LoopListItemModel
{
public Sprite Icon;
public string Describe;
//构造函数与类或结构体同名,并且没有返回类型
//构造函数是 C# 中一个非常重要的概念,它允许你在创建对象时初始化对象的状态。通过构造函数重载,
//可以创建不同的构造函数来满足不同的初始化需求。通过实例化结构体或类并调用其构造函数,可以创建具备特定属性值的对象,以便在程序中使用。
public LoopListItemModel(Sprite icon,string describe)
{
Icon = icon;
Describe = describe;
}
}