欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

解決Unity無限滾動復(fù)用列表的問題

 更新時間:2022年04月02日 14:03:00   作者:小紫蘇  
這篇文章主要介紹了Unity無限滾動復(fù)用列表,無限滾動復(fù)用ScrollView就是解決這種問題,還可以用來做朋友圈,聊天等,需要的朋友可以參考下

無限滾動復(fù)用列表

Demo展示

前言

游戲中有非常多的下拉滾動菜單,比如成就列表,任務(wù)列表,以及背包倉庫之類;如果列表內(nèi)容非常豐富,會占用大量內(nèi)存,這篇無限滾動復(fù)用ScrollView就是解決這種問題;還可以用來做朋友圈,聊天等;

一般情況,ScrollView中每個Item的大小是一直的,使用ContentSizeFillter組件足夠解決大部分問題;

如果每個Item大小不一致,問題就復(fù)雜起來,需要做滾動位置判斷,我這里做了大小適應(yīng);

設(shè)計思路

1.將數(shù)據(jù)部分和滾動邏輯部分分離開,數(shù)據(jù)設(shè)計成泛型類;

2.在ScrollView組件上添加ScrollView腳本,控制Item的添加和刪除,分為頭部和尾部;

3.在每個Item上添加ScrollItem腳本,重寫更新數(shù)據(jù)方法,同時監(jiān)聽自身是否為頭部或者尾部;

4.如果為頭部或者尾部,且超界通過委托調(diào)用ScrollView腳本中的添加或刪除Item方法;

關(guān)鍵基類

1.ScrollData

負(fù)責(zé)整個列表的數(shù)據(jù)管理,分為總數(shù)據(jù)和現(xiàn)實數(shù)據(jù)兩個鏈表,增刪查改方法;泛型類方便復(fù)用;

這里使用LinkedList方便查找并返回頭尾節(jié)點;

全部代碼:

public class ScrollData<T>
{
    public List<T> allDatas;
    public LinkedList<T> curDatas;
    
    public ScrollData()
    {
        allDatas = new List<T>();
        curDatas = new LinkedList<T>();
        //加載數(shù)據(jù);
    }
    //獲取頭數(shù)據(jù)
    public T GetHeadData()
        if(allDatas.Count == 0)
            return default(T);
        if (curDatas.Count == 0)
        {
            T head = allDatas[0];
            curDatas.AddFirst(head);
            return head;
        }
        T t = curDatas.First.Value;
        int index = allDatas.IndexOf(t);
        if (index != 0)
            T head = allDatas[index - 1];
        return default(T);
    //移出頭數(shù)據(jù)
    public bool RemoveHeadData()
        if (curDatas.Count == 0 || curDatas.Count == 1)
            return false;
        curDatas.RemoveFirst();
        return true;
    //獲取尾部數(shù)據(jù)
    public T GetEndData()
        if (allDatas.Count == 0)
            T end = allDatas[0];
            curDatas.AddLast(end);
            return end;
        T t = curDatas.Last.Value;
        
        if (index != allDatas.Count - 1)
            T end = allDatas[index + 1];
         
    //移出尾部數(shù)據(jù)
    public bool RemoveEndData()
        curDatas.RemoveLast();
    //添加數(shù)據(jù),通過數(shù)組
    public void AddData(T[] t)
        allDatas.AddRange(t);
    //添加數(shù)據(jù),通過鏈表
    public void AddData(List<T> t)
        allDatas.AddRange(t.ToArray()); 
    //添加單條數(shù)據(jù)
    public void AddData(T t)
        allDatas.Insert(0,t);
        curDatas.AddFirst(t);
    //情況當(dāng)前顯示節(jié)點
    public void ClearCurData()
        curDatas.Clear();
    //獲取當(dāng)前顯示鏈表的第一個數(shù)據(jù)在總數(shù)據(jù)中的下標(biāo)
    public int GetFirstIndex()
        return allDatas.IndexOf(t);
}

2.ScrollView

