在游戏开发中,经常会遇到需要展示大量数据的情况,例如排行榜、背包等。为了优化显示效果和性能,一个常见的做法是使用无限滑动列表(Infinite Scroll View)。本文将详细解析如何实现无限滑动列表。
基本原理
无限滑动列表的基本原理是,通过动态加载和回收Item的方式,实现滑动时只创建和显示可见的Item,大大减少了内存开销和渲染性能。
实现思路
无限滑动列表的实现思路如下:
- 创建一个滑动框,其中包含一个Content组件,用来容纳所有的Item。
- 在Content中使用GridLayoutGroup布局组件,目的是把Item按照规则排列。
- 固定显示一定数量的Item,剩下的Item通过滑动来动态创建和销毁。
- 根据滑动的方向(水平或竖直),判断滑动的距离和速度,当滑动超过一定阈值时,创建或销毁Item,并根据数据设置其位置和显示内容。
主要实现步骤
下面将具体介绍实现无限滑动列表的主要步骤:
-
初始化Init 首先,我们需要在脚本中声明一些变量和属性,用来控制滑动列表的显示和行为。在Init方法中,我们需要获取滑动框的组件,设置布局,设置Content的大小,并实例化固定数量的Item,这里使用了一个itemPrefab作为Item的模板。
-
处理滑动OnScroll 在OnScroll方法中,我们需要根据滑动的距离和速度,判断是否需要创建或销毁Item,以及设置其位置和显示内容。这里需要根据滑动的方向(水平或竖直)来做相应的处理。在滑动过程中,我们通过改变Item的位置和显示内容来实现无限滑动的效果。
-
设置Item的位置和显示内容 通过SetPos方法,我们可以设置Item的位置。其中,根据滑动的方向(水平或竖直),设置Item的锚点位置和偏移量。
-
外部调用 提供了一些外部调用的方法,用于设置显示内容、设置数据总数量和销毁所有的Item。通过SetShow方法,我们可以自定义Item的显示内容。通过SetTotalCount方法,我们可以设置数据的总数量。通过DestoryAll方法,我们可以销毁所有的Item,以便重置滑动列表。
运行实例
代码实现
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.Linq;
/// <summary>
/// 无限滑动列表
/// </summary>
public class InfiniteCrollView : MonoBehaviour
{
private ScrollRect scrollRect;//滑动框组件
private RectTransform content;//滑动框的Content
private GridLayoutGroup layout;//布局组件
[Header("滑动类型")]
public ScrollType scrollType;
[Header("固定的Item数量")]
public int fixedCount;
[Header("Item的预制体")]
public GameObject itemPrefab;
private int totalCount;//总的数据数量
private List<RectTransform> dataList = new List<RectTransform>();//数据实体列表
private int headIndex;//头下标
private int tailIndex;//尾下标
private Vector2 firstItemAnchoredPos;//第一个Item的锚点坐标
#region Init
/// <summary>
/// 实例化Item
/// </summary>
private void InitItem()
{
for (int i = 0; i < fixedCount; i++)
{
GameObject tempItem = Instantiate(itemPrefab, content);
dataList.Add(tempItem.GetComponent<RectTransform>());
SetShow(tempItem.GetComponent<RectTransform>(), i);
}
}
/// <summary>
/// 设置Content大小
/// </summary>
private void SetContentSize()
{
int h;
int v;
if (scrollType == ScrollType.Horizontal)
{
h = totalCount;
v = 1;
}
else
{
h = 1;
v = totalCount;
}
content.sizeDelta = new Vector2
(
layout.padding.left + layout.padding.right + h * (layout.cellSize.x + layout.spacing.x) - layout.spacing.x - transform.GetComponent<RectTransform>().rect.width+10,//10是一个调整数为了防止最后一个item显示正常可以按需调整
layout.padding.top + layout.padding.bottom + v * (layout.cellSize.y + layout.spacing.y) - layout.spacing.y
);
}
/// <summary>
/// 设置布局
/// </summary>
private void SetLayout()
{
layout.startCorner = GridLayoutGroup.Corner.UpperLeft;
layout.startAxis = GridLayoutGroup.Axis.Horizontal;
layout.childAlignment = TextAnchor.UpperLeft;
layout.constraintCount = 1;
if (scrollType == ScrollType.Horizontal)
{
scrollRect.horizontal = true;
scrollRect.vertical = false;
layout.constraint = GridLayoutGroup.Constraint.FixedRowCount;
}
else if (scrollType == ScrollType.Vertical)
{
scrollRect.horizontal = false;
scrollRect.vertical = true;
layout.constraint = GridLayoutGroup.Constraint.FixedColumnCount;
}
}
/// <summary>
/// 得到第一个数据的锚点位置
/// </summary>
private void GetFirstItemAnchoredPos()
{
firstItemAnchoredPos = new Vector2
(
layout.padding.left + layout.cellSize.x / 2,
-layout.padding.top - layout.cellSize.y / 2
);
}
#endregion
#region Main
/// <summary>
/// 滑动中
/// </summary>
private void OnScroll(Vector2 v)
{
if (dataList.Count == 0)
{
Debug.LogWarning("先调用SetTotalCount方法设置数据总数量再调用Init方法进行初始化");
return;
}
if (scrollType == ScrollType.Vertical)
{
//向上滑
while (content.anchoredPosition.y >= layout.padding.top + (headIndex + 1) * (layout.cellSize.y + layout.spacing.y)
&& tailIndex != totalCount - 1)
{
//将数据列表中的第一个元素移动到最后一个
RectTransform item = dataList[0];
dataList.Remove(item);
dataList.Add(item);
//设置位置
SetPos(item, tailIndex + 1);
//设置显示
SetShow(item, tailIndex + 1);
headIndex++;
tailIndex++;
}
//向下滑
while (content.anchoredPosition.y <= layout.padding.top + headIndex * (layout.cellSize.y + layout.spacing.y)
&& headIndex != 0)
{
//将数据列表中的最后一个元素移动到第一个
RectTransform item = dataList.Last();
dataList.Remove(item);
dataList.Insert(0, item);
//设置位置
SetPos(item, headIndex - 1);
//设置显示
SetShow(item, headIndex - 1);
headIndex--;
tailIndex--;
}
}
else if (scrollType == ScrollType.Horizontal)
{
//向左滑
while (content.anchoredPosition.x <= -layout.padding.left - (headIndex + 1) * (layout.cellSize.x + layout.spacing.x)
&& tailIndex != totalCount - 1)
{
//将数据列表中的第一个元素移动到最后一个
RectTransform item = dataList[0];
dataList.Remove(item);
dataList.Add(item);
//设置位置
SetPos(item, tailIndex + 1);
//设置显示
SetShow(item, tailIndex + 1);
headIndex++;
tailIndex++;
}
//向右滑
while (content.anchoredPosition.x >= -layout.padding.left - headIndex * (layout.cellSize.x + layout.spacing.x)
&& headIndex != 0)
{
//将数据列表中的最后一个元素移动到第一个
RectTransform item = dataList.Last();
dataList.Remove(item);
dataList.Insert(0, item);
//设置位置
SetPos(item, headIndex - 1);
//设置显示
SetShow(item, headIndex - 1);
headIndex--;
tailIndex--;
}
}
}
#endregion
#region Tool
/// <summary>
/// 设置位置
/// </summary>
private void SetPos(RectTransform trans, int index)
{
if (scrollType == ScrollType.Horizontal)
{
trans.anchoredPosition = new Vector2
(
index == 0 ? layout.padding.left + firstItemAnchoredPos.x :
layout.padding.left + firstItemAnchoredPos.x + index * (layout.cellSize.x + layout.spacing.x),
firstItemAnchoredPos.y
);
}
else if (scrollType == ScrollType.Vertical)
{
trans.anchoredPosition = new Vector2
(
firstItemAnchoredPos.x,
index == 0 ? -layout.padding.top + firstItemAnchoredPos.y :
-layout.padding.top + firstItemAnchoredPos.y - index * (layout.cellSize.y + layout.spacing.y)
);
}
}
#endregion
#region 外部调用
/// <summary>
/// 初始化
/// </summary>
public void Init()
{
scrollRect = GetComponent<ScrollRect>();
content = scrollRect.content;
layout = content.GetComponent<GridLayoutGroup>();
scrollRect.onValueChanged.AddListener((Vector2 v) => OnScroll(v));
//设置布局
SetLayout();
//设置头下标和尾下标
headIndex = 0;
tailIndex = fixedCount - 1;
//设置Content大小
SetContentSize();
//实例化Item
InitItem();
//得到第一个Item的锚点位置
GetFirstItemAnchoredPos();
}
/// <summary>
/// 设置显示
/// </summary>
public void SetShow(RectTransform trans, int index)
{
//=====根据需求进行编写
trans.GetComponentInChildren<Text>().text = index.ToString();
trans.name = index.ToString();
}
/// <summary>
/// 设置总的数据数量
/// </summary>
public void SetTotalCount(int count)
{
totalCount = count;
}
/// <summary>
/// 销毁所有的元素
/// </summary>
public void DestoryAll()
{
for (int i = dataList.Count - 1; i >= 0; i--)
{
DestroyImmediate(dataList[i].gameObject);
}
dataList.Clear();
}
#endregion
}
/// <summary>
/// 滑动类型
/// </summary>
public enum ScrollType
{
Horizontal,//竖直滑动
Vertical,//水平滑动
}
这篇代码实现了一个无限滑动列表,在Unity引擎中展示。它可以在水平方向或垂直方向上滑动,并根据固定的Item数量在滑动过程中动态加载和复用Item对象,以实现无限滑动的效果。下面将按照步骤逐一解析该代码。
代码解析
首先,代码定义了一个InfiniteScrollView类,该类继承自MonoBehaviour类,用于管理滑动列表的各项功能。该类主要包含以下成员变量:
- scrollRect:滑动框组件,用于控制滑动的行为。
- content:滑动框的Content,用于放置Item对象。
- layout:布局组件,用于控制Item对象的排列方式。
- scrollType:滑动类型枚举,用于指定滑动是水平还是垂直。
- fixedCount:固定的Item数量,用于定义一次滑动的可显示Item的数量。
- itemPrefab:Item的预制体,用于生成Item对象。
接下来,代码定义了一些辅助函数来初始化和操作滑动列表。其中包括:
- InitItem():实例化Item,根据fixedCount生成对应数量的Item对象,并将它们添加到dataList中。
- SetContentSize():根据totalCount和布局设置,计算并设置Content的大小,以便正确显示所有的Item。
- SetLayout():根据scrollType设置滑动框的布局相关属性,如起始角落、排列方式和约束条件。
- GetFirstItemAnchoredPos():计算第一个Item的锚点位置,以便在滑动列表中正确显示。
然后,代码定义了一个OnScroll()函数,用于监听滑动事件,并在滑动过程中处理Item的加载和复用。具体实现如下:
- 针对垂直滑动方式,当content的纵向偏移量超过下一个Item位置时,将dataList中的第一个元素移动到最后一个位置,并更新头尾下标,以及移动后的位置和展示。
- 针对水平滑动方式,当content的横向偏移量超过下一个Item位置时,将dataList中的第一个元素移动到最后一个位置,并更新头尾下标,以及移动后的位置和展示。
最后,代码提供了一些外部调用的函数,以便进行初始化和设置。它们包括:
- Init():初始化滑动列表,内部调用了上述的SetLayout、SetContentSize、InitItem和GetFirstItemAnchoredPos函数。
- SetShow():设置Item的显示内容,可以根据需求进行自定义。
- SetTotalCount():设置滑动列表中Item的总数量。
- DestoryAll():销毁所有的Item对象。
通过以上的解析和编写指导,该技术博客将尽可能详细地介绍了无限滑动列表的实现原理和具体操作步骤,帮助读者了解和使用该功能