Unity 無限滾動

先看一下實現的效果:

在這裏插入圖片描述

接下來詳細講解一下具體實現步驟:

一、 創建好Content以及初始個數的item
  1. 按照預設體的寬/高創建出Content的總長度。
  2. 根據ViewPort,也就是綠色背景的寬度來創建初始個數的預設體。也就是ViewPort_Witdh / item_Width 向上取整並 + 1,顯示部分是可以被看到的,但是在滑動那過程中,需要有一個臨時item來改變位置。如上圖未滑動時候第5個item,在滑動過程中,這個臨時的item會實時改變。
  3. 準備要顯示的數據,爲發生越界刷新數據用。
二、滑動時候判斷越界情況
  1. ScrollView中屬性onValueChanged提供了滑動時候的回調,返回的是Vector2的x,y是[0,1]的值,表明當前滑動到當前的相對位置。每幀需要記錄當前的值,以便下一幀判斷是左滑還是右滑。
  2. 先看下圖:在這裏插入圖片描述
    紅色框爲創建好的Content,綠色爲ViewPort。稍微思考知道:items(假想出所有未創建出來的Item)是跟着Content走的,每個item的位置在Content中是固定不變的,ViewPort是相對於Content實時改變的。由此可得出,判斷越界就是判斷兩端顯示的item在Content中的座標和ViewPort兩個邊在Content中的關係。判斷越界的條件方式可以不同,本博客判斷的條件如下圖:在這裏插入圖片描述
    左邊越界的條件:顯示的items列表中,最左邊的item的右邊(紅色的邊)超過Viewport的左邊位置爲越界。
    上面的解釋爲向左滑動時候的越界處理,右邊則同理(會有稍許不同)。

三、更新位置、UI

  1. 監測到越界之後要更新已經越界的item位置,如上圖第一個位置設置到最後,此時需要計算出下“最後”在Content中的位置,這裏的“第一個”和“最後一個”分別要用兩個變量 leftIndex 和 rightIndex 來記錄,這兩個變量表明瞭當前顯示的items是 leftIndex 到 rightIndex 之間的。
  2. 由於只有有限個item,但是數據是無限、不固定的,所以在顯示之前要將數據準備好,發生越界時候通過 leftIndex 和 rightIndex 來更新數據,也就是顯示 leftIndex 到 rightIndex 中的數據。

以上只是本片博客大體思路,實現無限滾動有很多種方式,不同之處大概可分爲:

  1. 預留item的個數
  2. 座標的參考、定義
  3. 越界的處理判斷

歡迎去我的 github博客 觀光

下面是功能的代碼實現,簡單寫的DEMO,稍有不嚴謹之處僅供參考:
LoopScrollView.cs

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

namespace EamonnLi.LoopScrollView{
	public class LoopScrollView : MonoBehaviour {
		public RectTransform content;
		public ScrollRect scrollRect;
		public RectTransform scrollRectRt;
		public GameObject loopObject;
		RectTransform loopRt;

		

		int count;
		public float spacing = 0;

		int leftIndex = 0;
		int rightIndex = 0;
		float leftPosOfContent = 0f;
		float rightPosOfContent = 0;
		int currentIndex = 0;

		List<ItemData> datas =  new List<ItemData>();
		List<RectTransform> loopObjectsList = new List<RectTransform>();
		void Awake(){
			loopRt = loopObject.GetComponent<RectTransform>();
		}

		public void InitLoopScrollView(int count, List<ItemData> dataList){
			this.count = count;
			datas = dataList;
			CreateContent();
			CaculateMinCount();
			CreateLoopObject();
			UpdateContent();
			scrollRect.onValueChanged.AddListener(OnScrolled);
		}

		void UpdateContent(){
			for (int i = 0; i < loopObjectsList.Count; i++)
			{
				ItemData data = datas[i + leftIndex];
				loopObjectsList[i].GetComponent<LoopItemController>().UpdateUI(data);
			}
			for (int i = leftIndex; i < rightIndex; i++)
			{
				
			}
		}

		float contentWidth = 0f;
		void CreateContent(){
			Vector2 itemSize = loopRt.sizeDelta;
			contentWidth = count * itemSize.x + (count - 1) * spacing;
			content.sizeDelta = new Vector2(contentWidth, content.sizeDelta.y);
		}

		int minCount = 0;
		void CaculateMinCount(){
			float minWidth = scrollRect.GetComponent<RectTransform>().sizeDelta.x;
			Debug.Log("minWidth : " + minWidth);
			minCount = 1;
			minCount = minCount + (int)Mathf.Floor(minWidth / (loopRt.sizeDelta.x + spacing));
			minCount++;
		}