關(guān)鍵字段:

scrollItemGo	//每個Item的預(yù)制體
content			//scrollRect下的Content
spacing			//每個Item的間隔
isStart			//是否第一次加載

方法:

GetChildItem;

1.獲取一個Item的預(yù)制體,先從content的子物體中尋找active為false的物體,如果沒有則根據(jù)scrollItemGo克隆一個;

2.創(chuàng)建新Item時,獲取ScrollItem組件,賦值其中的參數(shù)(四個委托),并初始化;

OnAddHead;OnRemoveHead;OnAddEnd;OnRemoveEnd;

委托方法:

1.調(diào)用ScrollData中GetHeadData方法,獲得頭數(shù)據(jù);

2.找到content中第一個節(jié)點;

3.調(diào)用GetChildItem方法獲得item的實例;

4.SetAsFirstSibling,將實例設(shè)置為首節(jié)點,同時調(diào)用RefreshData,刷新數(shù)據(jù);

5.根據(jù)item 的寬度做自適應(yīng)(item大小相同,只選掛載ContentSizeFitter);

全部代碼:

public class ScrollView : MonoBehaviour
{
    public GameObject scrollItemGo;
    private RectTransform content;
    
    [SerializeField]
    private float spacing;
    private bool isStart = true;
    void Start()
    {
        content = this.GetComponent<ScrollRect>().content;
        spacing = 15;
        OnAddHead();
    }
    private GameObject GetChildItem()
        //查找是否有未回收的子節(jié)點
        for (int i = 0; i < content.childCount; ++i)
        {
            GameObject tempGo = content.GetChild(i).gameObject;
            if (!tempGo.activeSelf)
            {
                tempGo.SetActive(true);
                return tempGo;
            }
        }
        //無創(chuàng)建新的
        GameObject childItem = GameObject.Instantiate<GameObject>(scrollItemGo,content.transform);
        ScrollViewItem scrollItem = childItem.GetComponent<ScrollViewItem>();
        if (scrollItem == null)
            scrollItem = childItem.AddComponent<ScrollViewItem>();
        
        scrollItem.onAddHead += OnAddHead;
        scrollItem.onRemoveHead += OnRemoveHead;
        scrollItem.onAddEnd += OnAddEnd;
        scrollItem.onRemoveEnd += OnRemoveEnd;
        scrollItem.Init();
        childItem.GetComponent<RectTransform>().anchorMin = new Vector2(0.5f, 1);
        childItem.GetComponent<RectTransform>().anchorMax = new Vector2(0.5f, 1);
        childItem.GetComponent<RectTransform>().pivot = new Vector2(0, 1);
        childItem.transform.localScale = Vector3.one;
        childItem.transform.localPosition = Vector3.zero;
        //-----設(shè)置寬高——加載數(shù)據(jù) 
        return childItem;
    private void OnAddHead()
        Data data = this.GetComponent<Test>().scrollData.GetHeadData();
        if (data != null)
            Transform first = FindFirst();
            //----first 不為 數(shù)據(jù)頭---在data中做了
            GameObject obj = GetChildItem();
            obj.GetComponent<ScrollViewItem>().RefreshData(data);
            obj.transform.SetAsFirstSibling();
            RectTransform objRect = obj.GetComponent<RectTransform>();
            float height = objRect.sizeDelta.y;
            
