Unity 无限滚动

标签: 技术分享  Unity3D

先看一下实现的效果:

在这里插入图片描述

接下来详细讲解一下具体实现步骤:

一、 创建好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);
	}
}

原文链接:加载失败,请重新获取