列表循环滑动

2020-01-03  本文已影响0人  APP4x

很多地方需要滑动列表,比如:排行榜。
滑动列表组件:

其中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();
上一篇下一篇

猜你喜欢

热点阅读