            if (first != null)
                obj.transform.localPosition = first.localPosition + new Vector3(0, height + spacing, 0);
            if (isStart)
                content.sizeDelta += new Vector2(0, height + spacing);
                isStart = false;
    private void OnRemoveHead()
        var scrollData = this.GetComponent<Test>().scrollData;
        if (scrollData.RemoveHeadData())
            Transform tf = FindFirst();
            if (tf != null)
                tf.gameObject.SetActive(false);
    private void OnAddEnd()
        Data data = this.GetComponent<Test>().scrollData.GetEndData();
            Transform end = FindEnd();
            //----end 不為 數(shù)據(jù)尾在data中做了
            obj.transform.SetAsLastSibling();
            float height = end.GetComponent<RectTransform>().sizeDelta.y;
            if (end != null)
                obj.transform.localPosition = end.localPosition - new Vector3(0, height + spacing, 0);
            //是否增加content高度
            if (IsAddContentH(obj.transform))
                float h = obj.GetComponent<RectTransform>().sizeDelta.y;
                content.sizeDelta += new Vector2(0, h + spacing);
    private void OnRemoveEnd()
        if (scrollData.RemoveEndData())
            Transform tf = FindEnd();
    private Transform FindFirst()
            if (content.GetChild(i).gameObject.activeSelf)
                return content.GetChild(i);
        return null;
    private Transform FindEnd()
        for (int i = content.childCount - 1; i >= 0; --i)
    private bool IsAddContentH(Transform tf)
        Vector3[] rectC = new Vector3[4];
        Vector3[] contentC = new Vector3[4];
        tf.GetComponent<RectTransform>().GetWorldCorners(rectC);
        content.GetWorldCorners(contentC);
        if (rectC[0].y < contentC[0].y)
            return true;
        return false;
}

3.ScrollItem

關(guān)鍵字段:四個委托

public Action onAddHead;
public Action onRemoveHead;
public Action onAddEnd;
public Action onRemoveEnd;

關(guān)鍵方法:

OnRecyclingItem;

1.判斷自身是否為頭尾節(jié)點;

2.判斷自身是否超界,超界需要隱藏自身;

3.判斷自身與邊界距離,是否添加節(jié)點;

關(guān)鍵API:

RectTransform.GetWorldCorners(Vector3[4])

獲取UI對象四個頂點的世界坐標(biāo),下標(biāo)對應(yīng)的位置;

全部代碼:

public class ScrollViewItem : MonoBehaviour
{
    private RectTransform viewRect;
    private RectTransform rect;
    [SerializeField]
    private float viewStart;
    [SerializeField]
    private float viewEnd;
    [SerializeField]
    private Vector3[] rectCorners;
    public Action onAddHead;
    public Action onRemoveHead;
    public Action onAddEnd;
    public Action onRemoveEnd;
    public Text nameT;
    public Text inputT;
    void Start()
    {
        Init();
    }
    public void Init()
    {
        viewRect = transform.parent.parent.GetComponent<RectTransform>();
        rect = this.GetComponent<RectTransform>();
        rectCorners = new Vector3[4];
        viewRect.GetWorldCorners(rectCorners);
        viewStart = rectCorners[1].y;
        viewEnd = rectCorners[0].y;
    }
    void Update()
    {
        OnRecyclingItem();
    }
    //超界變false;
    private void OnRecyclingItem()
    {
        rect = this.GetComponent<RectTransform>();
        rectCorners = new Vector3[4];
        rect.GetWorldCorners(rectCorners);
        if (IsFirst())
        {
            if (rectCorners[0].y > viewStart)
            {
                //隱藏頭節(jié)點  
                if (onRemoveHead != null)
                    onRemoveHead();
            }
            if (rectCorners[1].y < viewStart)
            {
                //添加頭節(jié)點-頭節(jié)點不為數(shù)據(jù)起始點
                if (onAddHead != null)
                    onAddHead();
            }
        }
        if (IsLast())
        {
            if (rectCorners[0].y > viewEnd)
            {
                //添加尾節(jié)點-尾節(jié)點不為數(shù)據(jù)末尾 
                if (onAddEnd != null)
                    onAddEnd();
            }
            if (rectCorners[1].y < viewEnd)
            {
                //隱藏尾節(jié)點 
                if (onRemoveEnd != null)
                    onRemoveEnd();
            }
        }
    }
    private bool IsFirst()
    {
        for (int i = 0; i < transform.parent.childCount; ++i)
        {
            Transform tf = transform.parent.GetChild(i);
            if (tf.gameObject.activeSelf)
            {
                if (tf == this.transform)
                {
                    return true;
                }
                break;
            }
        }
        return false;
    }
    private bool IsLast()
    {
        for (int i = transform.parent.childCount-1; i >= 0 ; i--)
        {
            Transform tf = transform.parent.GetChild(i);
            if (tf.gameObject.activeSelf)
            {
                if (tf == this.transform)
                {
                    return true;
                }
                break;
            }
        }
        return false;
    }
    public bool IsInView()
    {
        rect = this.GetComponent<RectTransform>();
        rect.GetWorldCorners(rectCorners);
        if (rectCorners[1].y > viewEnd || rectCorners[0].y < viewStart)
            return false;
        return true;
    }
    public void RefreshData(Data da)
    {
        nameT.text = da.name;
        inputT.text = da.text;
      
        Vector2 oldSize = rect.sizeDelta;
        rect.sizeDelta = new Vector2(oldSize.x, 200 + da.h);
    }
}

