UGUI进阶知识[八]循环利用滑动列表

滑动列表是程序里面最常用的一个组件之一
这次要实现的功能是使用少量的滑动列表项来加载显示大量的数据
即比如可显示部分能够容下5个滑动列表项,则demo只需要六个就可以达到若干个数据的显示
Demo在这里

实现的主要思路就是,
当上滑的时候,如果有子项超出可显示范围,则会对应添加到底部
当下滑的时候,如果有子项超出可显示范围,则会对应添加到顶部

程序主要分为三部分,模拟加载数据,单个子项item和总的list

程序的重要逻辑部分在代码里面有注释
代码如下所示:

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;


/// <summary>
/// 循环列表总控制
/// </summary>
public class LoopList : MonoBehaviour
{
    public float itemInterval;
    private float _itemHeight;

    /// <summary>
    /// ScrollRect的Content属性
    /// </summary>
    private RectTransform scrollRectContent;
    private List<LoopListItem> _items;
    private List<LoopListItemData> _models;


    private void Start()
    {
        _items = new List<LoopListItem>();
        _models = new List<LoopListItemData>();
        //模拟数据获取
        GetModel();

        scrollRectContent = transform.Find("Viewport/Content").GetComponent<RectTransform>();
        GameObject itemPrefab = Resources.Load<GameObject>("LoopListItem");
        _itemHeight = itemPrefab.GetComponent<RectTransform>().rect.height;
        int totalItemNum = GetTotalShowItemNum(_itemHeight, itemInterval);
        SpwanItem(totalItemNum, itemPrefab);
        SetContentSize();

        transform.GetComponent<ScrollRect>().onValueChanged.AddListener(OnScrollRectScroll);
    }

    private void OnScrollRectScroll(Vector2 data)
    {
        foreach (LoopListItem item in _items)
        {
            item.OnScrollRectScroll();
        }
    }

    /// <summary>
    /// 获得总共需要的项数 包括多余的一项
    /// </summary>
    /// <param name="itemHeight"></param>
    /// <param name="offset"></param>
    /// <returns></returns>
    private int GetTotalShowItemNum(float itemHeight, float offset)
    {
        float height = GetComponent<RectTransform>().rect.height;

        //这里多加的1是在显示区域外部 用来准备显示数据的一项
        return Mathf.CeilToInt(height / (itemHeight + offset)) + 1;
    }

    private void SpwanItem(int totalItemNum, GameObject itemPrefab)
    {
        GameObject temp = null;
        LoopListItem itemTemp = null;
        for (int idx = 0; idx < totalItemNum; idx++)
        {
            temp = Instantiate(itemPrefab, scrollRectContent);
            itemTemp = temp.AddComponent<LoopListItem>();
            itemTemp.AddGetDataListener(GetData);
            itemTemp.Init(idx, itemInterval, totalItemNum, scrollRectContent);
            _items.Add(itemTemp);
        }
    }

    /// <summary>
    /// 通过下标获取对应数据
    /// 下标超出范围的返回是new的数据
    /// </summary>
    /// <param name="index"></param>
    /// <returns></returns>
    private LoopListItemData GetData(int index)
    {
        if (index < 0 || index >= _models.Count)
            return new LoopListItemData();

        return _models[index];
    }

    private void GetModel()
    {
        foreach (var sprite in Resources.LoadAll<Sprite>("Icon"))
        {
            _models.Add(new LoopListItemData(sprite, sprite.name));
        }
    }

    /// <summary>
    /// 设置总内容区域的大小为所有数据加载之后的大小
    /// 只是超出可显示部分是没有LoopListItem的
    /// </summary>
    private void SetContentSize()
    {
        float y = _models.Count * _itemHeight + (_models.Count - 1) * itemInterval;
        scrollRectContent.sizeDelta = new Vector2(scrollRectContent.sizeDelta.x, y);
    }
}

using System;
using UnityEngine;
using UnityEngine.UI;


public class LoopListItem : MonoBehaviour
{
    /// <summary>
    /// 自身id  
    /// 其实也是每个LoopListItem在LoopList上对应的数据的下标
    /// 每个LoopListItem也根据这个id计算自身所处的位置
    /// </summary>
    private int selfId = -1;

    private RectTransform _rect;
    public RectTransform Rect
    {
        get
        {
            if (_rect == null)
                _rect = GetComponent<RectTransform>();
            return _rect;
        }
    }

    private Image _icon;
    public Image Icon
    {
        get
        {
            if (_icon == null)
                _icon = transform.Find("Image").GetComponent<Image>();
            return _icon;
        }
    }

    private Text _des;
    public Text Des
    {
        get
        {
            if (_des == null)
                _des = transform.Find("Text").GetComponent<Text>();
            return _des;
        }
    }

    private Func<int, LoopListItemData> _getData;
    private RectTransform scrollRectContent;
    /// <summary>
    /// 每项的空隙大小
    /// </summary>
    private float itemInterval;

