Files
EnglewoodLAB/Assets/Sample/InfiniteScroll/Scripts/InfiniteScrollSample.cs
2026-03-10 11:35:30 +09:00

434 lines
18 KiB
C#

namespace Gpm.Ui.Sample
{
using Gpm.Ui;
using System;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// InfiniteScroll의 다양한 기능을 보여주는 예제 클래스입니다.
/// 데이터 추가, 삭제, 업데이트, 필터링, 특정 위치로 스크롤하는 방법을 구현합니다.
/// </summary>
public class InfiniteScrollSample : MonoBehaviour
{
// Unity 에디터의 인스펙터 창에서 연결할 UI 요소들입니다.
[Header("UI Components")]
[Tooltip("세로 방향으로 스크롤되는 InfiniteScroll 컴포넌트입니다.")]
public InfiniteScroll verticalScrollList = null;
[Tooltip("가로 방향으로 스크롤되는 InfiniteScroll 컴포넌트입니다.")]
public InfiniteScroll horizontalScrollList = null;
[Tooltip("동작 로그를 표시할 UI Text 컴포넌트입니다.")]
public Text logText = null;
[Tooltip("현재 데이터 개수를 표시할 UI Text 컴포넌트입니다.")]
public Text dataCount = null;
[Tooltip("로그 텍스트를 담고 있는 ScrollRect입니다. 로그가 길어질 경우 스크롤을 위함입니다.")]
public ScrollRect logScrollRect = null;
[Tooltip("특정 데이터로 이동할 때 사용할 Dropdown UI입니다.")]
public Dropdown moveDataSelect = null;
[Tooltip("스크롤 이동 애니메이션 타입을 선택할 Dropdown UI입니다.")]
public Dropdown moveDataTypeSelect = null;
[Tooltip("스크롤 이동 시간을 입력할 InputField UI입니다.")]
public InputField moveDataTime = null;
[Tooltip("특정 아이템 인덱스로 이동할 때 사용할 InputField UI입니다.")]
public InputField moveItemSelect = null;
[Tooltip("현재 생성된 아이템(UI 오브젝트)의 개수를 표시할 Text 컴포넌트입니다.")]
public Text itemCount = null;
// 내부 상태 관리를 위한 변수들입니다.
/// <summary>
/// 새로 추가되는 데이터에 고유한 인덱스를 부여하기 위한 카운터입니다.
/// </summary>
private int index = 0;
/// <summary>
/// 총 몇 개의 데이터가 삽입되었는지 추적하는 카운터입니다.
/// </summary>
private int insertCount = 0;
/// <summary>
/// 총 몇 개의 데이터가 제거되었는지 추적하는 카운터입니다.
/// </summary>
private int removeCount = 0;
/// <summary>
/// 스크롤 뷰에 표시될 모든 데이터를 관리하는 리스트입니다.
/// InfiniteScroll은 이 리스트의 데이터를 기반으로 UI 아이템을 생성하고 재활용합니다.
/// </summary>
private List<TestItemData> dataList = new List<TestItemData>();
/// <summary>
/// 화면에 로그를 표시하기 위한 StringBuilder입니다.
/// 문자열을 반복적으로 합칠 때 성능상 이점을 가집니다.
/// </summary>
private StringBuilder log = new StringBuilder();
/// <summary>
/// 스크립트가 처음 활성화될 때 호출되는 Unity 생명주기 메서드입니다.
/// 주로 이벤트 리스너를 등록하고 초기화 작업을 수행합니다.
/// </summary>
private void Start()
{
// 세로 스크롤 리스트에서 아이템이 선택되었을 때 호출될 콜백 함수를 등록합니다.
// 사용자가 아이템을 클릭하면 해당 아이템의 데이터 정보가 로그에 표시됩니다.
verticalScrollList.AddSelectCallback((data) =>
{
// 콜백으로 받은 데이터(data)는 InfiniteScrollData 타입이므로,
// 실제 데이터 타입인 TestItemData로 형변환하여 사용합니다.
AddLog(string.Format("vertical select data : {0}", ((TestItemData)data).index.ToString()));
});
// 가로 스크롤 리스트에 대한 선택 콜백도 동일하게 등록합니다.
horizontalScrollList.AddSelectCallback((data) =>
{
AddLog(string.Format("horizontal select data : {0}", ((TestItemData)data).index.ToString()));
});
// 'MoveToData' 드롭다운의 값이 변경될 때 호출될 리스너를 등록합니다.
// 선택된 옵션의 인덱스를 기반으로 해당 데이터 위치로 스크롤을 이동시킵니다.
moveDataSelect.onValueChanged.AddListener((option) =>
{
MoveToDataIndex(option);
});
// 'MoveToItem' 입력 필드의 값이 변경될 때 호출될 리스너를 등록합니다.
// 입력된 아이템 인덱스로 스크롤을 이동시킵니다.
moveItemSelect.onValueChanged.AddListener((text) =>
{
int index = 0;
if (int.TryParse(text, out index) == false)
{
AddLog("Time is not Number");
}
MoveToItemIndex(index);
});
}
/// <summary>
/// 스크롤 뷰가 마지막 데이터 위치에 도달했는지 확인하고 결과를 로그에 출력합니다.
/// InfiniteScroll.IsMoveToLastData() API의 사용 예시입니다.
/// </summary>
public void IsMoveToLastData()
{
AddLog(string.Format("Is move to last data vertical:{0},horizontal:{1}",
verticalScrollList.IsMoveToLastData().ToString(), horizontalScrollList.IsMoveToLastData().ToString()));
}
/// <summary>
/// 새로운 데이터를 생성하여 스크롤 리스트의 끝에 추가합니다.
/// InfiniteScroll.InsertData() API의 사용 예시입니다.
/// </summary>
public void InsertData()
{
// 1. 새로운 데이터 객체를 생성하고 고유 인덱스를 할당합니다.
TestItemData data = new TestItemData();
data.index = index++;
// 2. 내부 데이터 리스트(dataList)에 추가하여 데이터를 관리합니다.
dataList.Add(data);
// 3. InfiniteScroll 컴포넌트에 데이터를 삽입하여 UI에 반영합니다.
// 이 호출로 인해 스크롤 뷰는 새로운 아이템을 표시할 준비를 합니다.
verticalScrollList.InsertData(data);
horizontalScrollList.InsertData(data);
// 4. 'MoveToData' 드롭다운에도 해당 데이터 인덱스를 옵션으로 추가합니다.
var options = new List<Dropdown.OptionData>() { new Dropdown.OptionData(data.index.ToString()) };
moveDataSelect.AddOptions(options);
// 5. 카운터를 업데이트하고 로그를 남깁니다.
++insertCount;
UpdateDataCount();
AddLog(string.Format("Insert Data : {0}", index - 1));
}
/// <summary>
/// 스크롤 리스트의 모든 데이터를 제거합니다.
/// InfiniteScroll.Clear() API의 사용 예시입니다.
/// </summary>
public void Clear()
{
// 1. InfiniteScroll의 모든 데이터를 제거합니다.
// 이 메서드는 내부 데이터 목록과 화면의 UI 아이템을 모두 정리합니다.
verticalScrollList.Clear();
horizontalScrollList.Clear();
// 2. 이 예제에서 사용하는 내부 데이터 리스트와 UI 요소도 함께 정리합니다.
removeCount += dataList.Count;
dataList.Clear();
moveDataSelect.ClearOptions();
// 3. 카운터를 업데이트하고 로그를 남깁니다.
UpdateDataCount();
AddLog("Clear Data");
}
/// <summary>
/// 데이터 리스트에서 무작위로 하나의 데이터를 선택하여 제거합니다.
/// InfiniteScroll.RemoveData() API의 사용 예시입니다.
/// </summary>
public void Remove()
{
if (dataList.Count <= 0)
{
return;
}
// 1. 무작위로 데이터 인덱스를 선택합니다.
int dataIndex = GetDataIndexByRandom();
TestItemData data = dataList[dataIndex];
// 2. InfiniteScroll에서 해당 데이터를 제거합니다.
// 스크롤 뷰는 이 데이터를 표시하던 UI 아이템을 숨기거나 재활용합니다.
verticalScrollList.RemoveData(data);
horizontalScrollList.RemoveData(data);
// 3. 내부 데이터 리스트와 UI 드롭다운에서도 해당 데이터를 제거합니다.
dataList.Remove(data);
int optionIndex = moveDataSelect.options.FindIndex(p => p.text.Equals(data.index.ToString()));
if (optionIndex != -1)
{
moveDataSelect.options.RemoveAt(optionIndex); // 드롭다운 표시값 갱신
}
// 4. 카운터를 업데이트하고 로그를 남깁니다.
removeCount++;
UpdateDataCount();
AddLog(string.Format("RemoveChild data : {0}", data.index));
}
/// <summary>
/// 무작위로 하나의 데이터를 선택하여 내용을 업데이트합니다.
/// InfiniteScroll.UpdateData() API의 사용 예시입니다.
/// </summary>
public void UpdateData()
{
if (dataList.Count <= 0)
{
return;
}
// 1. 무작위로 업데이트할 데이터를 선택합니다.
int dataIndex = GetDataIndexByRandom();
TestItemData data = dataList[dataIndex];
// 2. 데이터의 내용을 변경합니다. (예: 설명 텍스트 업데이트)
data.description = string.Format("Updated : {0}", DateTime.Now.ToString("T"));
// 3. InfiniteScroll에 데이터가 변경되었음을 알립니다.
// 이 호출을 받은 스크롤 뷰는 해당 데이터를 표시하는 UI 아이템을 찾아 내용을 새로고침합니다.
verticalScrollList.UpdateData(data);
horizontalScrollList.UpdateData(data);
AddLog(string.Format("Update data : {0}", data.index));
}
/// <summary>
/// 스크롤 리스트의 모든 데이터를 업데이트합니다.
/// InfiniteScroll.UpdateAllData() API의 사용 예시입니다.
/// </summary>
public void UpdateAllData()
{
// 1. 내부 데이터 리스트의 모든 항목을 순회하며 내용을 변경합니다.
for (int index = 0; index < dataList.Count; ++index)
{
dataList[index].description = string.Format("Updated : {0}", DateTime.Now.ToString("T"));
}
// 2. InfiniteScroll에 모든 데이터가 변경되었음을 알립니다.
// 스크롤 뷰는 현재 화면에 보이는 모든 아이템을 새로고침합니다.
verticalScrollList.UpdateAllData();
horizontalScrollList.UpdateAllData();
AddLog("Update all data");
}
/// <summary>
/// 데이터 및 아이템 개수를 UI에 최신 정보로 업데이트합니다.
/// </summary>
private void UpdateDataCount()
{
// dataList.Count는 원본 데이터의 총 개수입니다.
dataCount.text = string.Format("Data Count : {0} (insert[{1}] remove[{2}])", dataList.Count, insertCount, removeCount);
// InfiniteScroll.GetItemCount()는 필터링이 적용된 후, 실제 화면에 표시될 아이템의 개수입니다.
itemCount.text = verticalScrollList.GetItemCount().ToString();
}
/// <summary>
/// 스크롤을 가장 처음 위치로 이동시킵니다.
/// </summary>
public void MoveToFirstData()
{
verticalScrollList.MoveToFirstData();
horizontalScrollList.MoveToFirstData();
AddLog("Move to first data");
}
/// <summary>
/// 스크롤을 가장 마지막 위치로 이동시킵니다.
/// </summary>
public void MoveToLastData()
{
verticalScrollList.MoveToLastData();
horizontalScrollList.MoveToLastData();
AddLog("Move to last data");
}
/// <summary>
/// 지정된 '아이템 인덱스'로 스크롤을 이동시킵니다.
/// '아이템 인덱스'는 필터링과 정렬이 적용된 후 화면에 보이는 순서입니다.
/// </summary>
/// <param name="itemIndex">이동할 대상의 아이템 인덱스</param>
private void MoveToItemIndex(int itemIndex)
{
float time = 0;
if (float.TryParse(moveDataTime.text, out time) == false)
{
AddLog("Time is not Number");
}
// MoveTo API를 사용하여 (아이템 인덱스, 이동 타입, 이동 시간)을 인자로 전달합니다.
verticalScrollList.MoveTo(itemIndex, (InfiniteScroll.MoveToType)moveDataTypeSelect.value, time);
horizontalScrollList.MoveTo(itemIndex, (InfiniteScroll.MoveToType)moveDataTypeSelect.value, time);
AddLog(string.Format("Move to ItemIndex : {0}", itemIndex));
}
/// <summary>
/// 지정된 '데이터 인덱스'로 스크롤을 이동시킵니다.
/// '데이터 인덱스'는 원본 데이터 리스트(dataList)에서의 순서입니다.
/// </summary>
/// <param name="dataIndex">이동할 대상의 데이터 인덱스</param>
private void MoveToDataIndex(int dataIndex)
{
float time = 0;
if (float.TryParse(moveDataTime.text, out time) == false)
{
AddLog("Time is not Number");
}
// MoveToFromDataIndex 또는 MoveTo(data) API를 사용할 수 있습니다.
// 여기서는 두 가지 방법을 무작위로 선택하여 보여줍니다.
if (UnityEngine.Random.Range(0, 2) == 0)
{
// 방법 1: 데이터 인덱스를 직접 사용
verticalScrollList.MoveToFromDataIndex(dataIndex, (InfiniteScroll.MoveToType)moveDataTypeSelect.value, time);
horizontalScrollList.MoveToFromDataIndex(dataIndex, (InfiniteScroll.MoveToType)moveDataTypeSelect.value, time);
}
else
{
// 방법 2: 데이터 객체를 직접 사용
TestItemData data = dataList[dataIndex];
verticalScrollList.MoveTo(data, (InfiniteScroll.MoveToType)moveDataTypeSelect.value, time);
horizontalScrollList.MoveTo(data, (InfiniteScroll.MoveToType)moveDataTypeSelect.value, time);
}
AddLog(string.Format("Move to DataIndex : {0}", dataIndex));
}
/// <summary>
/// 홀수 인덱스를 가진 데이터만 표시하도록 필터를 설정합니다.
/// InfiniteScroll.SetFilter() API의 사용 예시입니다.
/// </summary>
public void FilterOdd()
{
// Predicate<InfiniteScrollData>는 데이터를 받아 bool을 반환하는 함수입니다.
// true를 반환하는 데이터만 화면에 표시됩니다.
Predicate<InfiniteScrollData> func = (data) =>
{
if (data is TestItemData testData)
{
// 인덱스가 짝수인 데이터만 남깁니다 (홀수번째 아이템).
return (testData.index % 2) == 0;
}
return false;
};
// 생성한 필터 함수를 스크롤 뷰에 설정합니다.
verticalScrollList.SetFilter(func);
horizontalScrollList.SetFilter(func);
UpdateDataCount();
}
/// <summary>
/// 짝수 인덱스를 가진 데이터만 표시하도록 필터를 설정합니다.
/// </summary>
public void FilterEven()
{
Predicate<InfiniteScrollData> func = (data) =>
{
if (data is TestItemData testData)
{
// 인덱스가 홀수인 데이터만 남깁니다 (짝수번째 아이템).
return (testData.index % 2) == 1;
}
return false;
};
verticalScrollList.SetFilter(func);
horizontalScrollList.SetFilter(func);
UpdateDataCount();
}
/// <summary>
/// 모든 필터를 제거하고 모든 데이터를 표시합니다.
/// </summary>
public void FilterAll()
{
// SetFilter에 null을 전달하면 필터가 해제됩니다.
verticalScrollList.SetFilter(null);
horizontalScrollList.SetFilter(null);
UpdateDataCount();
}
/// <summary>
/// 화면의 로그 UI에 새로운 텍스트를 추가하고, 로그 창을 맨 아래로 스크롤합니다.
/// </summary>
/// <param name="text">로그에 추가할 문자열</param>
private void AddLog(string text)
{
log.AppendLine(text);
logText.text = log.ToString();
// 로그가 추가될 때마다 스크롤을 맨 아래로 내려 최신 로그를 보여줍니다.
// verticalNormalizedPosition = 0.0f가 맨 아래를 의미합니다.
logScrollRect.verticalNormalizedPosition = 0.0f;
}
/// <summary>
/// 현재 데이터 리스트에서 유효한 랜덤 인덱스를 반환합니다.
/// </summary>
/// <returns>0과 (dataList.Count - 1) 사이의 랜덤 정수</returns>
private int GetDataIndexByRandom()
{
return UnityEngine.Random.Range(0, dataList.Count);
}
}
}