439 lines
14 KiB
C#
439 lines
14 KiB
C#
namespace Gpm.Ui
|
|
{
|
|
using System;
|
|
using UnityEngine;
|
|
using DataContext = InfiniteScroll.DataContext;
|
|
|
|
/// <summary>
|
|
/// InfiniteScroll에 표시될 데이터의 기반 클래스입니다.
|
|
/// 이 클래스를 상속하여 스크롤 아이템에 필요한 데이터를 정의할 수 있습니다.
|
|
/// </summary>
|
|
/// <example>
|
|
/// <code>
|
|
/// // 사용자 정의 데이터 클래스 예시
|
|
/// public class MyCustomData : InfiniteScrollData
|
|
/// {
|
|
/// public string title;
|
|
/// public string description;
|
|
/// public Sprite icon;
|
|
/// }
|
|
/// </code>
|
|
/// </example>
|
|
public class InfiniteScrollData {}
|
|
|
|
/// <summary>
|
|
/// InfiniteScroll의 각 아이템을 나타내는 기본 클래스입니다.
|
|
/// 이 클래스를 상속하여 아이템의 UI와 동작을 구현합니다.
|
|
/// </summary>
|
|
public class InfiniteScrollItem : MonoBehaviour
|
|
{
|
|
/// <summary>
|
|
/// 아이템의 크기가 변경될 때 자동으로 스크롤에 적용할지 여부를 결정합니다.
|
|
/// 주로 UI 요소(예: Text)의 내용에 따라 크기가 동적으로 변할 때 유용합니다.
|
|
/// </summary>
|
|
public bool autoApplySize = false;
|
|
|
|
protected RectTransform cachedRectTransform = null;
|
|
|
|
protected bool activeItem;
|
|
protected InfiniteScroll scroll = null;
|
|
|
|
/// <summary>
|
|
/// 이 아이템의 RectTransform 컴포넌트를 가져옵니다.
|
|
/// 성능을 위해 처음 접근할 때 캐시됩니다.
|
|
/// </summary>
|
|
public RectTransform rectTransform
|
|
{
|
|
get
|
|
{
|
|
if (System.Object.ReferenceEquals(cachedRectTransform, null) == true)
|
|
{
|
|
cachedRectTransform = transform as RectTransform;
|
|
}
|
|
|
|
return cachedRectTransform;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 이 아이템에 바인딩된 데이터를 가져옵니다.
|
|
/// 데이터는 InfiniteScrollData를 상속한 사용자 정의 클래스의 인스턴스입니다.
|
|
/// </summary>
|
|
protected InfiniteScrollData scrollData
|
|
{
|
|
get
|
|
{
|
|
if (dataContext != null)
|
|
{
|
|
return dataContext.data;
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
protected DataContext dataContext = null;
|
|
protected Action<InfiniteScrollData> selectCallback = null;
|
|
protected Action<InfiniteScrollData, RectTransform> updateSizeCallback = null;
|
|
|
|
protected int itemObjectIndex = -1;
|
|
|
|
internal bool needUpdateItemSize = true;
|
|
|
|
/// <summary>
|
|
/// InfiniteScroll에 의해 아이템이 생성될 때 호출되는 초기화 메서드입니다.
|
|
/// </summary>
|
|
/// <param name="scroll">부모 InfiniteScroll 인스턴스</param>
|
|
/// <param name="itemObjectIndex">아이템 객체 풀 내에서의 인덱스</param>
|
|
public void Initalize(InfiniteScroll scroll, int itemObjectIndex)
|
|
{
|
|
this.scroll = scroll;
|
|
this.itemObjectIndex = itemObjectIndex;
|
|
this.needUpdateItemSize = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 현재 화면에 보이는 아이템들 중 이 아이템의 인덱스를 반환합니다.
|
|
/// </summary>
|
|
/// <returns>화면 내 아이템 인덱스. 데이터가 없으면 -1을 반환합니다.</returns>
|
|
public int GetItemIndex()
|
|
{
|
|
if (dataContext != null)
|
|
{
|
|
return dataContext.itemIndex;
|
|
}
|
|
else
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 전체 데이터 목록에서 이 아이템이 사용하는 데이터의 인덱스를 반환합니다.
|
|
/// </summary>
|
|
/// <returns>전체 데이터 인덱스. 데이터가 없으면 -1을 반환합니다.</returns>
|
|
public int GetDataIndex()
|
|
{
|
|
if (dataContext != null)
|
|
{
|
|
return dataContext.index;
|
|
}
|
|
else
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 아이템이 현재 활성화 상태인지 확인합니다.
|
|
/// </summary>
|
|
/// <returns>활성화 상태이면 true, 아니면 false를 반환합니다.</returns>
|
|
public bool IsActive()
|
|
{
|
|
return activeItem;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 아이템이 선택되었을 때 호출될 콜백을 추가합니다.
|
|
/// </summary>
|
|
/// <param name="callback">실행할 콜백 함수</param>
|
|
/// <example>
|
|
/// <code>
|
|
/// void OnEnable()
|
|
/// {
|
|
/// var item = GetComponent<InfiniteScrollItem>();
|
|
/// item.AddSelectCallback(OnItemSelected);
|
|
/// }
|
|
///
|
|
/// void OnItemSelected(InfiniteScrollData data)
|
|
/// {
|
|
/// MyCustomData myData = data as MyCustomData;
|
|
/// if (myData != null)
|
|
/// {
|
|
/// Debug.Log("Selected: " + myData.title);
|
|
/// }
|
|
/// }
|
|
/// </code>
|
|
/// </example>
|
|
|
|
public void AddSelectCallback(Action<InfiniteScrollData> callback)
|
|
{
|
|
selectCallback += callback;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 등록된 선택 콜백을 제거합니다.
|
|
/// </summary>
|
|
/// <param name="callback">제거할 콜백 함수</param>
|
|
public void RemoveSelectCallback(Action<InfiniteScrollData> callback)
|
|
{
|
|
selectCallback -= callback;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 새로운 데이터로 아이템의 내용을 업데이트합니다.
|
|
/// 이 메서드를 상속하여 아이템의 UI(텍스트, 이미지 등)를 데이터에 맞게 설정해야 합니다.
|
|
/// </summary>
|
|
/// <param name="scrollData">표시할 데이터</param>
|
|
/// <example>
|
|
/// <code>
|
|
/// // 사용자 정의 아이템 클래스에서 UpdateData를 재정의하는 예시
|
|
/// public class MyScrollItem : InfiniteScrollItem
|
|
/// {
|
|
/// public Text titleText;
|
|
/// public Image iconImage;
|
|
///
|
|
/// public override void UpdateData(InfiniteScrollData scrollData)
|
|
/// {
|
|
/// base.UpdateData(scrollData);
|
|
///
|
|
/// MyCustomData myData = scrollData as MyCustomData;
|
|
/// if (myData != null)
|
|
/// {
|
|
/// titleText.text = myData.title;
|
|
/// iconImage.sprite = myData.icon;
|
|
/// }
|
|
/// }
|
|
/// }
|
|
/// </code>
|
|
/// </example>
|
|
public virtual void UpdateData(InfiniteScrollData scrollData)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// 스크롤 방향에 따라 아이템의 RectTransform 앵커와 피벗을 설정합니다. (내부용)
|
|
/// </summary>
|
|
internal void SetAxis(Vector2 anchorMin, Vector2 anchorMax, Vector2 pivot)
|
|
{
|
|
bool autoApplySize = this.autoApplySize;
|
|
|
|
this.autoApplySize = false;
|
|
|
|
rectTransform.anchorMin = anchorMin;
|
|
rectTransform.anchorMax = anchorMax;
|
|
rectTransform.pivot = pivot;
|
|
|
|
needUpdateItemSize = true;
|
|
|
|
this.autoApplySize = autoApplySize;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 아이템이 선택되었을 때 호출됩니다.
|
|
/// 주로 버튼의 OnClick 이벤트에 연결하여 사용합니다.
|
|
/// </summary>
|
|
protected void OnSelect()
|
|
{
|
|
if (selectCallback != null)
|
|
{
|
|
selectCallback(scrollData);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 아이템의 활성 상태를 설정합니다.
|
|
/// GameObject를 활성화/비활성화하고, 필요한 경우 부모 스크롤에 상태 변경을 알립니다.
|
|
/// </summary>
|
|
/// <param name="active">활성화 여부</param>
|
|
/// <param name="notifyEvent">부모 InfiniteScroll에 상태 변경 이벤트를 보낼지 여부</param>
|
|
public virtual void SetActive(bool active, bool notifyEvent = true)
|
|
{
|
|
activeItem = active;
|
|
|
|
gameObject.SetActive(activeItem);
|
|
|
|
if (notifyEvent == true)
|
|
{
|
|
if (scroll != null)
|
|
{
|
|
scroll.OnChangeActiveItem(GetDataIndex(), activeItem);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 동적 아이템 크기 모드에서 아이템의 크기를 설정합니다.
|
|
/// InfiniteScroll의 dynamicItemSize가 true일 때만 작동합니다.
|
|
/// </summary>
|
|
/// <param name="itemSize">새로운 아이템 크기 (주 축 기준)</param>
|
|
/// <param name="notity">크기 변경을 즉시 스크롤에 알릴지 여부</param>
|
|
public void SetSize(float itemSize, bool notity = true)
|
|
{
|
|
if (scrollData == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (scroll != null &&
|
|
scroll.dynamicItemSize == false)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (dataContext != null &&
|
|
dataContext.GetItemSize() != itemSize)
|
|
{
|
|
dataContext.SetItemSize(itemSize);
|
|
|
|
if (notity == true)
|
|
{
|
|
OnUpdateItemSize();
|
|
}
|
|
|
|
this.needUpdateItemSize = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 동적 아이템 크기 모드에서 아이템의 크기를 설정합니다.
|
|
/// InfiniteScroll의 dynamicItemSize가 true일 때만 작동합니다.
|
|
/// </summary>
|
|
/// <param name="sizeDelta">새로운 아이템의 sizeDelta</param>
|
|
/// <param name="notity">크기 변경을 즉시 스크롤에 알릴지 여부</param>
|
|
public void SetSize(Vector2 sizeDelta, bool notity = true)
|
|
{
|
|
if (scrollData == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (scroll != null &&
|
|
scroll.dynamicItemSize == false)
|
|
{
|
|
return;
|
|
}
|
|
|
|
float itemSize = scroll.layout.GetMainSize(sizeDelta);
|
|
SetSize(itemSize, notity);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 아이템의 데이터를 초기화하고 비활성화합니다. (내부용)
|
|
/// </summary>
|
|
internal void ClearData(bool notifyEvent = true)
|
|
{
|
|
SetActive(false, activeItem && notifyEvent);
|
|
|
|
if (dataContext != null)
|
|
{
|
|
dataContext.itemObject = null;
|
|
dataContext = null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 아이템의 모든 데이터와 콜백을 초기화합니다. (내부용)
|
|
/// </summary>
|
|
internal void Clear()
|
|
{
|
|
ClearData(false);
|
|
|
|
selectCallback = null;
|
|
updateSizeCallback = null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 새로운 데이터 컨텍스트로 아이템을 업데이트합니다. (내부용)
|
|
/// </summary>
|
|
internal void UpdateItem(DataContext context)
|
|
{
|
|
this.dataContext = context;
|
|
this.dataContext.itemObject = this;
|
|
|
|
UpdateData(this.dataContext.data);
|
|
|
|
this.dataContext.needUpdateItemData = false;
|
|
|
|
this.needUpdateItemSize = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 아이템의 크기가 업데이트될 때 호출될 콜백을 추가합니다.
|
|
/// `dynamicItemSize`가 활성화된 경우에 유용합니다.
|
|
/// </summary>
|
|
/// <param name="callback">실행할 콜백 함수. `InfiniteScrollData`와 `RectTransform`을 인자로 받습니다.</param>
|
|
public void AddUpdateSizeCallback(Action<InfiniteScrollData, RectTransform> callback)
|
|
{
|
|
updateSizeCallback += callback;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 등록된 크기 업데이트 콜백을 제거합니다.
|
|
/// </summary>
|
|
/// <param name="callback">제거할 콜백 함수.</param>
|
|
public void RemoveUpdateSizeCallback(Action<InfiniteScrollData, RectTransform> callback)
|
|
{
|
|
updateSizeCallback -= callback;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 아이템의 크기가 변경되었을 때 호출됩니다.
|
|
/// 부모 `InfiniteScroll`에 크기 변경을 알려 레이아웃을 다시 계산하도록 하고, 등록된 콜백을 실행합니다.
|
|
/// </summary>
|
|
protected void OnUpdateItemSize()
|
|
{
|
|
if (scroll != null &&
|
|
scroll.dynamicItemSize == false)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (scroll != null)
|
|
{
|
|
scroll.OnUpdateItemSize(dataContext);
|
|
}
|
|
|
|
if (updateSizeCallback != null)
|
|
{
|
|
updateSizeCallback(scrollData, rectTransform);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// `autoApplySize` 기능이 현재 실행될 수 있는 상태인지 확인합니다.
|
|
/// 아이템이 활성화되어 있고, 스크롤이 처리 중이 아닐 때 true를 반환합니다.
|
|
/// </summary>
|
|
protected bool CanAutoSizeCheck()
|
|
{
|
|
if (autoApplySize == false ||
|
|
activeItem == false)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if(scroll == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( scroll.processing == true ||
|
|
scroll.anchorUpdate == true)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (rectTransform == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unity의 UI 시스템에 의해 RectTransform의 크기가 변경될 때 호출됩니다.
|
|
/// autoApplySize가 true일 때, 이 메서드는 변경된 크기를 InfiniteScroll에 자동으로 전달하여
|
|
/// 스크롤 레이아웃을 다시 계산하도록 합니다.
|
|
/// </summary>
|
|
protected void OnRectTransformDimensionsChange()
|
|
{
|
|
if (CanAutoSizeCheck() == true)
|
|
{
|
|
SetSize(rectTransform.sizeDelta);
|
|
}
|
|
}
|
|
}
|
|
}
|