		void CreateLoopObject(){
			int createCount = 0;
			if (count < minCount - 1){
				leftIndex = 0;
				rightIndex = count;
				leftPosOfContent = 0f;
				rightPosOfContent = scrollRectRt.sizeDelta.x;
				createCount = count;
			}else{
				leftIndex = 0;
				rightIndex = minCount;
				leftPosOfContent = 0f;
				rightPosOfContent = scrollRectRt.sizeDelta.x;
				createCount = minCount;
			}
			Debug.Log("minCount : " + minCount);
			Debug.Log("createCount : " + createCount);
			for (int i = 0; i < createCount; i++)
			{
				GameObject loop = Instantiate(loopObject);
				RectTransform loopRectTransform = loop.GetComponent<RectTransform>();
				loopRectTransform.SetParent(content);
				loopRectTransform.localScale = Vector3.one;
				loopObjectsList.Add(loopRectTransform);
				SetPositionOfIndex(loopRectTransform, i);
			}
		}

		void SetPositionOfIndex(RectTransform rt, int index){
			float x = (rt.sizeDelta.x + spacing) * index;
			Vector3 anchoredPosition = new Vector3(x, -300, 0);
			rt.anchoredPosition = anchoredPosition;
		}

		float lastValuX = 0;
		void OnScrolled(Vector2 value){
			leftPosOfContent = - content.anchoredPosition.x;
			rightPosOfContent = leftPosOfContent + scrollRectRt.sizeDelta.x;
			// if (leftIndex <= 0 || rightIndex >= count - 1)
			// 	return;
			float currentValueX = value.x;
			Debug.Log("count    +++ " + count + "    ____rightIndex " + rightIndex);
			if (currentValueX > lastValuX){
				// 手指往左滑動,content往左走,判斷 loopObjectsList[0] 的右邊是否出去
				if (rightIndex >= count) 
					return;
				RectTransform rt = loopObjectsList[0];
				float rtRightPos = rt.sizeDelta.x + rt.anchoredPosition.x;
				if (rtRightPos < leftPosOfContent){
					loopObjectsList.RemoveAt(0);
					loopObjectsList.Add(rt);
					SetPositionOfIndex(rt, rightIndex);
					rightIndex += 1;
					leftIndex += 1;
					UpdateContent();
				}
			}
			else if (currentValueX < lastValuX){
				// 手指往右滑動,content往右走,判斷 loopObjectsList[loopObjectsList.Count - 1];] 的左邊是否出去
				if (leftIndex <= 0) 
					return;
				RectTransform rt = loopObjectsList[loopObjectsList.Count - 1];
				float rtLeftPos = rt.anchoredPosition.x;
				if (rtLeftPos > rightPosOfContent){
					loopObjectsList.RemoveAt(loopObjectsList.Count - 1);
					loopObjectsList.Insert(0, rt);
					rightIndex -= 1;
					leftIndex -= 1;
					SetPositionOfIndex(rt, leftIndex);
					UpdateContent();
				}
			}
			lastValuX = currentValueX;
		}
	}
}

LoopItemController.cs

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

namespace EamonnLi.LoopScrollView{
	public class LoopItemController : MonoBehaviour {
		public Image background;

		public Image icon;

		public Text title;

		public void UpdateUI(ItemData data){
			icon.gameObject.SetActive(false);
			title.gameObject.SetActive(false);
			background.sprite = data.bgSprite;
			icon.color = new Color(data.index / 100.0f, 1 - data.index / 100.0f, 1, 1);
			title.text = data.index.ToString();
		}
	}
}

TestUseLoopScrollView.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using EamonnLi.LoopScrollView;

public class ItemData{
	public int index;
	public Sprite iconSprite;
	public Sprite bgSprite;

	public ItemData(int index, Sprite iconSprite, Sprite bgSprite){
		this.index = index;
		this.iconSprite = iconSprite;
		this.bgSprite = bgSprite;
	}
}

public class TestUseLoopScrollView : MonoBehaviour {

	public LoopScrollView loopScrollView;
	void Awake(){
		int count = 100;
		List<ItemData> datas = new List<ItemData>();
		for (int i = 0; i < count; i++)
		{
			datas.Add(new ItemData(i, null, null));
		}
		loopScrollView.InitLoopScrollView(count, datas);
	}
}

發佈了23 篇原創文章 · 獲贊 6 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章