測試類

初始化數(shù)據(jù),隨機4中寬度的item;

void InitData()
{
    int[] hArr = new int[4];
    hArr[0] = 0;
    hArr[1] = 190;
    hArr[2] = 190 * 2;
    hArr[3] = 190 * 3;
    for (int i = 0; i < 30; ++i)
    {
        Data da = new Data();
        da.name = "小紫蘇" + i.ToString();
        da.text = "000000" + i.ToString();
        int index = UnityEngine.Random.Range(0, 3);
        da.h = hArr[index];
        scrollData.allDatas.Add(da);
    }
}

添加三個按鈕,及相應(yīng)的響應(yīng)方法;

1.添加20組數(shù)據(jù)

private void AddData()
{
    int[] hArr = new int[4];
    hArr[0] = 0;
    hArr[1] = 190;
    hArr[2] = 190 * 2;
    hArr[3] = 190 * 3;
    Data[] newData = new Data[20];
    for (int i = 0; i < 20; ++i)
    {
        Data da = new Data();
        da.name = "小紫蘇" + i.ToString();
        da.text = "000000" + i.ToString();
        int index = UnityEngine.Random.Range(0, 3);
        da.h = hArr[index];
        newData[i] = da;
    }
    scrollDat

回到頂部或底部需要有過程,因此需要在update中運行,也可以用插值;

2.回到頂部

private void OnGoHead()
{
    if (isGoHead)
        isGoHead = false;
    else
        isGoHead = true;
}
private void OnGoLast()
{
    if (isGoLast)
        isGoLast = false;
    else
        isGoLast = true;
}

3.回到底部

private void GoHead()
{
    if (!isGoHead)
        return;
    
    float curPos = scroll.verticalNormalizedPosition;
    if (curPos != 1)
    {
        curPos += 0.01f;
        if (curPos >= 1)
        {
            curPos = 1;
            isGoHead = false;
        }
        scroll.verticalNormalizedPosition = curPos;
    }
}
private void GoLast()
{
    if (!isGoLast)
        return;
    float curPos = scroll.verticalNormalizedPosition;
    if (curPos != 0)
    {
        curPos -= 0.01f;
        if (curPos <= 0)
        {
            curPos = 0;
            isGoLast = false;
        }
        scroll.verticalNormalizedPosition = curPos;
    }
}

坑點

1.ScrollView回滾設(shè)置延遲;

回滾判斷是通過verticalNormalizedPosition的API,更改這個值后需要間隔一幀才會修改,因為可能導(dǎo)致判斷兩次;

解決方法,延遲調(diào)用1s——Invoke;

2.錨點設(shè)置;

錨點的設(shè)置以及UI的自適應(yīng)會直接影響項目回滾的方向和位置;

大部分位置出錯都是因為錨點設(shè)置錯誤;

3.數(shù)據(jù)需要網(wǎng)絡(luò)請求,自適應(yīng)會失效;

網(wǎng)絡(luò)數(shù)據(jù)一般都是異步,所以判斷會做多次,因此數(shù)據(jù)上要求提前計算好item的寬度;

項目工程我上傳到Gitee,可自行下載學(xué)習(xí);https://gitee.com/small-perilla/scroll-view

以上是我對滾動復(fù)用組件的總結(jié),如果有更好的意見,歡迎給作者評論留言;

到此這篇關(guān)于Unity無限滾動復(fù)用列表的文章就介紹到這了,更多相關(guān)Unity無限滾動列表內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • C#實現(xiàn)的微信網(wǎng)頁授權(quán)操作邏輯封裝示例

    C#實現(xiàn)的微信網(wǎng)頁授權(quán)操作邏輯封裝示例

    這篇文章主要介紹了C#實現(xiàn)的微信網(wǎng)頁授權(quán)操作邏輯封裝,分析了微信網(wǎng)頁授權(quán)操作的原理、步驟并給出了C#實現(xiàn)的網(wǎng)頁授權(quán)操作邏輯封裝類,需要的朋友可以參考下
    2016-10-10
  • C#運算符大全_各種運算符號的概述及作用

    C#運算符大全_各種運算符號的概述及作用

    以下是對C#中各種運算符號的說明及作用進行了詳細的總結(jié)介紹,需要的朋友可以過來參考下,希望對大家有所幫助
    2013-10-10
  • C#實現(xiàn)winform中RichTextBox在指定光標(biāo)位置插入圖片的方法

    C#實現(xiàn)winform中RichTextBox在指定光標(biāo)位置插入圖片的方法

    這篇文章主要介紹了C#實現(xiàn)winform中RichTextBox在指定光標(biāo)位置插入圖片的方法,涉及RichTextBox控件及剪切板的相關(guān)操作技巧,非常簡單實用,需要的朋友可以參考下
    2016-06-06
  • C#實現(xiàn)用棧求逆序的方法示例

    C#實現(xiàn)用棧求逆序的方法示例

    這篇文章主要介紹了C#實現(xiàn)用棧求逆序的方法,涉及C#數(shù)據(jù)結(jié)構(gòu)中棧的壓入與取出相關(guān)操作技巧,需要的朋友可以參考下
    2017-07-07
  • C# 中的 is 真的是越來越強大越來越語義化(推薦)

    C# 中的 is 真的是越來越強大越來越語義化(推薦)

    這篇文章主要介紹了C# 中的 is 真的是越來越強大越來越語義化,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-09-09
  • WPF實現(xiàn)動畫效果(六)之路徑動畫

    WPF實現(xiàn)動畫效果(六)之路徑動畫

    這篇文章介紹了WPF實現(xiàn)動畫效果之路徑動畫,文中通過示例代碼介紹的非常詳細。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-06-06
  • C#字符串內(nèi)存駐留機制分析

    C#字符串內(nèi)存駐留機制分析

    這篇文章介紹了C#字符串內(nèi)存駐留機制,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-01-01
  • Unity3D基于UGUI實現(xiàn)虛擬搖桿

    Unity3D基于UGUI實現(xiàn)虛擬搖桿

    這篇文章主要為大家詳細介紹了Unity3D基于UGUI實現(xiàn)虛擬搖桿,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-04-04
  • WebService 的簡單封裝接口調(diào)用方法

    WebService 的簡單封裝接口調(diào)用方法

    這篇文章主要介紹了WebService 的簡單封裝接口調(diào)用方法,主要是通過簡單的sql語句來查詢數(shù)據(jù)庫,從而返回dataset,十分簡單實用,有需要的小伙伴可以參考下。
    2015-06-06
  • C# 利用代理爬蟲網(wǎng)頁的實現(xiàn)方法

    C# 利用代理爬蟲網(wǎng)頁的實現(xiàn)方法

    這篇文章主要介紹了C# 利用代理爬網(wǎng)頁的實現(xiàn)方法的相關(guān)資料,希望通過本能幫助到大家實現(xiàn)這樣的功能,需要的朋友可以參考下
    2017-10-10

最新評論