namespace Gpm.Ui.Sample { using Gpm.Ui; using System; using System.Collections.Generic; using System.Text; using UnityEngine; using UnityEngine.UI; /// /// InfiniteScroll의 다양한 기능을 보여주는 예제 클래스입니다. /// 데이터 추가, 삭제, 업데이트, 필터링, 특정 위치로 스크롤하는 방법을 구현합니다. /// 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; // 내부 상태 관리를 위한 변수들입니다. /// /// 새로 추가되는 데이터에 고유한 인덱스를 부여하기 위한 카운터입니다. /// private int index = 0; /// /// 총 몇 개의 데이터가 삽입되었는지 추적하는 카운터입니다. /// private int insertCount = 0; /// /// 총 몇 개의 데이터가 제거되었는지 추적하는 카운터입니다. /// private int removeCount = 0; /// /// 스크롤 뷰에 표시될 모든 데이터를 관리하는 리스트입니다. /// InfiniteScroll은 이 리스트의 데이터를 기반으로 UI 아이템을 생성하고 재활용합니다. /// private List dataList = new List(); /// /// 화면에 로그를 표시하기 위한 StringBuilder입니다. /// 문자열을 반복적으로 합칠 때 성능상 이점을 가집니다. /// private StringBuilder log = new StringBuilder(); /// /// 스크립트가 처음 활성화될 때 호출되는 Unity 생명주기 메서드입니다. /// 주로 이벤트 리스너를 등록하고 초기화 작업을 수행합니다. /// 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); }); } /// /// 스크롤 뷰가 마지막 데이터 위치에 도달했는지 확인하고 결과를 로그에 출력합니다. /// InfiniteScroll.IsMoveToLastData() API의 사용 예시입니다. /// public void IsMoveToLastData() { AddLog(string.Format("Is move to last data vertical:{0},horizontal:{1}", verticalScrollList.IsMoveToLastData().ToString(), horizontalScrollList.IsMoveToLastData().ToString())); } /// /// 새로운 데이터를 생성하여 스크롤 리스트의 끝에 추가합니다. /// InfiniteScroll.InsertData() API의 사용 예시입니다. /// 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() { new Dropdown.OptionData(data.index.ToString()) }; moveDataSelect.AddOptions(options); // 5. 카운터를 업데이트하고 로그를 남깁니다. ++insertCount; UpdateDataCount(); AddLog(string.Format("Insert Data : {0}", index - 1)); } /// /// 스크롤 리스트의 모든 데이터를 제거합니다. /// InfiniteScroll.Clear() API의 사용 예시입니다. /// public void Clear() { // 1. InfiniteScroll의 모든 데이터를 제거합니다. // 이 메서드는 내부 데이터 목록과 화면의 UI 아이템을 모두 정리합니다. verticalScrollList.Clear(); horizontalScrollList.Clear(); // 2. 이 예제에서 사용하는 내부 데이터 리스트와 UI 요소도 함께 정리합니다. removeCount += dataList.Count; dataList.Clear(); moveDataSelect.ClearOptions(); // 3. 카운터를 업데이트하고 로그를 남깁니다. UpdateDataCount(); AddLog("Clear Data"); } /// /// 데이터 리스트에서 무작위로 하나의 데이터를 선택하여 제거합니다. /// InfiniteScroll.RemoveData() API의 사용 예시입니다. /// 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)); } /// /// 무작위로 하나의 데이터를 선택하여 내용을 업데이트합니다. /// InfiniteScroll.UpdateData() API의 사용 예시입니다. /// 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)); } /// /// 스크롤 리스트의 모든 데이터를 업데이트합니다. /// InfiniteScroll.UpdateAllData() API의 사용 예시입니다. /// 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"); } /// /// 데이터 및 아이템 개수를 UI에 최신 정보로 업데이트합니다. /// 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(); } /// /// 스크롤을 가장 처음 위치로 이동시킵니다. /// public void MoveToFirstData() { verticalScrollList.MoveToFirstData(); horizontalScrollList.MoveToFirstData(); AddLog("Move to first data"); } /// /// 스크롤을 가장 마지막 위치로 이동시킵니다. /// public void MoveToLastData() { verticalScrollList.MoveToLastData(); horizontalScrollList.MoveToLastData(); AddLog("Move to last data"); } /// /// 지정된 '아이템 인덱스'로 스크롤을 이동시킵니다. /// '아이템 인덱스'는 필터링과 정렬이 적용된 후 화면에 보이는 순서입니다. /// /// 이동할 대상의 아이템 인덱스 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)); } /// /// 지정된 '데이터 인덱스'로 스크롤을 이동시킵니다. /// '데이터 인덱스'는 원본 데이터 리스트(dataList)에서의 순서입니다. /// /// 이동할 대상의 데이터 인덱스 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)); } /// /// 홀수 인덱스를 가진 데이터만 표시하도록 필터를 설정합니다. /// InfiniteScroll.SetFilter() API의 사용 예시입니다. /// public void FilterOdd() { // Predicate는 데이터를 받아 bool을 반환하는 함수입니다. // true를 반환하는 데이터만 화면에 표시됩니다. Predicate func = (data) => { if (data is TestItemData testData) { // 인덱스가 짝수인 데이터만 남깁니다 (홀수번째 아이템). return (testData.index % 2) == 0; } return false; }; // 생성한 필터 함수를 스크롤 뷰에 설정합니다. verticalScrollList.SetFilter(func); horizontalScrollList.SetFilter(func); UpdateDataCount(); } /// /// 짝수 인덱스를 가진 데이터만 표시하도록 필터를 설정합니다. /// public void FilterEven() { Predicate func = (data) => { if (data is TestItemData testData) { // 인덱스가 홀수인 데이터만 남깁니다 (짝수번째 아이템). return (testData.index % 2) == 1; } return false; }; verticalScrollList.SetFilter(func); horizontalScrollList.SetFilter(func); UpdateDataCount(); } /// /// 모든 필터를 제거하고 모든 데이터를 표시합니다. /// public void FilterAll() { // SetFilter에 null을 전달하면 필터가 해제됩니다. verticalScrollList.SetFilter(null); horizontalScrollList.SetFilter(null); UpdateDataCount(); } /// /// 화면의 로그 UI에 새로운 텍스트를 추가하고, 로그 창을 맨 아래로 스크롤합니다. /// /// 로그에 추가할 문자열 private void AddLog(string text) { log.AppendLine(text); logText.text = log.ToString(); // 로그가 추가될 때마다 스크롤을 맨 아래로 내려 최신 로그를 보여줍니다. // verticalNormalizedPosition = 0.0f가 맨 아래를 의미합니다. logScrollRect.verticalNormalizedPosition = 0.0f; } /// /// 현재 데이터 리스트에서 유효한 랜덤 인덱스를 반환합니다. /// /// 0과 (dataList.Count - 1) 사이의 랜덤 정수 private int GetDataIndexByRandom() { return UnityEngine.Random.Range(0, dataList.Count); } } }