美文网首页
列表循环滑动

列表循环滑动

作者: APP4x | 来源:发表于2020-01-03 18:55 被阅读0次

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

    其中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();
    

    相关文章

      网友评论

          本文标题:列表循环滑动

          本文链接:https://www.haomeiwen.com/subject/hectactx.html