    /// <summary>
    /// 总共需要的Item项数 包括多余的一项
    /// </summary>
    private int totalItemNum;
    private LoopListItemData data;

    public void Init(int id,float itemInterval,int totalItemNum, Transform scrollRectContent)
    {

        this.scrollRectContent = scrollRectContent.GetComponent<RectTransform>();

        this.totalItemNum = totalItemNum;

        this.itemInterval = itemInterval;

        ChangeSelfDataById(id);
    }

    /// <summary>
    /// 将通过id获取对应数据的工作交给外部即LoopList来做
    /// </summary>
    /// <param name="getData"></param>
    public void AddGetDataListener(Func<int, LoopListItemData> getData)
    {
        _getData = getData;
    }

    public void OnScrollRectScroll()
    {
        int toppestVisibleId, lowestVisibleId = 0;
        UpdateIdRange(out toppestVisibleId,out lowestVisibleId);
        OverflowRemedy(toppestVisibleId, lowestVisibleId);
    }

    private void UpdateIdRange(out int toppestVisibleId,out int lowestVisibleId)
    {
        //scrollRectContent的顶部ScrollRect顶部的时候 anchoredPosition.y = 0
        //anchoredPosition.y可以表示scrollRectContent滑出的时候
        //它的顶部到ScrollRect顶部的距离
        //越往上滑 y值越大 所得到的startId越大
        //所以这个值表示 scrollRectContent滑动到当前位置 时候 可见的最顶部的item的id 不是超出的item的id
        toppestVisibleId = Mathf.FloorToInt(scrollRectContent.anchoredPosition.y/(Rect.rect.height + itemInterval));

        //这个值表示的是可见的最底部的item的id
        lowestVisibleId = toppestVisibleId + totalItemNum - 1;

        //越往下 item的id越大
    }

    private void OverflowRemedy(int toppestVisibleId, int lowestVisibleId)
    {
        int offset = 0;

        //offset是为了防止向上或者向下滑动太快的时候
        //同时有几个item超出的话 超出的不同位置的item
        //要归位到它对应的位置
        if (selfId < toppestVisibleId)
        {
            
            //上滑的时候 超出两个item,这两个item是不可见的
            //则最顶部的item的selfId与toppestVisibleId的差值是2
            //offfset是1
            //它归为到底部时候的对应id是lowestVisibleId - 1 
            //第二个item的的selfId与toppestVisibleId的差值是1
            //offset是0 
            //它归为到底部时候的对应id是lowestVisibleId
            //顶部更多的超出也是一样的道理 
            //虽然没有越早超出的越快补上,但是每个item都会根据itemid刷新它对应的位置
            //所以看起来没问题
            offset = toppestVisibleId - selfId - 1;
            ChangeSelfDataById(lowestVisibleId - offset);
        }
        else if (selfId > lowestVisibleId)
        {
            //下滑的时候 超出两个item,这两个item是不可见的
            //则最底部的item的selfId与lowestVisibleId的差值是2
            //offfset是1
            //它归为到顶部时候的对应id是toppestVisibleId + 1 
            //第二个item的selfId与lowestVisibleId的差值是1
            //offset是0 
            //它归为到顶部时候的对应id是toppestVisibleId
            //底部更多的超出也是一样的道理 
            //虽然没有越早超出的越快补上,但是每个item都会根据itemid刷新它对应的位置
            //所以看起来没问题
            offset = selfId - lowestVisibleId - 1;
            ChangeSelfDataById(toppestVisibleId + offset);
        }
    }

    private void ChangeSelfDataById(int id)
    {
        if (selfId != id && JudgeIdValid(id))
        {
            selfId = id;
            data = _getData(id);
            Icon.sprite = data.Icon;
            Des.text = data.Describe;
            SetPos();
        }
    }

    private void SetPos()
    {
        //每个LoopListItem所挂物体的Pivot是在自身的左上角
        //当content的顶部与viewport顶部对齐的时候
        //anchoredPosition为(0,0)的在ScrollView最顶部 对应的selfId为0
        //当content的顶部超出viewport顶部对齐的时候
        //anchoredPosition为(0,toppestVisibleId)的在ScrollView最顶部
        Rect.anchoredPosition = new Vector2(0, - selfId * (Rect.rect.height + itemInterval));
    }

    private bool JudgeIdValid(int id)
    {
        //不合法的id返回的是new出来的新的LoopListItemModel
        return !_getData(id).Equals(new LoopListItemData());
    }
}

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 每项的数据
/// </summary>
public struct LoopListItemData
{
    public Sprite Icon;
    public string Describe;

    public LoopListItemData(Sprite icon, string describe)
    {
        Icon = icon;
        Describe = describe;
    }
}

发布了84 篇原创文章 · 获赞 13 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/weixin_43149049/article/details/103916821