본문 바로가기
개발/Unity

[Unity] 재사용 스크롤 리스트

by 김뜬뜬 2025. 10. 24.

안녕하세요 주인장입니다.

 

오늘은 Unity 재사용 스크롤 리스트에 대해서 다뤄보겠습니다.

 

ScrollView를 사용할 때 리스트 아이템이 많아지면,
하나씩 Instantiate해서 보여주는 방식은 금방 프레임 드랍과 메모리 낭비로 이어집니다.

이를 해결하기 위해 아이템을 재활용하는 방식을 사용하면,
적은 오브젝트로도 수천 개의 데이터를 효율적으로 표시할 수 있습니다.

 

이번 글에서는 개념 설명보다는
실제 코드와 결과를 중심으로,
재사용 스크롤 리스트가 어떻게 동작하는지를 간단히 확인해보겠습니다.

 

 

1. 변수선언

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

public class RecycleScrollView : MonoBehavior
{
	private ScrollRect _scroll;

	public RankingItem itemPrefab;
	public float itemHeight;
	public float offSet = 10f;

	public List<int> dataList;
	private List<RankingItem> itemList;
}

 

변수설명

  • _scroll → 사용할 ScrollView에 붙은 ScrollRect 컴포넌트입니다.
  • itemPrefab → 재사용 리스트에 들어갈 슬롯 프리팹입니다.
  • itemHeight → 슬롯 프리팹의 세로 높이 값입니다.
  • offset → 슬롯 간의 간격입니다, 임의로 10f로 지정했습니다.
  • dataList → 동적으로 생성할 데이터 리스트입니다.
    실제 프로젝트에서는 보통 API 응답(Json)을 역직렬화한 데이터를 사용합니다.
  • itemList → 재사용 스크롤 리스트에 사용될 슬롯 객체들의 리스트입니다.

 

2. 핵심 기능 코드

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

public class RecycleScrollView : MonoBehaviour
{
    private ScrollRect _scroll;

    public RankingItem itemPrefab;
    public float itemHeight;
    public float offSet = 10f;

    public List<int> dataList;
    private List<RankingItem> itemList;

    /// <summary>
    /// ScrollRect 컴포넌트 초기화 및 아이템 높이 계산
    /// </summary>
    void Awake()
    {
        _scroll = GetComponent<ScrollRect>();
        itemHeight = itemPrefab.GetComponent<RectTransform>().rect.height;
        Debug.Log($"##### itemHeight : {itemHeight}");
        Debug.Log($"##### scrollRect.rect.height / (itemHeight + offSet) : {_scroll.GetComponent<RectTransform>().rect.height / (itemHeight + offSet)}");
    }

    /// <summary>
    /// 더미 데이터 생성 및 초기 아이템 세팅
    /// </summary>
    private void Start()
    {
        dataList.Clear();
        for (int i = 0; i < 10000; i++)
        {
            dataList.Add(i);
        }
        CreateItem();
        SetContentHeight();
    }

    /// <summary>
    /// 스크롤 시 아이템 재배치 및 데이터 갱신
    /// </summary>
    private void Update()
    {
        foreach (RankingItem item in itemList)
        {
            bool isChanged = RelocationItem(item);
            if (isChanged)
            {
                int index = (int)(-item.transform.localPosition.y / (itemHeight + offSet));
                SetData(item, index);
            }
        }
    }

    /// <summary>
    /// 화면에 표시할 아이템 풀 생성
    /// </summary>
    private void CreateItem()
    {
        RectTransform scrollRect = _scroll.GetComponent<RectTransform>();
        itemList = new List<RankingItem>();

        int itemCount;
        if (dataList.Count <= scrollRect.rect.height / (itemHeight + offSet))
        {
            itemCount = dataList.Count;
        }
        else
        {
            itemCount = (int)(scrollRect.rect.height / (itemHeight + offSet) + 2);
        }

        for (int i = 0; i < itemCount; i++)
        {
            RankingItem item = Instantiate<RankingItem>(itemPrefab, _scroll.content);
            itemList.Add(item);

            item.transform.localPosition = new Vector3(0, -itemHeight * 0.5f - i * (itemHeight + offSet));
            SetData(item, i);
        }
    }

    /// <summary>
    /// ScrollView 콘텐츠 영역의 전체 높이 설정
    /// </summary>
    private void SetContentHeight()
    {
        _scroll.content.sizeDelta = new Vector2(_scroll.content.sizeDelta.x, dataList.Count * (itemHeight + offSet) - offSet);
    }

    /// <summary>
    /// 화면 밖으로 벗어난 아이템을 위/아래로 재배치하여 재사용
    /// </summary>
    private bool RelocationItem(RankingItem item)
    {
        RectTransform scrollRect = _scroll.GetComponent<RectTransform>();
        float scrollHeight = scrollRect.rect.height;
        float contentY = _scroll.content.anchoredPosition.y;

        if (item.transform.localPosition.y + contentY > itemHeight + offSet)
        {
            item.transform.localPosition -= new Vector3(0, itemList.Count * (itemHeight + offSet) * 2f);
            RelocationItem(item);
            return true;
        }
        else if (item.transform.localPosition.y + contentY < -scrollHeight - (itemHeight + offSet))
        {
            item.transform.localPosition += new Vector3(0, itemList.Count * (itemHeight + offSet));
            RelocationItem(item);
            return true;
        }
        else return false;
    }

    /// <summary>
    /// 아이템에 표시할 데이터 세팅
    /// </summary>
    private void SetData(RankingItem item, int index)
    {
        if (index < 0 || index >= dataList.Count)
        {
            item.gameObject.SetActive(false);
            return;
        }

        item.gameObject.SetActive(true);
        item.RankNumber.text = $"{index}";
        item.UserName.text = $"테스트 닉네임 {index}";
    }
}

 

 

3. 데이터 스크립트 (용도에 따라 커스터마이징 필요)

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

public class RankingItem : MonoBehaviour
{
    public Text RankNumber;
    public Text UserName;
}

 

4. 결과

https://youtu.be/te6moRBZR_o

 

 

오늘의 기록은 여기까지. 주인장은 이만 로그아웃 합니다. 모두 평안한 밤 보내세요!