很多地方需要滑动列表,比如:排行榜。
滑动列表组件:
其中ScrollView,就是控制滑动的组件
Viewport就是视野区域,通过Mask控制可显示的范围
Content就是真实滑动的区域,长度可能为很长很长
滑动通过控制,达到不同的区域透过视野区域显示的效果
问题:
假如有1000个人,就需要实例化1000条对应的记录
很浪费性能
其实真正现实的永远那么几个,可以优化做成循环滑动列表
1.向上滑动,下面的通过移动补到上面
2.向下滑动,上面的通过移动补到下面
若能显示n个记录
真正只需要n+1个记录,多一个的目的就是为了增强效果,达到缓冲的的目的
往下移动一点,其实上面的显示一半,下面的也显示一半,就是n+1个
因为要移动位置,锚点和中心点保持一直,确保(0,0)就是初始位置
移动只需控制y的高度,就是索引 * 预制体高度
达到的效果:
左边可以看到只有六个,但是右边会不停的滑动
因为:
1.Content长度还是那么长
2.移动过了一个prefabHeight的区域会把最一端的记录移动到另一端(有可能一帧内移动多个prefabHeight区域)
3.数据是有很多的,每一个记录对应一个索引 Index,读取数据内的对应记录,然后对UI刷新即可
4.这里是不能继承Mono的,如果可以,还会更简单
代码实现:
1.单个记录的基类
public interface IScrollRecord
{
int Index { get; set; }//数据索引
RectTransform RectTransform { get; }//控制anchoredPos
}
2.循环滑动列表类
/// <summary>
/// 循环滑动
/// </summary>
public class LoopScrollTool
{
public const int SCROLL_DOWN2UP = 1; //自下向上滑动
public const int SCROLL_UP2DOWN = -1;//自上向下滑动
private const float COMPARE_VALUE = 0.5f;//比较值
private const float MISTAKE_VALUE = 0.1f;//float 误差值
#region 字段
public LinkedList<IScrollRecord> records;// 滑动的元素 type : 双向链表可以快速实现首尾替换
public int count;// 总长度
public RectTransform content;// 容器
public float prefabHeight; // 预制体大小
public float spacing; // 间距
public int forward;// 滑动方向
public float maxPosY;// 上限坐标
public float minPosY;// 下限坐标
public int offY;//可能不是从0的位置开始
public Action<IScrollRecord, int> onRefresh;//移动位置的时候刷数据用的Event
private float lastAnchoredPosY;
private float currAnchoredPosY;
private float validAnchoredPosY;//有效操作记录Y
#endregion
#region 初始化
/// <summary>
/// 初始化滑动列表
/// </summary>
/// <param name="records">需要显示的UI组件链表</param>
/// <param name="allCount">总数据长度</param>
/// <param name="content">容器</param>
/// <param name="prefabHeight">每个UI的高度</param>
/// <param name="spacing">每个UI的间距</param>
/// <param name="forward">滑动方向</param>
public void Init(LinkedList<IScrollRecord> records, int allCount, RectTransform content, float prefabHeight, float spacing, int for
{
this.records = records;
this.count = allCount;
this.content = content;
this.prefabHeight = prefabHeight;
this.spacing = spacing;
this.forward = forward;
//总高度 = 总数据长度 * 每个的高度 + 间距
float height = allCount * prefabHeight + (allCount - 1) * spacing;
if (forward == LoopScrollTool.SCROLL_DOWN2UP)
{
this.minPosY = -1f * height;// 粗略的 不准
this.maxPosY = 0f;
}
else if (forward == LoopScrollTool.SCROLL_UP2DOWN)
{
this.minPosY = 0f;
this.maxPosY = height;
}
this.content.sizeDelta = new Vector2(content.sizeDelta.x, height);
}
/// <summary>
/// 设置要从第几个元素开始显示
/// </summary>
/// <param name="posY"></param>
/// <param name="part"></param>
public void SetShowRecordPart(int defaultPart = 0, bool changeContentPos = true)
{
if (changeContentPos)
{
//计算对应的位置
float posY = (prefabHeight + spacing) * defaultPart * forward * -1;
this.content.anchoredPosition = new Vector2(content.anchoredPosition.x, posY);
}
this.lastAnchoredPosY = (prefabHeight + spacing) * defaultPart;
//刷数据
var first = records.First;
for (int i = 0; i < records.Count; i++)
{
if (defaultPart + i < count)
{
UITool.SetGoActive(true, first.Value.RectTransform.gameObject);
onRefresh(first.Value, defaultPart + i);
if (changeContentPos)
first.Value.RectTransform.anchoredPosition = new Vector2(0, lastAnchoredPosY + (prefabHeight + spacing) * i * forwa
}
else
{
if (changeContentPos)
first.Value.RectTransform.anchoredPosition = new Vector2(0, lastAnchoredPosY + (prefabHeight + spacing) * i * forwa
//要显示的数量大于总数量 隐藏
UITool.SetGoActive(false, first.Value.RectTransform.gameObject);
}
first = first.Next;
}
}
/// <summary>
/// 只刷数据 保留当前位置
/// boo 是否改变content位置
/// </summary>
public void OnlyRefreshRecords(bool boo = true)
{
//不满一页显示
if (count <= records.Count)
{
SetShowRecordPart(0, boo);
return;
}
var first = records.First;
while (first != null)
{
if (first.Value.Index < count)
{
onRefresh(first.Value, first.Value.Index);
UITool.SetGoActive(true, first.Value.RectTransform.gameObject);
}
else
{
//要显示的数量大于总数量 隐藏
UITool.SetGoActive(false, first.Value.RectTransform.gameObject);
}
first = first.Next;
}
}
#endregion
#region 逻辑
public void Update()
{
//安全检测
if (records == null || count < records.Count)
return;
float anchoredPosY = content.anchoredPosition.y;
anchoredPosY = Mathf.Clamp(anchoredPosY, minPosY, maxPosY);
currAnchoredPosY = Mathf.Abs(anchoredPosY);
//无滑动操作
if (Mathf.Abs(currAnchoredPosY - validAnchoredPosY) < LoopScrollTool.MISTAKE_VALUE)
return;
//移动
float offset = currAnchoredPosY - lastAnchoredPosY - offY;
int moveForward = 0;
int moveCount = 0;
if (this.TryMove(offset, out moveForward, out moveCount))
{
for (int i = 0; i < moveCount; i++)
{
SetRecordFromTo(moveForward);
}
}
}
private bool TryMove(float offset, out int result, out int moveCount)
{
if (offset - prefabHeight > LoopScrollTool.COMPARE_VALUE)
{
result = 1;
moveCount = Mathf.FloorToInt(offset / prefabHeight);
return true;
}
else if (offset < -1f * LoopScrollTool.COMPARE_VALUE)
{
result = -1;
moveCount = Mathf.FloorToInt((prefabHeight - offset) / prefabHeight);
return true;
}
else
{
result = 0;
moveCount = 0;
return false;
}
}
/// <summary>
/// 移动到目标方向
/// </summary>
/// <param name="moveForward">方向</param>
private void SetRecordFromTo(int moveForward)
{
IScrollRecord record1 = null;
IScrollRecord record2 = null;
//根据方向获取对应的Record
this.GetRecordsByForward(moveForward, out record1, out record2);
//超范围检测
if (!CheckRecordIndex(record1.Index + moveForward))
return;
//获取目标位置
Vector2 pos = record1.RectTransform.anchoredPosition;
pos.y += (prefabHeight + spacing) * moveForward * forward;
//设置当前位置
record2.RectTransform.anchoredPosition = pos;
int silblingIdx = moveForward > 0 ? records.Count - 1 : 0;
record2.RectTransform.SetSiblingIndex(silblingIdx);
//刷数据
if (onRefresh != null)
{
onRefresh(record2, record1.Index + moveForward);
UITool.SetGoActive(true, record2.RectTransform.gameObject);
}
this.MoveLinkPos(moveForward);
lastAnchoredPosY += (prefabHeight + spacing) * moveForward;
validAnchoredPosY = currAnchoredPosY;
}
/// <summary>
/// 根据方向获取对应的Record
/// </summary>
/// <param name="record1"></param>
/// <param name="record2"></param>
private void GetRecordsByForward(int moveForward, out IScrollRecord record1, out IScrollRecord record2)
{
if (this.forward == LoopScrollTool.SCROLL_UP2DOWN)
{
record1 = records.First.Value;
record2 = records.Last.Value;
if (moveForward == 1)
{
record1 = records.Last.Value;
record2 = records.First.Value;
}
}
else //if (this.forward == LoopScrollTool.SCROLL_DOWN2UP)
{
record1 = records.Last.Value;
record2 = records.First.Value;
if (moveForward == -1)
{
record1 = records.First.Value;
record2 = records.Last.Value;
}
}
}
/// <summary>
/// 检测是否超范围
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
private bool CheckRecordIndex(int index)
{
return index >= 0 && index < count;
}
/// <summary>
/// 移动在链表的位置
/// </summary>
/// <param name="forward"></param>
private void MoveLinkPos(int forward)
{
if (forward > 0)
{
IScrollRecord record = records.First.Value;
records.RemoveFirst();
records.AddLast(record);
}
else
{
IScrollRecord record = records.Last.Value;
records.RemoveLast();
records.AddFirst(record);
}
}
#endregion
}
3.使用如下:
//预定义
private ScrollRect wSR;
private RectTransform wContent;
private float prefabHeight = 110f;
private float spacing = 0f;
private LoopScrollTool wLoopTool;
private LinkedList<IScrollRecord> wRecords;
//初始化
for (int i = 0; i < wContent.childCount; i++)
{
GameObject go = wContent.GetChild(i).gameObject;
GuildRankRecord record = Make<GuildRankRecord>(go);
wRecords.AddLast(record);
}
this.wLoopTool = new LoopScrollTool();
this.wLoopTool.onRefresh += OnWRefresh;
//刷新事件
public void OnWRefresh(IScrollRecord record, int index)
{
GuildRankRecord grr = record as GuildRankRecord;
GuildRankData d = this.data[index];
grr.Refresh(d);
grr.Index = index;
}
//轮询
this.wLoopTool.Update();