列表循环滑动

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

其中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();
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容

  • 专业考题类型管理运行工作负责人一般作业考题内容选项A选项B选项C选项D选项E选项F正确答案 变电单选GYSZ本规程...
    小白兔去钓鱼阅读 8,970评论 0 13
  • 1. file n. 文件;v. 保存文件2. command n. 命令指令3. use v. 使用用途4. p...
    喵呜Yuri阅读 747评论 0 4
  • 最近为同行有大量的客户和经销商提出了一个问题,那就是如何判断没有仪器的情况来识别和识别丹尼逊油泵的故障。 关于这个...
    柏煜液压设备阅读 286评论 0 0
  • 希望通过本周对关键对话的,能提高自己的沟通能力。特别是自己与同事意见不同时,不会因为怕争吵而退缩,不去表达自己的关...
    假装Yes阅读 86评论 0 0
  • 1、 设置堆叠画布窗口跟随主窗口变化 form级触发器:WHEN-WINDOW_RESIZED触发器中修改in中的...
    Funk_V阅读 3,059评论 0 2