Component List 개발 중
This commit is contained in:
@@ -1,16 +1,38 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace Gpm.Ui
|
||||
{
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
[ExecuteAlways]
|
||||
[RequireComponent(typeof(RectTransform))]
|
||||
/// <summary>
|
||||
/// 이 컴포넌트가 부착된 RectTransform의 크기가 변경될 때, 지정된 'target' RectTransform들의 크기를 동일하게 맞추는 역할을 합니다.
|
||||
/// 주로 ContentSizeFitter와 함께 사용되어 동적으로 변하는 콘텐츠의 크기에 맞춰 배경 이미지 등의 크기를 조절할 때 유용합니다.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <b>UI 계층 구조 예시:</b>
|
||||
/// <code>
|
||||
/// - Background (RectTransform)
|
||||
/// - Content (RectTransform, VerticalLayoutGroup, ContentSizeFitter, ContentSizeSetter)
|
||||
/// - Item1 (Image)
|
||||
/// - Item2 (Image)
|
||||
/// - ...
|
||||
/// </code>
|
||||
/// 위와 같은 구조에서 'Content' GameObject에 이 스크립트를 추가하고,
|
||||
/// 'target' 배열에 'Background'의 RectTransform을 할당하면,
|
||||
/// 'Content'의 크기가 내부 아이템(Item1, Item2)에 의해 변경될 때마다 'Background'의 크기도 함께 조절됩니다.
|
||||
/// </example>
|
||||
[ExecuteAlways] // 에디터 모드에서도 스크립트가 실행되도록 하여, UI 변경을 실시간으로 확인할 수 있게 합니다.
|
||||
[RequireComponent(typeof(RectTransform))] // 이 컴포넌트는 RectTransform이 필수적으로 필요함을 명시합니다.
|
||||
public class ContentSizeSetter : UIBehaviour
|
||||
{
|
||||
// 성능 최적화를 위해 RectTransform 컴포넌트를 캐싱하는 변수입니다.
|
||||
[System.NonSerialized]
|
||||
private RectTransform m_Rect;
|
||||
|
||||
/// <summary>
|
||||
/// 이 컴포넌트가 부착된 GameObject의 RectTransform에 대한 참조입니다.
|
||||
/// 처음 접근할 때 GetComponent를 통해 초기화하고, 이후에는 캐시된 값을 사용합니다.
|
||||
/// </summary>
|
||||
private RectTransform rectTransform
|
||||
{
|
||||
get
|
||||
@@ -23,30 +45,54 @@ namespace Gpm.Ui
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 크기를 조절할 때 추가할 여백(margin)입니다.
|
||||
/// x, y 값을 설정하여 target의 너비와 높이에 각각 추가적인 공간을 줄 수 있습니다.
|
||||
/// </summary>
|
||||
public Vector2 margin;
|
||||
|
||||
/// <summary>
|
||||
/// 이 컴포넌트의 크기에 맞춰 함께 크기가 조절될 RectTransform(들)의 배열입니다.
|
||||
/// 인스펙터 창에서 크기를 동기화할 UI 요소들을 여기에 할당합니다.
|
||||
/// </summary>
|
||||
public RectTransform[] target;
|
||||
|
||||
/// <summary>
|
||||
/// 컴포넌트가 활성화될 때 호출되는 Unity 생명주기 함수입니다.
|
||||
/// 레이아웃을 다시 계산하도록 시스템에 요청합니다.
|
||||
/// </summary>
|
||||
protected override void OnEnable()
|
||||
{
|
||||
SetDirty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이 컴포넌트가 부착된 RectTransform의 크기나 앵커 등이 변경될 때 호출됩니다.
|
||||
/// 이 스크립트의 핵심 로직으로, target 배열에 있는 모든 RectTransform의 크기를 현재 RectTransform의 크기에 맞게 업데이트합니다.
|
||||
/// </summary>
|
||||
protected override void OnRectTransformDimensionsChange()
|
||||
{
|
||||
base.OnRectTransformDimensionsChange();
|
||||
|
||||
// 현재 RectTransform의 크기에 margin 값을 더하여 최종 크기를 계산합니다.
|
||||
Vector2 sizeDelta = new Vector2(rectTransform.sizeDelta.x + margin.x, rectTransform.sizeDelta.y + margin.y);
|
||||
|
||||
if (target != null)
|
||||
{
|
||||
// target 배열의 모든 RectTransform에 대해 계산된 크기를 적용합니다.
|
||||
for (int i = 0; i < target.Length; i++)
|
||||
{
|
||||
target[i].sizeDelta = sizeDelta;
|
||||
// target의 레이아웃을 갱신하도록 표시하여 UI가 올바르게 다시 그려지도록 합니다.
|
||||
LayoutRebuilder.MarkLayoutForRebuild(target[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UI 레이아웃을 다시 계산해야 함을 시스템에 알립니다.
|
||||
/// </summary>
|
||||
/// <param name="force">true일 경우, 즉시 레이아웃을 다시 계산합니다. false일 경우, 다음 프레임에 다시 계산하도록 예약합니다.</param>
|
||||
protected void SetDirty(bool force = false)
|
||||
{
|
||||
if (IsActive() == false)
|
||||
@@ -56,15 +102,21 @@ namespace Gpm.Ui
|
||||
|
||||
if (force == true)
|
||||
{
|
||||
// 즉시 레이아웃을 강제로 다시 계산합니다.
|
||||
LayoutRebuilder.ForceRebuildLayoutImmediate(rectTransform);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 다음 레이아웃 계산 주기에 맞춰 다시 계산하도록 표시만 해둡니다. (일반적인 경우)
|
||||
LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// (Unity 에디터 전용) 스크립트가 로드되거나 인스펙터에서 값이 변경될 때 호출됩니다.
|
||||
/// 에디터에서 margin과 같은 값을 변경했을 때, 실시간으로 UI에 반영되도록 레이아웃 업데이트를 요청합니다.
|
||||
/// </summary>
|
||||
protected override void OnValidate()
|
||||
{
|
||||
SetDirty(false);
|
||||
|
||||
@@ -1,51 +1,100 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace Gpm.Ui
|
||||
{
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// UI 요소를 마우스 드래그로 이동할 수 있게 만드는 컴포넌트입니다.
|
||||
/// 이 컴포넌트를 UI GameObject에 추가하면 해당 UI를 화면 내에서 자유롭게 움직일 수 있습니다.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <b>사용 방법:</b>
|
||||
/// <code>
|
||||
/// 1. 움직이게 하고 싶은 UI GameObject(예: Image, Panel)를 선택합니다.
|
||||
/// 2. 인스펙터 창에서 [Add Component] 버튼을 누르고 'DraggableRect'를 검색하여 추가합니다.
|
||||
/// 3. 'Drag Rect Transform' 필드에 움직일 대상을 지정합니다. 비워두면 컴포넌트가 부착된 GameObject 자신이 움직입니다.
|
||||
/// 4. 게임을 실행하고 UI를 마우스로 드래그하면 이동하는 것을 확인할 수 있습니다.
|
||||
/// </code>
|
||||
public class DraggableRect : DragaEventHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// 드래그 시 실제로 위치가 변경될 UI의 RectTransform입니다.
|
||||
/// 만약 비어있다면, 이 스크립트가 부착된 GameObject의 RectTransform이 기본값으로 사용됩니다.
|
||||
/// </summary>
|
||||
public RectTransform dragRectTransform;
|
||||
|
||||
// 성능 최적화를 위해 Canvas 컴포넌트를 캐싱하는 변수입니다.
|
||||
private Canvas m_Canvas;
|
||||
|
||||
/// <summary>
|
||||
/// 이 UI가 속해 있는 최상위 Canvas에 대한 참조입니다.
|
||||
/// 드래그 이동량을 계산할 때 Canvas의 scaleFactor를 적용하여,
|
||||
/// Canvas Scaler의 설정과 관계없이 일관된 속도로 움직이게 합니다.
|
||||
/// </summary>
|
||||
public Canvas canvas
|
||||
{
|
||||
get
|
||||
{
|
||||
if(m_Canvas == null)
|
||||
{
|
||||
// 컴포넌트의 부모 계층에서 Canvas를 찾아서 할당합니다.
|
||||
m_Canvas = GetComponentInParent<Canvas>();
|
||||
}
|
||||
return m_Canvas;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 컴포넌트가 활성화될 때 호출되는 Unity 생명주기 함수입니다.
|
||||
/// 필요한 변수를 초기화하고, 드래그 이벤트를 리스너에 등록합니다.
|
||||
/// </summary>
|
||||
protected override void OnEnable()
|
||||
{
|
||||
if (dragRectTransform == null)
|
||||
{
|
||||
// dragRectTransform이 설정되지 않았다면, 현재 GameObject의 RectTransform으로 설정합니다.
|
||||
dragRectTransform = gameObject.GetComponent<RectTransform>();
|
||||
}
|
||||
|
||||
// 필수적인 RectTransform이나 Canvas가 없으면 컴포넌트를 비활성화합니다.
|
||||
if (dragRectTransform == null || canvas == null)
|
||||
{
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
// 부모 클래스(DragaEventHandler)의 onDrag 이벤트에 OnDragMove 함수를 연결합니다.
|
||||
// 이제 드래그가 발생할 때마다 OnDragMove 함수가 호출됩니다.
|
||||
onDrag.AddListener(OnDragMove);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 컴포넌트가 비활성화될 때 호출되는 Unity 생명주기 함수입니다.
|
||||
/// 메모리 누수를 방지하기 위해 등록했던 이벤트 리스너를 제거합니다.
|
||||
/// </summary>
|
||||
protected override void OnDisable()
|
||||
{
|
||||
// OnEnable에서 연결했던 OnDragMove 함수를 onDrag 이벤트에서 제거합니다.
|
||||
onDrag.RemoveListener(OnDragMove);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 드래그 이벤트가 발생할 때 호출되는 함수입니다.
|
||||
/// 마우스의 움직임에 따라 dragRectTransform의 위치를 업데이트합니다.
|
||||
/// </summary>
|
||||
/// <param name="eventData">드래그 이벤트와 관련된 정보(예: 마우스 이동량)를 담고 있습니다.</param>
|
||||
public void OnDragMove(PointerEventData eventData)
|
||||
{
|
||||
// eventData.delta는 이번 프레임에서 마우스가 움직인 거리(픽셀 단위)입니다.
|
||||
// 이를 canvas.scaleFactor로 나누어주어 Canvas 좌표계에 맞는 이동량으로 변환합니다.
|
||||
// 변환된 값을 anchoredPosition에 더하여 UI를 이동시킵니다.
|
||||
dragRectTransform.anchoredPosition += eventData.delta / canvas.scaleFactor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이 컴포넌트의 부모 계층 구조가 변경될 때 호출됩니다.
|
||||
/// (예: 다른 Canvas 하위로 이동될 경우)
|
||||
/// 캐시된 Canvas 정보를 다시 최신 상태로 갱신합니다.
|
||||
/// </summary>
|
||||
protected override void OnCanvasHierarchyChanged()
|
||||
{
|
||||
m_Canvas = GetComponentInParent<Canvas>();
|
||||
|
||||
@@ -6,72 +6,143 @@ namespace Gpm.Ui
|
||||
|
||||
public partial class InfiniteScroll
|
||||
{
|
||||
/// <summary>
|
||||
/// 스크롤에 필요한 아이템의 개수를 지정합니다.
|
||||
/// 이 값은 스크롤 뷰포트 크기를 기반으로 동적으로 계산될 수 있으며,
|
||||
/// 화면에 보이는 아이템보다 더 많은 아이템을 미리 생성하여 부드러운 스크롤링을 보장합니다.
|
||||
/// 일반적으로 (화면에 보이는 아이템 개수 * 2) 정도로 설정하는 것이 좋습니다.
|
||||
/// </summary>
|
||||
[Header("Scroll Item", order = 2)]
|
||||
public int needItemCount = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 스크롤 리스트에 표시될 아이템의 프리팹입니다.
|
||||
/// 이 프리팹은 `InfiniteScrollItem` 컴포넌트 또는 이를 상속받은 클래스를 포함해야 합니다.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // Unity 에디터에서 `InfiniteScroll` 컴포넌트가 있는 GameObject를 선택합니다.
|
||||
/// // Inspector 창에서 'Item Prefab' 필드에 프로젝트에 있는 아이템 프리팹을 드래그 앤 드롭합니다.
|
||||
/// </code>
|
||||
/// </example>
|
||||
public InfiniteScrollItem itemPrefab = null;
|
||||
|
||||
/// <summary>
|
||||
/// 아이템의 크기가 동적으로 변할 수 있는지 여부를 설정합니다.
|
||||
/// true로 설정하면 각 아이템의 크기가 데이터에 따라 다를 수 있습니다.
|
||||
/// 예를 들어, 텍스트 길이에 따라 높이가 변하는 채팅 메시지 아이템에 유용합니다.
|
||||
/// 이 기능을 사용하려면 각 데이터(`DataContext`)가 `GetItemSize()`를 통해 자신의 크기를 반환해야 합니다.
|
||||
/// </summary>
|
||||
public bool dynamicItemSize = false;
|
||||
|
||||
/// <summary>
|
||||
/// 화면에 보이는 영역 외에 추가로 아이템을 생성할 범위를 결정하는 비율입니다.
|
||||
/// 값이 2이면, 화면 크기의 2배에 해당하는 영역만큼 아이템을 미리 생성하고 배치합니다.
|
||||
/// 이를 통해 사용자가 스크롤할 때 아이템이 갑자기 나타나거나 사라지는 현상을 방지하고 부드러운 경험을 제공합니다.
|
||||
/// </summary>
|
||||
private const float NEED_MORE_ITEM_RATE = 2;
|
||||
|
||||
/// <summary>
|
||||
/// `itemPrefab`의 기본 크기를 저장하는 변수입니다.
|
||||
/// `dynamicItemSize`가 false일 때 모든 아이템의 크기를 동일하게 설정하기 위해 사용됩니다.
|
||||
/// 스크롤 초기화 시점에 `itemPrefab`의 RectTransform에서 크기를 가져와 캐시합니다.
|
||||
/// </summary>
|
||||
private Vector2 defaultItemPrefabSize = Vector2.zero;
|
||||
|
||||
/// <summary>
|
||||
/// 생성된 `InfiniteScrollItem` 객체들을 관리하는 리스트(객체 풀)입니다.
|
||||
/// 스크롤이 움직일 때 새로운 아이템을 계속 생성하고 파괴하는 대신,
|
||||
/// 이 리스트에 있는 아이템들을 재활용하여 성능을 최적화합니다.
|
||||
/// </summary>
|
||||
private List<InfiniteScrollItem> itemObjectList = new List<InfiniteScrollItem>();
|
||||
|
||||
/// <summary>
|
||||
/// 지정된 인덱스에 해당하는 아이템의 크기를 가져옵니다.
|
||||
/// 스크롤 레이아웃을 계산할 때 각 아이템의 위치를 결정하기 위해 사용됩니다.
|
||||
/// </summary>
|
||||
/// <param name="itemIndex">크기를 가져올 아이템의 인덱스입니다.</param>
|
||||
/// <returns>아이템의 주 축(세로 스크롤이면 높이, 가로 스크롤이면 너비) 크기를 반환합니다.</returns>
|
||||
public float GetItemSize(int itemIndex)
|
||||
{
|
||||
float size = 0;
|
||||
|
||||
// 동적 아이템 크기 모드가 활성화된 경우
|
||||
if (dynamicItemSize == true)
|
||||
{
|
||||
// 유효한 아이템 인덱스인지 확인
|
||||
if (itemIndex < itemCount)
|
||||
{
|
||||
// 해당 인덱스의 데이터 컨텍스트를 가져옴
|
||||
DataContext context = GetContextFromItem(itemIndex);
|
||||
if (context != null)
|
||||
{
|
||||
// 데이터 컨텍스트로부터 아이템 크기를 가져옴
|
||||
size = context.GetItemSize();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 동적 크기 모드가 아닌 경우, 캐시된 기본 프리팹 크기를 사용
|
||||
size = layout.GetMainSize(defaultItemPrefabSize);
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 동적 아이템 크기 모드가 활성화되어 있는지 확인합니다.
|
||||
/// </summary>
|
||||
/// <returns>동적 아이템 크기 모드가 활성화되어 있으면 true, 그렇지 않으면 false를 반환합니다.</returns>
|
||||
public bool IsDynamicItemSize()
|
||||
{
|
||||
return dynamicItemSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// `itemPrefab`을 사용하여 새로운 아이템 객체를 생성하고 초기화합니다.
|
||||
/// 생성된 아이템은 `itemObjectList`에 추가되어 객체 풀링의 일부로 관리됩니다.
|
||||
/// </summary>
|
||||
/// <returns>새로 생성된 `InfiniteScrollItem` 인스턴스를 반환합니다.</returns>
|
||||
private InfiniteScrollItem CreateItem()
|
||||
{
|
||||
// 프리팹으로부터 아이템 게임 오브젝트를 생성하고, content를 부모로 설정합니다.
|
||||
InfiniteScrollItem itemObject = Instantiate(itemPrefab, content, false);
|
||||
|
||||
// 아이템을 초기화합니다. 부모 스크롤과 풀에서의 인덱스를 전달합니다.
|
||||
itemObject.Initalize(this, itemObjectList.Count);
|
||||
// 생성된 아이템은 기본적으로 비활성화 상태로 시작합니다.
|
||||
itemObject.SetActive(false, false);
|
||||
|
||||
// 스크롤 방향에 맞게 아이템의 앵커와 피벗을 설정합니다.
|
||||
itemObject.SetAxis(cachedData.anchorMin, cachedData.anchorMax, cachedData.itemPivot);
|
||||
|
||||
// 아이템이 선택되었을 때 호출될 콜백 함수를 등록합니다.
|
||||
itemObject.AddSelectCallback(OnSelectItem);
|
||||
|
||||
|
||||
// 아이템의 크기를 스크롤 레이아웃에 맞게 조정합니다.
|
||||
RectTransform itemTransform = itemObject.rectTransform;
|
||||
itemTransform.sizeDelta = layout.GetAxisVector(layout.GetMainSize(itemTransform.sizeDelta));
|
||||
|
||||
// 생성된 아이템을 객체 풀 리스트에 추가합니다.
|
||||
itemObjectList.Add(itemObject);
|
||||
|
||||
|
||||
|
||||
return itemObject;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 부드러운 스크롤링을 위해 필요한 전체 아이템 영역의 크기를 계산합니다.
|
||||
/// 뷰포트 크기에 `NEED_MORE_ITEM_RATE`를 곱하여 화면 밖의 버퍼 영역까지 포함합니다.
|
||||
/// </summary>
|
||||
/// <returns>필요한 총 아이템 영역의 크기 (주 축 기준)</returns>
|
||||
private float GetNeedSize()
|
||||
{
|
||||
return layout.GetMainSize(viewport) * NEED_MORE_ITEM_RATE;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// `needItemCount`에 설정된 개수만큼 아이템 객체를 미리 생성합니다.
|
||||
/// 스크롤 시작 시점에 호출되어 초기 아이템 풀을 구성합니다.
|
||||
/// </summary>
|
||||
private void CreateNeedItem()
|
||||
{
|
||||
for (int itemNumber = itemObjectList.Count; itemNumber < needItemCount; itemNumber++)
|
||||
@@ -80,6 +151,11 @@ namespace Gpm.Ui
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 풀에 있는 모든 아이템의 데이터를 초기화합니다.
|
||||
/// 아이템 게임 오브젝트는 파괴하지 않고, 데이터만 제거하여 재사용할 수 있도록 준비합니다.
|
||||
/// 주로 새로운 데이터 리스트를 설정할 때 사용됩니다.
|
||||
/// </summary>
|
||||
private void ClearItemsData()
|
||||
{
|
||||
for (int index = 0; index < itemObjectList.Count; ++index)
|
||||
@@ -88,9 +164,15 @@ namespace Gpm.Ui
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 풀에 있는 모든 아이템 객체를 완전히 제거하고 리스트를 비웁니다.
|
||||
/// 아이템의 게임 오브젝트를 파괴하므로, 스크롤을 더 이상 사용하지 않을 때 호출해야 합니다.
|
||||
/// </summary>
|
||||
private void ClearItems()
|
||||
{
|
||||
// 먼저 모든 아이템의 데이터를 초기화합니다.
|
||||
ClearItemsData();
|
||||
// 그 다음, 모든 아이템 게임 오브젝트를 파괴하고 풀에서 제거합니다.
|
||||
for (int index = 0; index < itemObjectList.Count; ++index)
|
||||
{
|
||||
itemObjectList[index].Clear();
|
||||
@@ -99,21 +181,33 @@ namespace Gpm.Ui
|
||||
itemObjectList.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 특정 데이터(`DataContext`)를 표시하기 위한 아이템을 객체 풀에서 가져옵니다.
|
||||
/// 만약 이미 해당 데이터에 할당된 아이템이 있다면 그것을 반환하고,
|
||||
/// 없다면 풀에서 비어있는 아이템을 찾아 할당하거나, 비어있는 아이템이 없으면 새로 생성합니다.
|
||||
/// </summary>
|
||||
/// <param name="context">표시할 데이터 정보를 담고 있는 `DataContext` 객체입니다.</param>
|
||||
/// <returns>데이터를 표시할 `InfiniteScrollItem` 인스턴스를 반환합니다.</returns>
|
||||
private InfiniteScrollItem PullItem(DataContext context)
|
||||
{
|
||||
InfiniteScrollItem item = context.itemObject;
|
||||
|
||||
if( item == null ||
|
||||
// 컨텍스트에 아이템이 연결되어 있지 않거나, 연결된 아이템이 다른 데이터를 표시하고 있는 경우
|
||||
if ( item == null ||
|
||||
item.GetDataIndex() != context.index)
|
||||
{
|
||||
// 기존 연결을 해제하고 새로운 아이템을 찾습니다.
|
||||
context.itemObject = null;
|
||||
// 데이터 인덱스에 해당하는 아이템을 찾거나, 비어있는 아이템을 찾습니다.
|
||||
int itemObjectIndex = GetItemIndexFromDataIndex(context.index, true);
|
||||
if (itemObjectIndex == -1)
|
||||
{
|
||||
// 사용 가능한 아이템이 없으면 새로 생성합니다.
|
||||
item = CreateItem();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 사용 가능한 아이템이 있으면 풀에서 가져옵니다.
|
||||
item = itemObjectList[itemObjectIndex];
|
||||
}
|
||||
}
|
||||
@@ -121,31 +215,53 @@ namespace Gpm.Ui
|
||||
return item;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 데이터 인덱스를 사용하여 객체 풀(`itemObjectList`)에서 해당 아이템의 인덱스를 찾습니다.
|
||||
/// </summary>
|
||||
/// <param name="dataIndex">찾고자 하는 데이터의 인덱스입니다.</param>
|
||||
/// <param name="findEmptyIndex">true로 설정하면, `dataIndex`에 해당하는 아이템을 찾는 동시에 비어있는(비활성화된) 아이템의 인덱스도 함께 찾습니다.</param>
|
||||
/// <returns>
|
||||
/// `dataIndex`에 해당하는 아이템을 찾으면 그 아이템의 풀 인덱스를 반환합니다.
|
||||
/// 찾지 못했을 경우, `findEmptyIndex`가 true이면 찾은 비어있는 아이템의 인덱스를 반환하고,
|
||||
/// 비어있는 아이템도 없으면 -1을 반환합니다.
|
||||
/// </returns>
|
||||
private int GetItemIndexFromDataIndex(int dataIndex, bool findEmptyIndex = false)
|
||||
{
|
||||
int emptyIndex = -1;
|
||||
for (int index = 0; index < itemObjectList.Count; ++index)
|
||||
{
|
||||
// 현재 아이템이 찾고 있는 데이터 인덱스를 이미 가지고 있다면 즉시 반환
|
||||
if (itemObjectList[index].GetDataIndex() == dataIndex)
|
||||
{
|
||||
return index;
|
||||
}
|
||||
|
||||
// 비어있는 아이템을 찾는 옵션이 켜져 있을 경우
|
||||
if (findEmptyIndex == true)
|
||||
{
|
||||
// 아직 비어있는 아이템을 찾지 못했고, 현재 아이템이 비활성화 상태라면
|
||||
if (emptyIndex == -1 &&
|
||||
itemObjectList[index].IsActive() == false )
|
||||
{
|
||||
// 이 아이템의 인덱스를 예비로 저장
|
||||
emptyIndex = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 루프가 끝날 때까지 dataIndex에 해당하는 아이템을 찾지 못했다면,
|
||||
// 예비로 저장해 둔 비어있는 아이템의 인덱스를 반환
|
||||
return emptyIndex;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 아이템의 크기가 동적으로 변경되었을 때 호출되는 내부 메서드입니다.
|
||||
/// `dynamicItemSize`가 true일 때만 작동하며, 레이아웃을 다시 계산해야 함을 표시합니다.
|
||||
/// </summary>
|
||||
/// <param name="context">크기가 변경된 아이템의 `DataContext`입니다.</param>
|
||||
internal void OnUpdateItemSize(DataContext context)
|
||||
{
|
||||
// 아이템이 실제로 화면에 표시되고 있는 경우, 모든 데이터의 위치를 업데이트
|
||||
if (dynamicItemSize == true)
|
||||
{
|
||||
if(context.itemObject != null)
|
||||
@@ -153,6 +269,7 @@ namespace Gpm.Ui
|
||||
UpdateAllData(false);
|
||||
}
|
||||
|
||||
// 다음 프레임에 레이아웃을 다시 계산하도록 플래그를 설정
|
||||
needReBuildLayout = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,72 +2,169 @@ namespace Gpm.Ui
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Events;
|
||||
|
||||
public partial class InfiniteScroll
|
||||
{
|
||||
/// <summary>
|
||||
/// InfiniteScroll에서 각 아이템의 데이터를 관리하는 내부 클래스입니다.
|
||||
/// 원본 데이터와 함께 스크롤 내에서의 상태(인덱스, 크기, 연결된 UI 객체 등)를 저장합니다.
|
||||
/// </summary>
|
||||
public class DataContext
|
||||
{
|
||||
/// <summary>
|
||||
/// DataContext의 생성자입니다.
|
||||
/// </summary>
|
||||
/// <param name="data">아이템에 표시될 원본 데이터입니다. `InfiniteScrollData`를 상속받은 클래스의 인스턴스여야 합니다.</param>
|
||||
/// <param name="index">전체 데이터 리스트(`dataList`) 내에서의 인덱스입니다.</param>
|
||||
public DataContext(InfiniteScrollData data, int index)
|
||||
{
|
||||
this.index = index;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 아이템에 표시될 원본 데이터입니다.
|
||||
/// 사용자가 정의한 `InfiniteScrollData`의 자식 클래스 인스턴스입니다.
|
||||
/// </summary>
|
||||
internal InfiniteScrollData data;
|
||||
|
||||
/// <summary>
|
||||
/// 전체 데이터 리스트(`dataList`) 내에서 이 데이터의 인덱스입니다.
|
||||
/// 이 값은 데이터가 추가되거나 삭제될 때 변경될 수 있습니다.
|
||||
/// </summary>
|
||||
internal int index = -1;
|
||||
|
||||
/// <summary>
|
||||
/// 현재 화면에 표시되는 아이템들 사이에서의 인덱스입니다.
|
||||
/// 필터링으로 인해 일부 아이템이 보이지 않는 경우 `index`와 다른 값을 가질 수 있습니다.
|
||||
/// 아이템이 화면에 표시되지 않으면 -1 값을 가집니다.
|
||||
/// </summary>
|
||||
internal int itemIndex = -1;
|
||||
|
||||
/// <summary>
|
||||
/// 스크롤 내에서 아이템의 시작 위치(오프셋)입니다.
|
||||
/// 레이아웃 계산에 사용됩니다.
|
||||
/// </summary>
|
||||
internal float offset = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 데이터가 변경되어 연결된 UI 아이템(`itemObject`)의 내용을 갱신해야 하는지 여부를 나타냅니다.
|
||||
/// true이면 다음 레이아웃 업데이트 시점에 `InfiniteScrollItem.UpdateData`가 호출됩니다.
|
||||
/// </summary>
|
||||
internal bool needUpdateItemData = true;
|
||||
|
||||
/// <summary>
|
||||
/// 이 데이터에 해당하는 아이템의 크기입니다. (스크롤의 주 축 기준)
|
||||
/// `dynamicItemSize`가 활성화된 경우 각 아이템마다 다른 크기를 가질 수 있습니다.
|
||||
/// </summary>
|
||||
internal float scrollItemSize = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 이 데이터에 현재 연결된 `InfiniteScrollItem` UI 객체입니다.
|
||||
/// 아이템이 화면 밖으로 스크롤되어 재활용되면 null이 될 수 있습니다.
|
||||
/// </summary>
|
||||
internal InfiniteScrollItem itemObject;
|
||||
|
||||
/// <summary>
|
||||
/// UI 아이템의 내용을 갱신해야 하는지 확인합니다.
|
||||
/// </summary>
|
||||
/// <returns>갱신이 필요하면 true, 그렇지 않으면 false를 반환합니다.</returns>
|
||||
public bool IsNeedUpdateItemData()
|
||||
{
|
||||
return needUpdateItemData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 데이터와 연결된 UI 아이템(`itemObject`)의 연결을 끊습니다.
|
||||
/// 아이템은 재활용 풀로 돌아가 다른 데이터를 위해 사용될 수 있습니다.
|
||||
/// </summary>
|
||||
/// <param name="notifyEvent">상태 변경 이벤트를 발생시킬지 여부입니다.</param>
|
||||
public void UnlinkItem(bool notifyEvent = false)
|
||||
{
|
||||
if (itemObject != null)
|
||||
{
|
||||
// 아이템의 데이터를 초기화하고 재활용 준비를 합니다.
|
||||
itemObject.ClearData(notifyEvent);
|
||||
itemObject = null;
|
||||
}
|
||||
|
||||
// 화면에 표시되는 아이템이 아니므로 itemIndex를 -1로 설정합니다.
|
||||
itemIndex = -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이 DataContext의 원본 데이터를 새로운 데이터로 교체합니다.
|
||||
/// </summary>
|
||||
/// <param name="data">새로운 `InfiniteScrollData`입니다.</param>
|
||||
public void UpdateData(InfiniteScrollData data)
|
||||
{
|
||||
this.data = data;
|
||||
// 데이터가 변경되었으므로 UI 갱신이 필요함을 표시합니다.
|
||||
needUpdateItemData = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 아이템의 캐시된 크기를 가져옵니다.
|
||||
/// </summary>
|
||||
/// <returns>아이템의 크기 (스크롤 주 축 기준)</returns>
|
||||
|
||||
public float GetItemSize()
|
||||
{
|
||||
return scrollItemSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 아이템의 크기를 설정합니다.
|
||||
/// 이 값은 `dynamicItemSize`가 true일 때 레이아웃 계산에 사용됩니다.
|
||||
/// </summary>
|
||||
/// <param name="value">설정할 아이템의 크기입니다.</param>
|
||||
public void SetItemSize(float value)
|
||||
{
|
||||
scrollItemSize = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 스크롤에 추가된 모든 데이터(`DataContext`)를 관리하는 리스트입니다.
|
||||
/// </summary>
|
||||
protected List<DataContext> dataList = new List<DataContext>();
|
||||
|
||||
// <summary>
|
||||
/// 현재 필터링 조건을 만족하여 화면에 표시될 수 있는 아이템의 총 개수입니다.
|
||||
/// `onFilter` 델리게이트가 설정된 경우 `dataList.Count`와 다를 수 있습니다.
|
||||
/// </summary>
|
||||
protected int itemCount = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 필터링 조건 변경 등으로 인해 `itemCount`와 각 아이템의 `itemIndex`를 다시 계산해야 하는지 여부를 나타냅니다.
|
||||
/// </summary>
|
||||
protected bool needUpdateItemList = true;
|
||||
|
||||
/// <summary>
|
||||
/// 현재 선택된 아이템의 데이터 인덱스(`index`)입니다.
|
||||
/// 선택된 아이템이 없으면 -1입니다.
|
||||
/// </summary>
|
||||
protected int selectDataIndex = -1;
|
||||
|
||||
/// <summary>
|
||||
/// 아이템이 선택되었을 때 호출될 콜백 함수들의 체인입니다.
|
||||
/// </summary>
|
||||
protected Action<InfiniteScrollData> selectCallback = null;
|
||||
|
||||
/// <summary>
|
||||
/// 특정 `InfiniteScrollData`가 전체 데이터 리스트에서 몇 번째에 있는지 인덱스를 찾습니다.
|
||||
/// </summary>
|
||||
/// <param name="data">찾고자 하는 데이터 객체입니다.</param>
|
||||
/// <returns>데이터를 찾으면 해당 인덱스를, 찾지 못하면 -1을 반환합니다.</returns>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// MyData dataToFind = new MyData("찾을 아이템");
|
||||
/// int index = infiniteScroll.GetDataIndex(dataToFind);
|
||||
/// if (index != -1)
|
||||
/// {
|
||||
/// Debug.Log($"데이터를 찾았습니다. 인덱스: {index}");
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public int GetDataIndex(InfiniteScrollData data)
|
||||
{
|
||||
if (isInitialize == false)
|
||||
@@ -81,34 +178,53 @@ namespace Gpm.Ui
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 스크롤에 추가된 전체 데이터의 개수를 반환합니다.
|
||||
/// 필터링 여부와 관계없이 모든 데이터의 수를 반환합니다.
|
||||
/// </summary>
|
||||
/// <returns>전체 데이터 개수</returns>
|
||||
public int GetDataCount()
|
||||
{
|
||||
return dataList.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 지정된 인덱스에 해당하는 `InfiniteScrollData`를 가져옵니다.
|
||||
/// </summary>
|
||||
/// <param name="index">가져올 데이터의 인덱스 (전체 데이터 리스트 기준)</param>
|
||||
/// <returns>해당 인덱스의 `InfiniteScrollData` 객체</returns>
|
||||
public InfiniteScrollData GetData(int index)
|
||||
{
|
||||
return dataList[index].data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 스크롤에 있는 모든 원본 데이터(`InfiniteScrollData`)를 리스트로 복사하여 반환합니다.
|
||||
/// </summary>
|
||||
/// <returns>모든 데이터가 담긴 새로운 리스트</returns>
|
||||
public List<InfiniteScrollData> GetDataList()
|
||||
{
|
||||
List<InfiniteScrollData> list = new List<InfiniteScrollData>();
|
||||
|
||||
for(int index = 0; index < dataList.Count; index++)
|
||||
for (int index = 0; index < dataList.Count; index++)
|
||||
{
|
||||
list.Add(dataList[index].data);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 화면에 표시될 수 있는 (필터링되지 않은) 아이템들의 원본 데이터 리스트를 반환합니다.
|
||||
/// </summary>
|
||||
/// <returns>표시 가능한 아이템의 데이터가 담긴 새로운 리스트</returns>
|
||||
public List<InfiniteScrollData> GetItemList()
|
||||
{
|
||||
List<InfiniteScrollData> list = new List<InfiniteScrollData>();
|
||||
|
||||
for (int index = 0; index < dataList.Count; index++)
|
||||
{
|
||||
if(dataList[index].itemIndex != -1)
|
||||
// itemIndex가 -1이 아니라는 것은 필터링을 통과한 표시 가능한 아이템임을 의미합니다.
|
||||
if (dataList[index].itemIndex != -1)
|
||||
{
|
||||
list.Add(dataList[index].data);
|
||||
}
|
||||
@@ -116,17 +232,47 @@ namespace Gpm.Ui
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 화면에 표시될 수 있는 (필터링되지 않은) 아이템의 총 개수를 반환합니다.
|
||||
/// </summary>
|
||||
/// <returns>표시 가능한 아이템의 개수</returns>
|
||||
public int GetItemCount()
|
||||
{
|
||||
return itemCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 특정 데이터가 현재 표시되는 아이템 리스트에서 몇 번째 인덱스인지 가져옵니다.
|
||||
/// </summary>
|
||||
/// <param name="data">찾고자 하는 데이터 객체입니다.</param>
|
||||
/// <returns>표시되는 아이템 리스트 내의 인덱스. 데이터가 없거나 필터링되어 보이지 않으면 -1을 반환합니다.</returns>
|
||||
public int GetItemIndex(InfiniteScrollData data)
|
||||
{
|
||||
var context = GetDataContext(data);
|
||||
return context.itemIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 아이템 선택 시 호출될 콜백 함수를 등록합니다.
|
||||
/// </summary>
|
||||
/// <param name="callback">등록할 콜백 함수. 선택된 아이템의 `InfiniteScrollData`를 인자로 받습니다.</param>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// void Start()
|
||||
/// {
|
||||
/// infiniteScroll.AddSelectCallback(OnItemSelected);
|
||||
/// }
|
||||
///
|
||||
/// private void OnItemSelected(InfiniteScrollData data)
|
||||
/// {
|
||||
/// MyData myData = data as MyData;
|
||||
/// if (myData != null)
|
||||
/// {
|
||||
/// Debug.Log($"아이템 선택됨: {myData.title}");
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public void AddSelectCallback(Action<InfiniteScrollData> callback)
|
||||
{
|
||||
if (isInitialize == false)
|
||||
@@ -137,6 +283,10 @@ namespace Gpm.Ui
|
||||
selectCallback += callback;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 등록했던 아이템 선택 콜백 함수를 제거합니다.
|
||||
/// </summary>
|
||||
/// <param name="callback">제거할 콜백 함수입니다.</param>
|
||||
public void RemoveSelectCallback(Action<InfiniteScrollData> callback)
|
||||
{
|
||||
if (isInitialize == false)
|
||||
@@ -147,11 +297,21 @@ namespace Gpm.Ui
|
||||
selectCallback -= callback;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 아이템의 활성/비활성 상태가 변경될 때 이벤트를 발생시킵니다. (내부 호출용)
|
||||
/// </summary>
|
||||
/// <param name="dataIndex">상태가 변경된 아이템의 데이터 인덱스</param>
|
||||
/// <param name="active">활성화 여부</param>
|
||||
public void OnChangeActiveItem(int dataIndex, bool active)
|
||||
{
|
||||
onChangeActiveItem.Invoke(dataIndex, active);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// `InfiniteScrollData`에 해당하는 `DataContext`를 찾아서 반환합니다.
|
||||
/// </summary>
|
||||
/// <param name="data">찾고자 하는 데이터 객체</param>
|
||||
/// <returns>찾은 `DataContext` 객체. 없으면 null을 반환합니다.</returns>
|
||||
protected DataContext GetDataContext(InfiniteScrollData data)
|
||||
{
|
||||
if (isInitialize == false)
|
||||
@@ -165,6 +325,11 @@ namespace Gpm.Ui
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 표시되는 아이템의 인덱스(`itemIndex`)를 사용하여 `DataContext`를 찾습니다.
|
||||
/// </summary>
|
||||
/// <param name="itemIndex">찾고자 하는 아이템의 인덱스</param>
|
||||
/// <returns>찾은 `DataContext` 객체. 없으면 null을 반환합니다.</returns>
|
||||
protected DataContext GetContextFromItem(int itemIndex)
|
||||
{
|
||||
if (isInitialize == false)
|
||||
@@ -182,6 +347,10 @@ namespace Gpm.Ui
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 데이터 리스트의 맨 끝에 새로운 데이터를 추가합니다.
|
||||
/// </summary>
|
||||
/// <param name="data">추가할 데이터</param>
|
||||
protected void AddData(InfiniteScrollData data)
|
||||
{
|
||||
DataContext addData = new DataContext(data, dataList.Count);
|
||||
@@ -192,17 +361,28 @@ namespace Gpm.Ui
|
||||
CheckItemAfterAddData(addData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 데이터가 추가된 후, 해당 데이터가 필터링되지 않고 표시되어야 하는지 확인하고
|
||||
/// `itemIndex`를 재정렬하는 등의 후속 처리를 합니다.
|
||||
/// </summary>
|
||||
/// <param name="addData">새로 추가된 데이터의 `DataContext`</param>
|
||||
/// <returns>데이터가 성공적으로 아이템 리스트에 추가되었으면 true, 필터링되었으면 false를 반환합니다.</returns>
|
||||
|
||||
private bool CheckItemAfterAddData(DataContext addData)
|
||||
{
|
||||
// 필터가 설정되어 있고, 필터 조건에 의해 걸러진다면 아무것도 하지 않습니다.
|
||||
if (onFilter != null &&
|
||||
onFilter(addData.data) == true)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 새로 추가된 아이템이 들어갈 위치(itemIndex)를 찾습니다.
|
||||
int itemIndex = 0;
|
||||
if (itemCount > 0)
|
||||
{
|
||||
// 추가된 데이터의 바로 앞 데이터부터 역순으로 탐색하여
|
||||
// 가장 먼저 발견되는 표시 가능한 아이템의 다음 자리를 찾습니다.
|
||||
for (int dataIndex = addData.index - 1; dataIndex >= 0; dataIndex--)
|
||||
{
|
||||
if (dataList[dataIndex].itemIndex != -1)
|
||||
@@ -216,19 +396,25 @@ namespace Gpm.Ui
|
||||
addData.itemIndex = itemIndex;
|
||||
itemCount++;
|
||||
|
||||
for (int dataIndex = addData.index+1; dataIndex < dataList.Count; dataIndex++)
|
||||
// 새로 추가된 아이템보다 뒤에 있는 모든 아이템들의 itemIndex를 1씩 증가시킵니다.
|
||||
for (int dataIndex = addData.index + 1; dataIndex < dataList.Count; dataIndex++)
|
||||
{
|
||||
if (dataList[dataIndex].itemIndex != -1)
|
||||
{
|
||||
dataList[dataIndex].itemIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
// 레이아웃을 다시 계산해야 함을 표시합니다.
|
||||
needReBuildLayout = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 지정된 인덱스에 새로운 데이터를 삽입합니다.
|
||||
/// </summary>
|
||||
/// <param name="data">삽입할 데이터</param>
|
||||
/// <param name="insertIndex">데이터를 삽입할 위치의 인덱스</param>
|
||||
protected void InsertData(InfiniteScrollData data, int insertIndex)
|
||||
{
|
||||
if (insertIndex < 0 || insertIndex > dataList.Count)
|
||||
@@ -240,7 +426,8 @@ namespace Gpm.Ui
|
||||
{
|
||||
DataContext addData = new DataContext(data, insertIndex);
|
||||
InitFitContext(addData);
|
||||
|
||||
|
||||
// 삽입 위치보다 뒤에 있는 모든 데이터들의 index를 1씩 증가시킵니다.
|
||||
for (int dataIndex = insertIndex; dataIndex < dataList.Count; dataIndex++)
|
||||
{
|
||||
dataList[dataIndex].index++;
|
||||
@@ -252,10 +439,16 @@ namespace Gpm.Ui
|
||||
}
|
||||
else
|
||||
{
|
||||
// 삽입 위치가 맨 끝이면 AddData와 동일합니다.
|
||||
AddData(data);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// `DataContext`의 아이템 크기를 초기화합니다.
|
||||
/// `dynamicItemSize`가 true이면 데이터에 저장된 크기를 사용하고, 아니면 기본 크기를 사용합니다.
|
||||
/// </summary>
|
||||
/// <param name="context">초기화할 `DataContext`</param>
|
||||
protected void InitFitContext(DataContext context)
|
||||
{
|
||||
float size = layout.GetMainSize(defaultItemPrefabSize);
|
||||
@@ -271,37 +464,58 @@ namespace Gpm.Ui
|
||||
context.SetItemSize(size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 주어진 인덱스가 전체 데이터 리스트(`dataList`) 범위 내에 있는지 확인합니다.
|
||||
/// </summary>
|
||||
/// <param name="index">확인할 데이터 인덱스</param>
|
||||
/// <returns>유효한 인덱스이면 true, 아니면 false</returns>
|
||||
protected bool IsValidDataIndex(int index)
|
||||
{
|
||||
return (index >= 0 && index < dataList.Count) ? true : false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 주어진 인덱스가 표시 가능한 아이템 리스트(`itemCount`) 범위 내에 있는지 확인합니다.
|
||||
/// </summary>
|
||||
/// <param name="index">확인할 아이템 인덱스</param>
|
||||
/// <returns>유효한 인덱스이면 true, 아니면 false</returns>
|
||||
protected bool IsValidItemIndex(int index)
|
||||
{
|
||||
return (index >= 0 && index < itemCount) ? true : false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 필터링 조건(`onFilter`)에 따라 전체 데이터 리스트를 순회하며
|
||||
/// 각 데이터의 `itemIndex`를 재설정하고, 표시 가능한 아이템의 총 개수(`itemCount`)를 다시 계산합니다.
|
||||
/// </summary>
|
||||
protected void BuildItemList()
|
||||
{
|
||||
itemCount = 0;
|
||||
for (int i = 0; i < dataList.Count; i++)
|
||||
{
|
||||
DataContext context = dataList[i];
|
||||
|
||||
// 필터가 있고, 필터 조건에 걸러진다면
|
||||
if (onFilter != null &&
|
||||
onFilter(context.data) == true)
|
||||
{
|
||||
// UI 아이템과의 연결을 끊고, itemIndex를 -1로 만듭니다.
|
||||
context.UnlinkItem(false);
|
||||
|
||||
continue;
|
||||
}
|
||||
// 필터를 통과했다면, 순서대로 itemIndex를 부여합니다.
|
||||
context.itemIndex = itemCount;
|
||||
itemCount++;
|
||||
}
|
||||
|
||||
// 아이템 리스트가 변경되었으므로 레이아웃을 다시 계산해야 합니다.
|
||||
needReBuildLayout = true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 아이템이 선택되었을 때 내부적으로 호출되는 메서드입니다.
|
||||
/// `selectDataIndex`를 갱신하고 등록된 콜백(`selectCallback`)을 실행합니다.
|
||||
/// </summary>
|
||||
/// <param name="data">선택된 아이템의 데이터</param>
|
||||
private void OnSelectItem(InfiniteScrollData data)
|
||||
{
|
||||
int dataIndex = GetDataIndex(data);
|
||||
|
||||
@@ -1,19 +1,37 @@
|
||||
namespace Gpm.Ui
|
||||
{
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// 이 partial 클래스는 InfiniteScroll의 레이아웃 관련 로직을 담당합니다.
|
||||
/// 아이템의 배치, 줄 계산, 스크롤 영역 업데이트 등 UI 레이아웃에 필요한 모든 계산을 처리합니다.
|
||||
/// InfiniteScroll.cs의 일부로, 코드 관리를 용이하게 하기 위해 별도 파일로 분리되었습니다.
|
||||
/// </summary>
|
||||
public partial class InfiniteScroll
|
||||
{
|
||||
[Header("Layout", order = 3)]
|
||||
[Tooltip("스크롤 뷰의 레이아웃 설정을 담고 있는 객체입니다. 인스펙터에서 스크롤 방향, 간격, 그리드 등을 설정할 수 있습니다.")]
|
||||
public ScrollLayout layout = new ScrollLayout();
|
||||
|
||||
/// <summary>
|
||||
/// 스크롤 뷰 내에서 한 줄(Row 또는 Column)의 레이아웃 정보를 관리하는 클래스입니다.
|
||||
/// 예: 수직 스크롤에서는 하나의 가로 줄(Row)을 의미합니다.
|
||||
/// </summary>
|
||||
public class LineLayout
|
||||
{
|
||||
/// <summary>
|
||||
/// LineLayout 생성자입니다.
|
||||
/// </summary>
|
||||
/// <param name="index">해당 줄의 인덱스입니다.</param>
|
||||
public LineLayout(int index)
|
||||
{
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 줄의 모든 데이터를 초기화합니다.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
dataList.Clear();
|
||||
@@ -21,57 +39,144 @@ namespace Gpm.Ui
|
||||
size = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 줄에 데이터(아이템)를 추가하고, 줄의 전체 크기를 갱신합니다.
|
||||
/// </summary>
|
||||
/// <param name="context">추가할 아이템의 데이터 컨텍스트입니다.</param>
|
||||
/// <returns>아이템 추가 후 갱신된 줄의 끝 위치를 반환합니다.</returns>
|
||||
public float Add(DataContext context)
|
||||
{
|
||||
context.offset = offset;
|
||||
dataList.Add(context);
|
||||
|
||||
// 아이템의 크기를 가져옵니다.
|
||||
float contextSize = context.GetItemSize();
|
||||
|
||||
// 현재 줄의 크기(size)는 줄에 포함된 아이템들 중 가장 큰 아이템의 크기를 따릅니다.
|
||||
if (size < contextSize)
|
||||
{
|
||||
size = contextSize;
|
||||
}
|
||||
|
||||
// 이 줄의 시작 위치(offset)에 크기(size)를 더하여 이 줄이 차지하는 최종 위치를 반환합니다.
|
||||
return offset + size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이 줄의 인덱스입니다.
|
||||
/// </summary>
|
||||
internal int index = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 이 줄에 포함된 아이템들의 데이터 리스트입니다.
|
||||
/// </summary>
|
||||
internal List<DataContext> dataList = new List<DataContext>();
|
||||
|
||||
/// <summary>
|
||||
/// 스크롤의 주 축(main axis)을 기준으로 이 줄의 시작 위치(offset)입니다.
|
||||
/// </summary>
|
||||
internal float offset = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 스크롤의 주 축을 기준으로 이 줄의 크기입니다.
|
||||
/// 이 줄에 포함된 아이템 중 가장 큰 아이템의 크기와 같습니다.
|
||||
/// </summary>
|
||||
internal float size = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 이 줄에 포함된 아이템의 개수를 반환합니다.
|
||||
/// </summary>
|
||||
/// <returns>아이템 개수</returns>
|
||||
public int GetCount()
|
||||
{
|
||||
return dataList.Count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 스크롤 뷰의 레이아웃 관련 상태를 캐싱하는 클래스입니다.
|
||||
/// 매 프레임마다 모든 값을 다시 계산하는 것을 피하고, 변경이 있을 때만 레이아웃을 업데이트하여 성능을 최적화합니다.
|
||||
/// </summary>
|
||||
public class CachedScrollData
|
||||
{
|
||||
/// <summary>
|
||||
/// 스크롤 콘텐츠의 현재 위치입니다.
|
||||
/// </summary>
|
||||
public float contentPosition = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 스크롤 콘텐츠의 전체 크기입니다.
|
||||
/// </summary>
|
||||
public float contentSize = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 스크롤 뷰포트(보여지는 영역)의 크기입니다.
|
||||
/// </summary>
|
||||
public float viewportSize = 0;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 캐시된 콘텐츠 여백(padding)입니다.
|
||||
/// </summary>
|
||||
public Vector2 padding;
|
||||
|
||||
/// <summary>
|
||||
/// 캐시된 아이템 간 간격(space)입니다.
|
||||
/// </summary>
|
||||
public Vector2 space;
|
||||
|
||||
/// <summary>
|
||||
/// 캐시된 그리드의 수입니다. (예: 3열 그리드이면 3)
|
||||
/// </summary>
|
||||
public int gridCount = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 캐시된 스크롤 축 정보입니다.
|
||||
/// </summary>
|
||||
public ScrollAxis axis;
|
||||
|
||||
/// <summary>
|
||||
/// 캐시된 수직 스크롤 방향입니다. (true: 위에서 아래로)
|
||||
/// </summary>
|
||||
public bool topToBotton = true;
|
||||
|
||||
/// <summary>
|
||||
/// 캐시된 수평 스크롤 방향입니다. (true: 왼쪽에서 오른쪽으로)
|
||||
/// </summary>
|
||||
public bool leftToRight = true;
|
||||
|
||||
/// <summary>
|
||||
/// 캐시된 그리드 레이아웃 값 리스트입니다.
|
||||
/// </summary>
|
||||
public List<ScrollLayout.LayoutValue> values = new List<ScrollLayout.LayoutValue>();
|
||||
|
||||
/// <summary>
|
||||
/// 현재 스크롤이 수직인지 여부입니다.
|
||||
/// </summary>
|
||||
public bool IsVertical = false;
|
||||
|
||||
/// <summary>
|
||||
/// 캐시된 아이템의 앵커 최소값입니다.
|
||||
/// </summary>
|
||||
public Vector2 anchorMin = Vector2.zero;
|
||||
|
||||
/// <summary>
|
||||
/// 캐시된 아이템의 앵커 최대값입니다.
|
||||
/// </summary>
|
||||
public Vector2 anchorMax = Vector2.zero;
|
||||
|
||||
/// <summary>
|
||||
/// 캐시된 아이템의 피벗입니다.
|
||||
/// </summary>
|
||||
public Vector2 itemPivot = Vector2.zero;
|
||||
|
||||
/// <summary>
|
||||
/// 캐시된 콘텐츠의 피벗입니다.
|
||||
/// </summary>
|
||||
public Vector2 contentPivot = Vector2.zero;
|
||||
|
||||
/// <summary>
|
||||
/// 캐시된 모든 데이터를 초기화합니다.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
contentPosition = 0;
|
||||
@@ -80,40 +185,50 @@ namespace Gpm.Ui
|
||||
}
|
||||
}
|
||||
|
||||
internal List<LineLayout> lineLayout = new List<LineLayout>();
|
||||
internal float layoutSize = 0;
|
||||
internal int lineCount = 0;
|
||||
// 레이아웃 계산에 사용되는 내부 변수들
|
||||
internal List<LineLayout> lineLayout = new List<LineLayout>(); // 전체 라인들의 레이아웃 정보를 담는 리스트
|
||||
internal float layoutSize = 0; // 모든 라인들의 크기를 합한 총 레이아웃 크기
|
||||
internal int lineCount = 0; // 현재 데이터로 계산된 총 라인 수
|
||||
|
||||
private int showLineIndex = 0;
|
||||
private int showLineCount = 0;
|
||||
private int showLineIndex = 0; // 화면에 보이는 첫 번째 라인의 인덱스
|
||||
private int showLineCount = 0; // 화면에 보이는 총 라인의 수
|
||||
|
||||
private int firstItemIndex = 0;
|
||||
private int lastItemIndex = 0;
|
||||
private int firstItemIndex = 0; // 화면에 보이는 첫 번째 아이템의 인덱스
|
||||
private int lastItemIndex = 0; // 화면에 보이는 마지막 아이템의 인덱스
|
||||
|
||||
private bool isUpdateArea = false;
|
||||
private bool isRebuildLayout = false;
|
||||
// 레이아웃 업데이트 상태를 관리하는 플래그
|
||||
private bool isUpdateArea = false; // 스크롤 영역(위치, 크기)이 변경되었는지 여부
|
||||
private bool isRebuildLayout = false; // 레이아웃을 처음부터 다시 빌드했는지 여부
|
||||
|
||||
protected bool needReBuildLayout = true;
|
||||
protected bool needReBuildLayout = true; // 레이아웃을 다시 빌드해야 하는지 여부 (데이터 변경, 설정 변경 시 true)
|
||||
|
||||
private bool isStartLine = true;
|
||||
private bool isEndLine = true;
|
||||
private bool isStartLine = true; // 스크롤이 시작 지점에 있는지 여부
|
||||
private bool isEndLine = true; // 스크롤이 끝 지점에 있는지 여부
|
||||
|
||||
internal bool processing = false;
|
||||
internal bool processing = false; // 현재 레이아웃 업데이트가 진행 중인지 여부 (중복 실행 방지)
|
||||
|
||||
// 레이아웃 상태 캐싱을 위한 객체
|
||||
private CachedScrollData cachedData = new CachedScrollData();
|
||||
|
||||
// 앵커 업데이트가 필요한지 여부
|
||||
internal bool anchorUpdate = false;
|
||||
|
||||
// 구버전과의 호환성을 위한 필드. 현재는 layout.padding을 사용합니다.
|
||||
[SerializeField]
|
||||
[HideInInspector]
|
||||
[System.Obsolete("padding is obsolete. Use GetMainPadding() instead (UnityUpgradable) -> GetMainPadding()", false)]
|
||||
private int padding = 0;
|
||||
|
||||
// 구버전과의 호환성을 위한 필드. 현재는 layout.space를 사용합니다.
|
||||
[SerializeField]
|
||||
[HideInInspector]
|
||||
[System.Obsolete("space is obsolete. Use GetMainSpace() instead (UnityUpgradable) -> GetMainSpace()", false)]
|
||||
private int space = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 스크롤 축(방향)을 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="axis">설정할 스크롤 축</param>
|
||||
public void SetScrollAxis(ScrollAxis axis)
|
||||
{
|
||||
layout.axis = axis;
|
||||
@@ -122,33 +237,59 @@ namespace Gpm.Ui
|
||||
CheckScrollData();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 설정된 스크롤 축을 가져옵니다.
|
||||
/// </summary>
|
||||
/// <returns>현재 스크롤 축</returns>
|
||||
public ScrollAxis GetScrollAxis()
|
||||
{
|
||||
return layout.axis;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 콘텐츠의 여백(padding)을 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="padding">x: 가로 여백, y: 세로 여백</param>
|
||||
public void SetPadding(Vector2 padding)
|
||||
{
|
||||
layout.padding = padding;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 아이템 간의 간격(space)을 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="space">x: 가로 간격, y: 세로 간격</param>
|
||||
public void SetSpace(Vector2 space)
|
||||
{
|
||||
layout.space = space;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 설정된 여백(padding)을 가져옵니다.
|
||||
/// </summary>
|
||||
/// <returns>현재 여백</returns>
|
||||
public Vector2 GetPadding()
|
||||
{
|
||||
return layout.padding;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 설정된 아이템 간 간격(space)을 가져옵니다.
|
||||
/// </summary>
|
||||
/// <returns>현재 간격</returns>
|
||||
public Vector2 GetSpace()
|
||||
{
|
||||
return layout.space;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 스크롤 주 축 방향의 여백을 가져옵니다.
|
||||
/// </summary>
|
||||
/// <returns>주 축 여백</returns>
|
||||
public float GetMainPadding()
|
||||
{
|
||||
#pragma warning disable 618
|
||||
// 구버전 호환성 코드
|
||||
if (padding > 0)
|
||||
{
|
||||
layout.MainPadding = padding;
|
||||
@@ -158,9 +299,14 @@ namespace Gpm.Ui
|
||||
return layout.MainPadding;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 스크롤 주 축 방향의 아이템 간 간격을 가져옵니다.
|
||||
/// </summary>
|
||||
/// <returns>주 축 간격</returns>
|
||||
public float GetMainSpace()
|
||||
{
|
||||
#pragma warning disable 618
|
||||
// 구버전 호환성 코드
|
||||
if (space > 0)
|
||||
{
|
||||
layout.MainSpace = space;
|
||||
@@ -170,27 +316,39 @@ namespace Gpm.Ui
|
||||
return layout.MainSpace;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 스크롤 교차 축 방향의 여백을 가져옵니다.
|
||||
/// </summary>
|
||||
/// <returns>교차 축 여백</returns>
|
||||
public float GetCrossPadding()
|
||||
{
|
||||
return layout.CrossPadding;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 스크롤 교차 축 방향의 아이템 간 간격을 가져옵니다.
|
||||
/// </summary>
|
||||
/// <returns>교차 축 간격</returns>
|
||||
public float GetCrossSpace()
|
||||
{
|
||||
return layout.CrossSpace;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 레이아웃 관련 데이터의 변경 사항을 확인하고, 업데이트가 필요한 경우 관련 플래그를 설정합니다.
|
||||
/// 이 메서드는 매 프레임 호출되어 스크롤 위치, 뷰포트 크기, 레이아웃 설정 등의 변경을 감지합니다.
|
||||
/// </summary>
|
||||
protected void CheckScrollData()
|
||||
{
|
||||
float contentPosition = GetContentPosition();
|
||||
float viewportSize = GetViewportSize();
|
||||
float contentSize = GetContentSize();
|
||||
|
||||
// 스크롤 위치, 콘텐츠 크기, 뷰포트 크기 변경 감지
|
||||
if (cachedData.contentPosition != contentPosition)
|
||||
{
|
||||
cachedData.contentPosition = contentPosition;
|
||||
isUpdateArea = true;
|
||||
isUpdateArea = true; // 보이는 영역이 변경되었으므로 업데이트 필요
|
||||
}
|
||||
|
||||
if (cachedData.contentSize != contentSize)
|
||||
@@ -205,12 +363,14 @@ namespace Gpm.Ui
|
||||
isUpdateArea = true;
|
||||
}
|
||||
|
||||
// 간격(space) 변경 감지
|
||||
if (cachedData.space != layout.space)
|
||||
{
|
||||
cachedData.space = layout.space;
|
||||
needReBuildLayout = true;
|
||||
needReBuildLayout = true; // 레이아웃 구조가 변경되었으므로 재계산 필요
|
||||
}
|
||||
|
||||
// 그리드 수 또는 그리드 설정값 변경 감지
|
||||
if (cachedData.padding != layout.padding)
|
||||
{
|
||||
cachedData.padding = layout.padding;
|
||||
@@ -226,15 +386,15 @@ namespace Gpm.Ui
|
||||
|
||||
if (layout.values.Count > 0)
|
||||
{
|
||||
for(int i=0;i< layout.values.Count;i++)
|
||||
for (int i = 0; i < layout.values.Count; i++)
|
||||
{
|
||||
if(cachedData.values.Count < i+1)
|
||||
if (cachedData.values.Count < i + 1)
|
||||
{
|
||||
cachedData.values.Add(new ScrollLayout.LayoutValue());
|
||||
needReBuildLayout = true;
|
||||
}
|
||||
|
||||
if( cachedData.values[i].valueType != layout.values[i].valueType ||
|
||||
if (cachedData.values[i].valueType != layout.values[i].valueType ||
|
||||
cachedData.values[i].value != layout.values[i].value)
|
||||
{
|
||||
cachedData.values[i].valueType = layout.values[i].valueType;
|
||||
@@ -246,6 +406,7 @@ namespace Gpm.Ui
|
||||
}
|
||||
|
||||
bool isUpdateLayout = false;
|
||||
// 스크롤 축, 방향 등 레이아웃의 근본적인 구조 변경 감지
|
||||
if (cachedData.axis != layout.axis)
|
||||
{
|
||||
cachedData.axis = layout.axis;
|
||||
@@ -255,6 +416,7 @@ namespace Gpm.Ui
|
||||
{
|
||||
cachedData.IsVertical = IsVertical;
|
||||
|
||||
// ScrollRect의 스크롤 방향을 동기화
|
||||
if (layout.IsVertical() == true)
|
||||
{
|
||||
scrollRect.vertical = true;
|
||||
@@ -280,10 +442,12 @@ namespace Gpm.Ui
|
||||
isUpdateLayout = true;
|
||||
}
|
||||
|
||||
// 레이아웃 구조가 변경되었다면, 앵커와 피벗을 다시 계산하고 적용
|
||||
if (isUpdateLayout == true)
|
||||
{
|
||||
anchorUpdate = true;
|
||||
|
||||
// 아이템과 콘텐츠의 앵커, 피벗 값을 새로 계산하여 캐시
|
||||
Rect anchor = layout.GetItemAnchor();
|
||||
cachedData.anchorMin = new Vector2(anchor.xMin, anchor.yMin);
|
||||
cachedData.anchorMax = new Vector2(anchor.xMax, anchor.yMax);
|
||||
@@ -291,58 +455,81 @@ namespace Gpm.Ui
|
||||
cachedData.itemPivot = layout.GetItemPivot();
|
||||
cachedData.contentPivot = layout.GetAxisPivot();
|
||||
|
||||
// 변경된 축 정보를 실제 RectTransform에 적용
|
||||
UpdateAxis();
|
||||
|
||||
isUpdateArea = true;
|
||||
isUpdateArea = true; // 영역 업데이트 필요
|
||||
|
||||
isUpdateLayout = false;
|
||||
anchorUpdate = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 캐시된 앵커 및 피벗 값을 콘텐츠와 아이템들의 RectTransform에 적용합니다.
|
||||
/// 스크롤 방향이 변경될 때 호출됩니다.
|
||||
/// </summary>
|
||||
private void UpdateAxis()
|
||||
{
|
||||
// 스크롤 위치를 보존하기 위해 현재 normalizedPosition을 저장
|
||||
Vector2 normalizedPosition = scrollRect.normalizedPosition;
|
||||
|
||||
// 콘텐츠(Content)의 앵커와 피벗을 설정
|
||||
content.anchorMin = cachedData.anchorMin;
|
||||
content.anchorMax = cachedData.anchorMax;
|
||||
content.pivot = cachedData.contentPivot;
|
||||
|
||||
// 현재 활성화된 모든 아이템 객체들의 앵커와 피벗도 동일하게 설정
|
||||
for (int index = 0; index < itemObjectList.Count; ++index)
|
||||
{
|
||||
itemObjectList[index].SetAxis(cachedData.anchorMin, cachedData.anchorMax, cachedData.itemPivot);
|
||||
}
|
||||
|
||||
// 스크롤 위치 복원
|
||||
scrollRect.normalizedPosition = normalizedPosition;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ScrollRect의 스크롤 가능 여부(vertical/horizontal)를 현재 레이아웃 축 설정에 맞게 조정합니다.
|
||||
/// </summary>
|
||||
public void CheckScrollAxis()
|
||||
{
|
||||
layout.CheckAxis(scrollRect);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 모든 아이템을 포함하는 데 필요한 총 거리를 계산합니다.
|
||||
/// </summary>
|
||||
protected float GetItemTotalSize()
|
||||
{
|
||||
return GetTotalDistance();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 특정 아이템까지의 거리를 계산합니다.
|
||||
/// </summary>
|
||||
/// <param name="itemIndex">아이템의 인덱스</param>
|
||||
/// <returns>스크롤 시작점부터 해당 아이템이 속한 라인까지의 거리</returns>
|
||||
protected float GetItemDistance(int itemIndex)
|
||||
{
|
||||
int lineIndex = GetLineIndex(itemIndex);
|
||||
return GetLineDistance(GetLineOffset(lineIndex), lineIndex);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 전체 데이터 리스트를 기반으로 레이아웃을 처음부터 다시 계산하고 빌드합니다.
|
||||
/// 데이터가 변경되거나 레이아웃 설정이 변경될 때 호출됩니다.
|
||||
/// </summary>
|
||||
protected void BuildLayout()
|
||||
{
|
||||
ClearLayout();
|
||||
|
||||
// 모든 데이터에 대해 반복
|
||||
for (int dataIndex = 0; dataIndex < dataList.Count; dataIndex++)
|
||||
{
|
||||
var context = dataList[dataIndex];
|
||||
|
||||
// 유효한 아이템만 레이아웃에 추가
|
||||
if (context.itemIndex == -1)
|
||||
{
|
||||
continue;
|
||||
@@ -351,11 +538,16 @@ namespace Gpm.Ui
|
||||
AddItem(context);
|
||||
}
|
||||
|
||||
// 계산된 레이아웃 크기에 맞게 콘텐츠(Content)의 크기를 조절
|
||||
ResizeContent();
|
||||
|
||||
isRebuildLayout = true;
|
||||
isRebuildLayout = true; // 재빌드가 완료되었음을 표시
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 스크롤 위치를 기준으로 화면에 보여야 할 라인(line)의 범위를 계산합니다.
|
||||
/// `showLineIndex`와 `showLineCount`를 설정합니다.
|
||||
/// </summary>
|
||||
protected void CheckShowLine()
|
||||
{
|
||||
bool showLine = false;
|
||||
@@ -363,14 +555,16 @@ namespace Gpm.Ui
|
||||
showLineIndex = 0;
|
||||
showLineCount = 0;
|
||||
|
||||
// 모든 라인을 순회하며 화면에 보이는지 검사
|
||||
for (int lineIndex = 0; lineIndex < GetLineCount(); lineIndex++)
|
||||
{
|
||||
var line = GetLine(lineIndex);
|
||||
if (showLine == false)
|
||||
{
|
||||
// 뷰포트 상단 이전에 끝나는 라인은 건너뛴다.
|
||||
if (IsShowBeforePosition(GetLineDistance(line.offset + line.size, lineIndex), cachedData.contentPosition) == false)
|
||||
{
|
||||
showLineIndex = lineIndex;
|
||||
showLineIndex = lineIndex; // 화면에 보이기 시작하는 첫 라인
|
||||
showLine = true;
|
||||
|
||||
showLineCount++;
|
||||
@@ -378,6 +572,7 @@ namespace Gpm.Ui
|
||||
}
|
||||
else
|
||||
{
|
||||
// 뷰포트 하단을 벗어나는 라인이 나오면 중단
|
||||
if (IsShowAfterPosition(GetLineDistance(line.offset, lineIndex), cachedData.contentPosition, cachedData.viewportSize) == true)
|
||||
{
|
||||
break;
|
||||
@@ -387,17 +582,25 @@ namespace Gpm.Ui
|
||||
}
|
||||
}
|
||||
|
||||
// 스크롤바 크기 조절
|
||||
ResizeScrollView();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 화면에 보이는 아이템들을 업데이트하고, 보이지 않는 아이템은 비활성화(재활용)합니다.
|
||||
/// 스크롤이 움직일 때 주로 호출됩니다.
|
||||
/// </summary>
|
||||
/// <param name="forceUpdateData">true이면 보이는 모든 아이템의 데이터를 강제로 새로고침합니다.</param>
|
||||
protected void UpdateShowItem(bool forceUpdateData = false)
|
||||
{
|
||||
// 이미 처리 중이면 중복 실행 방지
|
||||
if (forceUpdateData == false &&
|
||||
processing == true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 아이템 업데이트가 필요 없으면 종료
|
||||
if (NeedUpdateItem() == false)
|
||||
{
|
||||
return;
|
||||
@@ -405,6 +608,7 @@ namespace Gpm.Ui
|
||||
|
||||
processing = true;
|
||||
|
||||
// 레이아웃 재빌드가 필요하면 실행
|
||||
if (forceUpdateData == true ||
|
||||
needReBuildLayout == true)
|
||||
{
|
||||
@@ -412,6 +616,7 @@ namespace Gpm.Ui
|
||||
needReBuildLayout = false;
|
||||
}
|
||||
|
||||
// 스크롤 영역이 변경되었거나 레이아웃이 재빌드되었다면, 보이는 라인을 다시 계산
|
||||
if (forceUpdateData == true ||
|
||||
isUpdateArea == true ||
|
||||
isRebuildLayout == true)
|
||||
@@ -422,6 +627,7 @@ namespace Gpm.Ui
|
||||
isRebuildLayout = false;
|
||||
}
|
||||
|
||||
// 보이는 아이템의 첫 인덱스와 마지막 인덱스를 계산
|
||||
int prevFirstItemIndex = firstItemIndex;
|
||||
firstItemIndex = GetLineFirstItemIndex(showLineIndex);
|
||||
|
||||
@@ -436,12 +642,15 @@ namespace Gpm.Ui
|
||||
lastItemIndex = firstItemIndex;
|
||||
}
|
||||
|
||||
// 보이는 아이템 범위가 변경되었는지 확인
|
||||
if (prevFirstItemIndex < firstItemIndex ||
|
||||
prevLastItemIndex > lastItemIndex)
|
||||
{
|
||||
changeValue = true;
|
||||
}
|
||||
|
||||
// ** 아이템 비활성화 (재활용) **
|
||||
// 현재 활성화된 아이템 객체들 중, 새로 계산된 보이는 범위(first ~ last)를 벗어난 것들을 비활성화
|
||||
for (int index = 0; index < itemObjectList.Count; ++index)
|
||||
{
|
||||
int linkedIndex = itemObjectList[index].GetItemIndex();
|
||||
@@ -455,6 +664,8 @@ namespace Gpm.Ui
|
||||
}
|
||||
}
|
||||
|
||||
// ** 아이템 활성화 및 업데이트 **
|
||||
// 화면에 보여야 할 라인들을 순회
|
||||
for (int lineIndex = showLineIndex; lineIndex < showLineIndex + showLineCount; lineIndex++)
|
||||
{
|
||||
if (lineIndex >= GetLineCount())
|
||||
@@ -463,14 +674,17 @@ namespace Gpm.Ui
|
||||
}
|
||||
|
||||
var line = GetLine(lineIndex);
|
||||
// 해당 라인에 속한 모든 아이템에 대해
|
||||
for (int i = 0; i < line.GetCount(); i++)
|
||||
{
|
||||
var context = line.dataList[i];
|
||||
|
||||
// 아이템 풀에서 재활용할 아이템 객체를 가져옴
|
||||
InfiniteScrollItem item = PullItem(context);
|
||||
|
||||
bool needUpdateItemData = false;
|
||||
|
||||
// 아이템이 비활성 상태이거나, 데이터가 변경되었으면 데이터 업데이트 필요
|
||||
if (item.IsActive() == false ||
|
||||
item.GetDataIndex() != context.index ||
|
||||
context.IsNeedUpdateItemData() == true)
|
||||
@@ -479,11 +693,13 @@ namespace Gpm.Ui
|
||||
changeValue = true;
|
||||
}
|
||||
|
||||
// 데이터 업데이트가 필요하거나, 강제 업데이트 모드일 때
|
||||
if (needUpdateItemData == true || forceUpdateData == true)
|
||||
{
|
||||
item.UpdateItem(context);
|
||||
}
|
||||
|
||||
// 아이템이 비활성 상태였다면 활성화
|
||||
if (item.IsActive() == false)
|
||||
{
|
||||
item.SetActive(true, true);
|
||||
@@ -491,10 +707,12 @@ namespace Gpm.Ui
|
||||
|
||||
RectTransform itemTransform = (RectTransform)item.transform;
|
||||
|
||||
// 앵커와 피벗 설정
|
||||
itemTransform.anchorMin = cachedData.anchorMin;
|
||||
itemTransform.anchorMax = cachedData.anchorMax;
|
||||
itemTransform.pivot = cachedData.itemPivot;
|
||||
|
||||
// 동적 아이템 크기 모드에서 크기 업데이트가 필요하면 적용
|
||||
if (item.needUpdateItemSize == true)
|
||||
{
|
||||
float size = context.GetItemSize();
|
||||
@@ -504,6 +722,7 @@ namespace Gpm.Ui
|
||||
item.needUpdateItemSize = false;
|
||||
}
|
||||
|
||||
// 최종 위치 계산 및 적용
|
||||
FitItemPosition(itemTransform, context.itemIndex);
|
||||
}
|
||||
}
|
||||
@@ -519,23 +738,37 @@ namespace Gpm.Ui
|
||||
processing = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 아이템의 RectTransform 위치를 계산하고 적용합니다.
|
||||
/// </summary>
|
||||
/// <param name="rectTransform">위치를 설정할 아이템의 RectTransform</param>
|
||||
/// <param name="itemIndex">아이템의 인덱스</param>
|
||||
protected void FitItemPosition(RectTransform rectTransform, int itemIndex)
|
||||
{
|
||||
// 교차 축 위치 설정 (예: 수직 스크롤에서 좌/우 위치)
|
||||
layout.FitItemInlinePosition(rectTransform, itemIndex, GetCrossSize());
|
||||
|
||||
// 주 축 위치 설정 (예: 수직 스크롤에서 상/하 위치)
|
||||
float itemPosition = GetItemPosition(itemIndex);
|
||||
rectTransform.anchoredPosition = layout.GetAxisVector(Vector2.zero, itemPosition);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 오프셋 값을 실제 anchoredPosition 값으로 변환합니다.
|
||||
/// </summary>
|
||||
protected float ItemPostionFromOffset(float offset)
|
||||
{
|
||||
float postion = layout.GetAxisPostionFromOffset(offset);
|
||||
|
||||
// 콘텐츠와 아이템의 피벗 차이를 보정
|
||||
postion += GetPivotPostion();
|
||||
|
||||
return postion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 콘텐츠와 아이템의 피벗 차이로 인해 발생하는 위치 오차를 계산합니다.
|
||||
/// </summary>
|
||||
protected float GetPivotPostion()
|
||||
{
|
||||
float contentSize = GetContentSize();
|
||||
@@ -549,12 +782,19 @@ namespace Gpm.Ui
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 레이아웃 계산에 사용되는 변수들을 초기화합니다.
|
||||
/// </summary>
|
||||
protected void ClearLayout()
|
||||
{
|
||||
layoutSize = 0;
|
||||
lineCount = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 데이터를 레이아웃에 추가합니다. 그리드 설정에 따라 자동으로 줄바꿈을 처리합니다.
|
||||
/// </summary>
|
||||
/// <param name="context">추가할 아이템의 데이터 컨텍스트</param>
|
||||
protected void AddItem(DataContext context)
|
||||
{
|
||||
bool newLine = false;
|
||||
@@ -563,11 +803,13 @@ namespace Gpm.Ui
|
||||
int lineIndex = lineCount - 1;
|
||||
if (lineCount == 0)
|
||||
{
|
||||
// 첫 아이템은 항상 새 라인에서 시작
|
||||
newLine = true;
|
||||
}
|
||||
else if (lineIndex < lineCount)
|
||||
{
|
||||
currentLine = lineLayout[lineIndex];
|
||||
// 현재 라인이 그리드 수만큼 꽉 찼으면 새 라인으로
|
||||
if (currentLine.GetCount() >= layout.GridCount())
|
||||
{
|
||||
newLine = true;
|
||||
@@ -582,6 +824,7 @@ namespace Gpm.Ui
|
||||
{
|
||||
lineIndex = lineCount;
|
||||
|
||||
// 기존에 생성된 LineLayout 객체가 있으면 재사용, 없으면 새로 생성
|
||||
if (lineIndex < lineLayout.Count)
|
||||
{
|
||||
currentLine = lineLayout[lineIndex];
|
||||
@@ -593,7 +836,7 @@ namespace Gpm.Ui
|
||||
}
|
||||
|
||||
currentLine.Clear();
|
||||
currentLine.offset = layoutSize;
|
||||
currentLine.offset = layoutSize; // 새 라인의 시작 위치는 현재까지의 총 레이아웃 크기
|
||||
|
||||
newLine = false;
|
||||
lineCount++;
|
||||
@@ -603,36 +846,50 @@ namespace Gpm.Ui
|
||||
currentLine = lineLayout[lineIndex];
|
||||
}
|
||||
|
||||
// 현재 라인에 아이템을 추가하고, 레이아웃 전체 크기를 갱신
|
||||
layoutSize = currentLine.Add(context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 아이템 인덱스로 해당 아이템의 데이터 컨텍스트를 가져옵니다.
|
||||
/// </summary>
|
||||
protected DataContext GetItem(int itemIndex)
|
||||
{
|
||||
if (layout.IsGrid() == true)
|
||||
{
|
||||
int gridCount = layout.GridCount();
|
||||
|
||||
int line = itemIndex / gridCount;
|
||||
int index = itemIndex % gridCount;
|
||||
int line = itemIndex / gridCount; // 아이템이 속한 라인 인덱스
|
||||
int index = itemIndex % gridCount; // 라인 내에서의 인덱스
|
||||
|
||||
return lineLayout[line].dataList[index];
|
||||
}
|
||||
else
|
||||
{
|
||||
// 그리드가 아니면 아이템 인덱스가 라인 인덱스와 동일
|
||||
return lineLayout[itemIndex].dataList[0];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 라인 인덱스로 해당 라인의 LineLayout 객체를 가져옵니다.
|
||||
/// </summary>
|
||||
protected LineLayout GetLine(int lineIndex)
|
||||
{
|
||||
return lineLayout[lineIndex];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 라인 인덱스로 해당 라인의 시작 위치(offset)를 가져옵니다.
|
||||
/// </summary>
|
||||
protected float GetLineOffset(int lineIndex)
|
||||
{
|
||||
return lineLayout[lineIndex].offset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 아이템 인덱스로 해당 아이템이 속한 라인의 시작 위치(offset)를 가져옵니다.
|
||||
/// </summary>
|
||||
protected float GetItemOffset(int itemIndex)
|
||||
{
|
||||
int lineIndex = GetLineIndex(itemIndex);
|
||||
@@ -640,15 +897,21 @@ namespace Gpm.Ui
|
||||
return lineLayout[lineIndex].offset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 아이템 인덱스로 해당 아이템이 속한 라인의 인덱스를 계산합니다.
|
||||
/// </summary>
|
||||
protected int GetLineIndex(int itemIndex)
|
||||
{
|
||||
if (layout.IsGrid() == true)
|
||||
{
|
||||
return itemIndex / layout.GridCount();
|
||||
}
|
||||
return itemIndex;
|
||||
return itemIndex; // 그리드가 아니면 아이템 인덱스 = 라인 인덱스
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 해당 아이템이 라인의 마지막 아이템인지 확인합니다.
|
||||
/// </summary>
|
||||
protected bool IsLast(int itemIndex)
|
||||
{
|
||||
if (layout.IsGrid() == true)
|
||||
@@ -661,27 +924,41 @@ namespace Gpm.Ui
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return true; // 그리드가 아니면 모든 아이템이 마지막 아이템
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 레이아웃의 총 라인 수를 가져옵니다.
|
||||
/// </summary>
|
||||
protected int GetLineCount()
|
||||
{
|
||||
return lineCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 모든 아이템, 여백, 간격을 포함한 콘텐츠의 총 거리를 계산합니다.
|
||||
/// </summary>
|
||||
protected float GetTotalDistance()
|
||||
{
|
||||
int lineCount = GetLineCount();
|
||||
|
||||
// 총 라인 크기 + 상하/좌우 여백
|
||||
float size = layoutSize + GetMainPadding() * UPDOWN_MULTIPLY;
|
||||
if (lineCount > 1)
|
||||
{
|
||||
// 라인 간의 간격 추가
|
||||
size += ((lineCount - 1) * GetMainSpace());
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 특정 라인까지의 실제 거리를 계산합니다. (여백, 간격 포함)
|
||||
/// </summary>
|
||||
/// <param name="offset">라인의 기본 오프셋</param>
|
||||
/// <param name="lineIndex">라인의 인덱스</param>
|
||||
/// <returns>스크롤 시작점부터의 실제 거리</returns>
|
||||
protected float GetLineDistance(float offset, int lineIndex)
|
||||
{
|
||||
float distance = offset + GetMainPadding();
|
||||
@@ -693,7 +970,9 @@ namespace Gpm.Ui
|
||||
return distance;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 특정 라인에 포함된 첫 번째 아이템의 인덱스를 가져옵니다.
|
||||
/// </summary>
|
||||
protected int GetLineFirstItemIndex(int lineIndex)
|
||||
{
|
||||
int lineCount = GetLineCount();
|
||||
@@ -721,10 +1000,13 @@ namespace Gpm.Ui
|
||||
return firstItemIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 특정 라인에 포함된 마지막 아이템의 인덱스를 가져옵니다.
|
||||
/// </summary>
|
||||
protected int GetLineLastItemIndex(int lineIndex)
|
||||
{
|
||||
int lineCount = GetLineCount();
|
||||
if(lineCount == 0)
|
||||
if (lineCount == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@@ -740,7 +1022,7 @@ namespace Gpm.Ui
|
||||
{
|
||||
lastItemIndex = lineIndex * layout.GridCount();
|
||||
}
|
||||
|
||||
|
||||
lastItemIndex += lineLayout[lineIndex].GetCount() - 1;
|
||||
}
|
||||
else
|
||||
@@ -751,6 +1033,9 @@ namespace Gpm.Ui
|
||||
return lastItemIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 특정 라인의 크기(주 축 기준)를 가져옵니다.
|
||||
/// </summary>
|
||||
protected float GetLineSize(int lineIndex)
|
||||
{
|
||||
int lineCount = GetLineCount();
|
||||
|
||||
@@ -2,36 +2,74 @@ namespace Gpm.Ui
|
||||
{
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.UI;
|
||||
|
||||
// 이 partial 클래스는 InfiniteScroll의 스크롤 이동 관련 로직을 담당합니다.
|
||||
// 특정 아이템으로 이동하거나, 스크롤 위치를 직접 제어하는 기능을 구현합니다.
|
||||
public partial class InfiniteScroll : IMoveScroll
|
||||
{
|
||||
/// <summary>
|
||||
/// 특정 아이템으로 이동할 때, 뷰포트 내에서 해당 아이템을 정렬할 위치를 지정합니다.
|
||||
/// </summary>
|
||||
public enum MoveToType
|
||||
{
|
||||
/// <summary>
|
||||
/// 아이템을 뷰포트의 상단(Vertical) 또는 왼쪽(Horizontal)에 맞춥니다.
|
||||
/// </summary>
|
||||
MOVE_TO_TOP = 0,
|
||||
|
||||
/// <summary>
|
||||
/// 아이템을 뷰포트의 중앙에 맞춥니다.
|
||||
/// </summary>
|
||||
MOVE_TO_CENTER,
|
||||
|
||||
/// <summary>
|
||||
/// 아이템을 뷰포트의 하단(Vertical) 또는 오른쪽(Horizontal)에 맞춥니다.
|
||||
/// </summary>
|
||||
MOVE_TO_BOTTOM
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 스크롤 이동 애니메이션을 제어하는 정적 클래스입니다.
|
||||
/// ScrollMoveTo 컴포넌트를 사용하여 부드러운 스크롤 효과를 구현합니다.
|
||||
/// </summary>
|
||||
public static class Control
|
||||
{
|
||||
/// <summary>
|
||||
/// 지정된 아이템 인덱스로 일정 시간 동안 부드럽게 스크롤합니다.
|
||||
/// </summary>
|
||||
/// <param name="scroll">대상 InfiniteScroll 인스턴스</param>
|
||||
/// <param name="itemIndex">이동할 아이템의 인덱스</param>
|
||||
/// <param name="moveToType">뷰포트 내 정렬 방식</param>
|
||||
/// <param name="time">애니메이션 시간 (0이면 즉시 이동)</param>
|
||||
public static void MoveTo(InfiniteScroll scroll, int itemIndex, MoveToType moveToType, float time = 0)
|
||||
{
|
||||
// ScrollMoveTo 컴포넌트가 없으면 동적으로 추가합니다.
|
||||
ScrollMoveTo moveto = scroll.gameObject.GetComponent<ScrollMoveTo>();
|
||||
if (moveto == null)
|
||||
{
|
||||
moveto = scroll.gameObject.AddComponent<ScrollMoveTo>();
|
||||
}
|
||||
|
||||
// 이동 목표 설정
|
||||
moveto.Set(itemIndex, moveToType, time);
|
||||
// 애니메이션 커브 설정 (부드러운 시작과 끝)
|
||||
moveto.curve = AnimationCurve.EaseInOut(0, 0, 1, 1);
|
||||
|
||||
// 이동 완료 후 컴포넌트 자동 파괴 설정
|
||||
moveto.autoDestory = true;
|
||||
|
||||
// 애니메이션 재생
|
||||
moveto.Play();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 지정된 스크롤 비율(0.0 ~ 1.0)로 일정 시간 동안 부드럽게 스크롤합니다.
|
||||
/// </summary>
|
||||
/// <param name="scroll">대상 InfiniteScroll 인스턴스</param>
|
||||
/// <param name="scrollRate">이동할 스크롤 비율 (0.0 = 시작, 1.0 = 끝)</param>
|
||||
/// <param name="time">애니메이션 시간 (0이면 즉시 이동)</param>
|
||||
public static void MoveTo(InfiniteScroll scroll, float scrollRate, float time = 0)
|
||||
{
|
||||
ScrollMoveTo moveto = scroll.gameObject.GetComponent<ScrollMoveTo>();
|
||||
@@ -49,6 +87,8 @@ namespace Gpm.Ui
|
||||
}
|
||||
}
|
||||
|
||||
// 스크롤 위치 비교 시 오차를 보정하기 위한 값입니다. (콘텐츠 크기의 0.01%)
|
||||
// 부동 소수점 연산의 정밀도 문제로 인해 정확히 0 또는 1이 되지 않는 경우를 처리합니다.
|
||||
private const float UPDOWN_MULTIPLY = 2.0f;
|
||||
private const int NEEDITEM_MORE_LINE = 1;
|
||||
private const int NEEDITEM_EXTRA_ADD = 2;
|
||||
@@ -57,13 +97,39 @@ namespace Gpm.Ui
|
||||
protected RectTransform viewport = null;
|
||||
|
||||
protected float sizeInterpolationValue = 0.0001f; // 0.01%
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 특정 데이터(InfiniteScrollData)에 해당하는 아이템으로 스크롤을 이동합니다.
|
||||
/// </summary>
|
||||
/// <param name="data">이동할 대상 데이터</param>
|
||||
/// <param name="moveToType">뷰포트 내 정렬 방식</param>
|
||||
/// <param name="time">이동에 걸리는 시간 (0이면 즉시 이동)</param>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// InfiniteScrollData targetData = myScroll.GetData(10);
|
||||
/// if (targetData != null)
|
||||
/// {
|
||||
/// myScroll.MoveTo(targetData, InfiniteScroll.MoveToType.MOVE_TO_CENTER, 0.5f);
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public void MoveTo(InfiniteScrollData data, MoveToType moveToType, float time = 0)
|
||||
{
|
||||
MoveTo(GetItemIndex(data), moveToType, time);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 화면에 보이는 아이템 목록 기준의 인덱스로 스크롤을 이동합니다.
|
||||
/// </summary>
|
||||
/// <param name="itemIndex">이동할 아이템의 인덱스 (화면에 보이는 아이템 기준)</param>
|
||||
/// <param name="moveToType">뷰포트 내 정렬 방식</param>
|
||||
/// <param name="time">이동에 걸리는 시간 (0이면 즉시 이동)</param>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // 50번째 아이템으로 0.3초 동안 스크롤 (아이템이 뷰포트 상단에 오도록)
|
||||
/// myScroll.MoveTo(50, InfiniteScroll.MoveToType.MOVE_TO_TOP, 0.3f);
|
||||
/// </code>
|
||||
/// </example>
|
||||
public void MoveTo(int itemIndex, MoveToType moveToType, float time = 0)
|
||||
{
|
||||
if (isInitialize == false)
|
||||
@@ -75,16 +141,31 @@ namespace Gpm.Ui
|
||||
{
|
||||
if (time > 0)
|
||||
{
|
||||
// 시간이 지정된 경우 Control 클래스를 통해 부드럽게 이동
|
||||
Control.MoveTo(this, itemIndex, moveToType, time);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 즉시 이동
|
||||
SetScrollPosition(GetMovePosition(itemIndex, moveToType));
|
||||
Control.MoveTo(this, itemIndex, moveToType, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 전체 데이터 목록 기준의 인덱스로 스크롤을 이동합니다.
|
||||
/// 필터링이 적용된 상태에서, 필터링과 상관없이 원본 데이터의 인덱스를 사용하고 싶을 때 유용합니다.
|
||||
/// </summary>
|
||||
/// <param name="dataIndex">이동할 데이터의 인덱스 (전체 데이터 리스트 기준)</param>
|
||||
/// <param name="moveToType">뷰포트 내 정렬 방식</param>
|
||||
/// <param name="time">이동에 걸리는 시간 (0이면 즉시 이동)</param>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // 원본 데이터 리스트의 100번째 데이터로 즉시 이동
|
||||
/// myScroll.MoveToFromDataIndex(100, InfiniteScroll.MoveToType.MOVE_TO_CENTER);
|
||||
/// </code>
|
||||
/// </example>
|
||||
public void MoveToFromDataIndex(int dataIndex, MoveToType moveToType, float time = 0)
|
||||
{
|
||||
if (isInitialize == false)
|
||||
@@ -98,6 +179,17 @@ namespace Gpm.Ui
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 전체 스크롤 범위의 특정 비율(0.0 ~ 1.0)로 스크롤을 이동합니다.
|
||||
/// </summary>
|
||||
/// <param name="scrollRate">이동할 비율 (0.0 = 시작, 0.5 = 중간, 1.0 = 끝)</param>
|
||||
/// <param name="time">이동에 걸리는 시간 (0이면 즉시 이동)</param>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // 스크롤을 80% 위치로 1초 동안 이동
|
||||
/// myScroll.MoveTo(0.8f, 1.0f);
|
||||
/// </code>
|
||||
/// </example>
|
||||
public void MoveTo(float scrollRate, float time = 0)
|
||||
{
|
||||
if (isInitialize == false)
|
||||
@@ -112,10 +204,22 @@ namespace Gpm.Ui
|
||||
else
|
||||
{
|
||||
SetScrollPosition(GetMovePosition(scrollRate));
|
||||
Control.MoveTo(this, scrollRate, 0);
|
||||
Control.MoveTo(this, scrollRate, 0); // 위치 보정을 위해 호출
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 스크롤을 가장 처음 위치로 이동합니다.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // '맨 위로' 버튼 클릭 시 호출
|
||||
/// public void OnClick_MoveToTop()
|
||||
/// {
|
||||
/// myScroll.MoveToFirstData();
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public void MoveToFirstData()
|
||||
{
|
||||
if (isInitialize == false)
|
||||
@@ -128,11 +232,11 @@ namespace Gpm.Ui
|
||||
Vector2 normalizedPosition;
|
||||
if (layout.IsVertical() == true)
|
||||
{
|
||||
normalizedPosition = Vector2.one;
|
||||
normalizedPosition = Vector2.one; // 세로 스크롤의 시작은 (0, 1)
|
||||
}
|
||||
else
|
||||
{
|
||||
normalizedPosition = Vector2.zero;
|
||||
normalizedPosition = Vector2.zero; // 가로 스크롤의 시작은 (0, 0)
|
||||
}
|
||||
|
||||
if (scrollRect.normalizedPosition != normalizedPosition)
|
||||
@@ -141,9 +245,21 @@ namespace Gpm.Ui
|
||||
isUpdateArea = true;
|
||||
}
|
||||
|
||||
MoveTo(0);
|
||||
MoveTo(0); // 0% 위치로 이동
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 스크롤을 가장 마지막 위치로 이동합니다.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // '맨 아래로' 버튼 클릭 시 호출
|
||||
/// public void OnClick_MoveToBottom()
|
||||
/// {
|
||||
/// myScroll.MoveToLastData();
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public void MoveToLastData()
|
||||
{
|
||||
if (isInitialize == false)
|
||||
@@ -156,11 +272,11 @@ namespace Gpm.Ui
|
||||
Vector2 normalizedPosition;
|
||||
if (layout.IsVertical() == true)
|
||||
{
|
||||
normalizedPosition = Vector2.zero;
|
||||
normalizedPosition = Vector2.zero; // 세로 스크롤의 끝은 (0, 0)
|
||||
}
|
||||
else
|
||||
{
|
||||
normalizedPosition = Vector2.one;
|
||||
normalizedPosition = Vector2.one; // 가로 스크롤의 끝은 (1, 0)
|
||||
}
|
||||
if (scrollRect.normalizedPosition != normalizedPosition)
|
||||
{
|
||||
@@ -168,9 +284,21 @@ namespace Gpm.Ui
|
||||
isUpdateArea = true;
|
||||
}
|
||||
|
||||
MoveTo(1);
|
||||
MoveTo(1); // 100% 위치로 이동
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 스크롤이 가장 처음에 도달했는지 확인합니다.
|
||||
/// </summary>
|
||||
/// <returns>처음 위치에 있으면 true, 아니면 false를 반환합니다.</returns>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// if (myScroll.IsMoveToFirstData())
|
||||
/// {
|
||||
/// Debug.Log("스크롤이 맨 위에 있습니다.");
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public bool IsMoveToFirstData()
|
||||
{
|
||||
if (isInitialize == false)
|
||||
@@ -190,8 +318,19 @@ namespace Gpm.Ui
|
||||
return IsMoveToFirstData(contentPosition, viewportSize, contentSize);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 스크롤이 가장 마지막에 도달했는지 확인합니다.
|
||||
/// </summary>
|
||||
/// <returns>마지막 위치에 있으면 true, 아니면 false를 반환합니다.</returns>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// if (myScroll.IsMoveToLastData())
|
||||
/// {
|
||||
/// Debug.Log("스크롤이 맨 아래에 있습니다.");
|
||||
/// // 추가 데이터 로딩 로직 호출 가능
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public bool IsMoveToLastData()
|
||||
{
|
||||
if (isInitialize == false)
|
||||
@@ -206,11 +345,17 @@ namespace Gpm.Ui
|
||||
return IsMoveToLastData(contentPosition, viewportSize, contentSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 지정된 스크롤 비율에 해당하는 content의 anchoredPosition을 계산합니다.
|
||||
/// </summary>
|
||||
/// <param name="scrollRate">계산할 스크롤 비율 (0.0 ~ 1.0)</param>
|
||||
/// <returns>계산된 스크롤 위치(Vector2)</returns>
|
||||
public Vector2 GetMovePosition(float scrollRate)
|
||||
{
|
||||
float viewportSize = GetViewportSize();
|
||||
float contentSize = GetContentSize();
|
||||
|
||||
// 전체 스크롤 가능 범위에서 비율만큼의 위치를 계산
|
||||
float move = (contentSize - viewportSize) * Mathf.Clamp01(scrollRate);
|
||||
move = Math.Max(0.0f, move);
|
||||
|
||||
@@ -219,6 +364,12 @@ namespace Gpm.Ui
|
||||
return layout.GetAxisVector(content.anchoredPosition, move);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 지정된 아이템 인덱스로 이동하기 위한 content의 anchoredPosition을 계산합니다.
|
||||
/// </summary>
|
||||
/// <param name="itemIndex">대상 아이템의 인덱스</param>
|
||||
/// <param name="moveToType">뷰포트 내 정렬 방식</param>
|
||||
/// <returns>계산된 스크롤 위치(Vector2)</returns>
|
||||
public Vector2 GetMovePosition(int itemIndex, MoveToType moveToType)
|
||||
{
|
||||
float move = GetMoveOffset(itemIndex, moveToType);
|
||||
@@ -226,11 +377,20 @@ namespace Gpm.Ui
|
||||
return layout.GetAxisVector(content.anchoredPosition, move);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 스크롤 content의 anchoredPosition을 가져옵니다.
|
||||
/// </summary>
|
||||
/// <returns>현재 스크롤 위치(Vector2)</returns>
|
||||
public Vector2 GetScrollPosition()
|
||||
{
|
||||
return content.anchoredPosition;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 스크롤 content의 anchoredPosition을 지정된 위치로 설정합니다.
|
||||
/// 이 메서드는 스크롤 위치를 즉시 변경합니다.
|
||||
/// </summary>
|
||||
/// <param name="position">설정할 위치(Vector2)</param>
|
||||
public void SetScrollPosition(Vector2 position)
|
||||
{
|
||||
content.anchoredPosition = position;
|
||||
@@ -239,10 +399,14 @@ namespace Gpm.Ui
|
||||
if (cachedData.contentPosition != contentPosition)
|
||||
{
|
||||
cachedData.contentPosition = contentPosition;
|
||||
isUpdateArea = true;
|
||||
isUpdateArea = true; // 위치가 변경되었으므로 업데이트 필요 플래그 설정
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 스크롤의 주 축(Vertical이면 Y, Horizontal이면 X) 위치를 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="movePosition">설정할 주 축의 위치 값</param>
|
||||
public void SetScrollPosition(float movePosition)
|
||||
{
|
||||
Vector2 prevPosition = GetScrollPosition();
|
||||
@@ -250,6 +414,10 @@ namespace Gpm.Ui
|
||||
SetScrollPosition(layout.GetAxisVector(prevPosition, movePosition));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 스크롤 content의 위치와 크기를 초기화합니다.
|
||||
/// 데이터는 그대로 유지됩니다.
|
||||
/// </summary>
|
||||
public void ClearScrollContent()
|
||||
{
|
||||
content.anchoredPosition = Vector2.zero;
|
||||
@@ -260,6 +428,25 @@ namespace Gpm.Ui
|
||||
CheckScrollData();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 내부 ScrollRect의 onValueChanged 이벤트에 리스너를 추가합니다.
|
||||
/// 사용자가 스크롤을 움직일 때마다 콜백을 받고 싶을 때 사용합니다.
|
||||
/// </summary>
|
||||
/// <param name="listener">추가할 리스너(콜백 함수)</param>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// void Start()
|
||||
/// {
|
||||
/// myScroll.AddScrollValueChangedLisnter(OnScrollMoved);
|
||||
/// }
|
||||
///
|
||||
/// void OnScrollMoved(Vector2 position)
|
||||
/// {
|
||||
/// // position은 0.0 ~ 1.0 사이의 정규화된 값입니다.
|
||||
/// Debug.Log($"스크롤 위치 변경: {position}");
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public void AddScrollValueChangedLisnter(UnityAction<Vector2> listener)
|
||||
{
|
||||
if (isInitialize == false)
|
||||
@@ -270,16 +457,19 @@ namespace Gpm.Ui
|
||||
scrollRect.onValueChanged.AddListener(listener);
|
||||
}
|
||||
|
||||
// 스크롤이 시작 지점에 있는지 내부적으로 계산하는 함수
|
||||
private bool IsMoveToFirstData(float position, float viewportSize, float contentSize)
|
||||
{
|
||||
bool isShow = false;
|
||||
|
||||
// 콘텐츠 크기가 뷰포트보다 작으면 항상 시작/끝에 있는 것으로 간주
|
||||
if (viewportSize > contentSize)
|
||||
{
|
||||
isShow = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 부동 소수점 오차를 감안하여 비교
|
||||
float interpolation = contentSize * sizeInterpolationValue;
|
||||
if (-position > -interpolation)
|
||||
{
|
||||
@@ -290,6 +480,7 @@ namespace Gpm.Ui
|
||||
return isShow;
|
||||
}
|
||||
|
||||
// 스크롤이 끝 지점에 있는지 내부적으로 계산하는 함수
|
||||
private bool IsMoveToLastData(float position, float viewportSize, float contentSize)
|
||||
{
|
||||
bool isShow = false;
|
||||
@@ -302,6 +493,7 @@ namespace Gpm.Ui
|
||||
{
|
||||
float interpolation = contentSize * sizeInterpolationValue;
|
||||
|
||||
// 끝 지점 계산 (뷰포트 크기 + 현재 위치 - 콘텐츠 크기)
|
||||
if (-(viewportSize + position - contentSize) <= interpolation)
|
||||
{
|
||||
isShow = true;
|
||||
@@ -311,6 +503,12 @@ namespace Gpm.Ui
|
||||
return isShow;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 특정 아이템으로 이동하기 위한 스크롤 content의 오프셋(offset)을 계산합니다.
|
||||
/// </summary>
|
||||
/// <param name="itemIndex">대상 아이템 인덱스</param>
|
||||
/// <param name="moveToType">뷰포트 내 정렬 방식</param>
|
||||
/// <returns>계산된 오프셋 값</returns>
|
||||
protected float GetMoveOffset(int itemIndex, MoveToType moveToType)
|
||||
{
|
||||
float viewportSize = GetViewportSize();
|
||||
@@ -318,33 +516,38 @@ namespace Gpm.Ui
|
||||
|
||||
float move = 0.0f;
|
||||
float itemSize = GetItemSize(itemIndex);
|
||||
float distance = GetItemDistance(itemIndex);
|
||||
float distance = GetItemDistance(itemIndex); // 아이템의 시작 위치
|
||||
|
||||
move = distance;
|
||||
|
||||
// 정렬 방식에 따라 오프셋 조정
|
||||
switch (moveToType)
|
||||
{
|
||||
case MoveToType.MOVE_TO_CENTER:
|
||||
{
|
||||
// (뷰포트 중앙) - (아이템 중앙)
|
||||
move -= viewportSize * 0.5f - itemSize * 0.5f;
|
||||
break;
|
||||
}
|
||||
case MoveToType.MOVE_TO_BOTTOM:
|
||||
{
|
||||
// (뷰포트 하단) - (아이템 하단)
|
||||
move -= viewportSize - itemSize;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 계산된 위치가 스크롤 가능 범위를 벗어나지 않도록 제한
|
||||
move = Mathf.Clamp(move, 0.0f, contentSize - viewportSize);
|
||||
move = Math.Max(0.0f, move);
|
||||
|
||||
return ItemPostionFromOffset(move);
|
||||
}
|
||||
|
||||
|
||||
// ScrollRect의 onValueChanged 이벤트에 연결되어 호출되는 메서드
|
||||
private void OnValueChanged(Vector2 value)
|
||||
{
|
||||
// 스크롤이 시작/끝 지점에 도달했는지 확인하고 이벤트를 발생시킵니다.
|
||||
bool prevIsStartLine = isStartLine;
|
||||
isStartLine = IsMoveToFirstData();
|
||||
if (prevIsStartLine != isStartLine)
|
||||
@@ -363,6 +566,7 @@ namespace Gpm.Ui
|
||||
changeValue = true;
|
||||
}
|
||||
|
||||
// 스크롤이 움직였으므로 화면에 보이는 아이템을 갱신합니다.
|
||||
UpdateShowItem();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,83 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace Gpm.Ui
|
||||
{
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.Events;
|
||||
|
||||
/// <summary>
|
||||
/// 무한 스크롤 기능을 구현하는 클래스입니다.
|
||||
/// 이 클래스는 UI 항목의 동적 로딩 및 재활용을 처리하여 성능을 최적화합니다.
|
||||
/// </summary>
|
||||
public partial class InfiniteScroll : MonoBehaviour
|
||||
{
|
||||
// 초기화 여부를 확인하는 플래그입니다.
|
||||
protected bool isInitialize = false;
|
||||
|
||||
// 스크롤 뷰의 콘텐츠 RectTransform에 대한 참조입니다.
|
||||
protected RectTransform content = null;
|
||||
|
||||
// 스크롤 값이 변경되었는지 여부를 나타내는 플래그입니다.
|
||||
private bool changeValue = false;
|
||||
|
||||
[Header("Event", order = 4)]
|
||||
/// <summary>
|
||||
/// 스크롤 값이 변경될 때 발생하는 이벤트입니다.
|
||||
/// 매개변수: int (첫 번째 보이는 아이템 인덱스), int (마지막 보이는 아이템 인덱스), bool (시작 라인 여부), bool (끝 라인 여부)
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// infiniteScroll.onChangeValue.AddListener((first, last, isStart, isEnd) => {
|
||||
/// Debug.Log($"First visible item: {first}, Last visible item: {last}, Is at start: {isStart}, Is at end: {isEnd}");
|
||||
/// });
|
||||
/// </code>
|
||||
/// </example>
|
||||
public ChangeValueEvent onChangeValue = new ChangeValueEvent();
|
||||
|
||||
/// <summary>
|
||||
/// 아이템의 활성 상태가 변경될 때 발생하는 이벤트입니다.
|
||||
/// 매개변수: int (데이터 인덱스), bool (활성 상태)
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// infiniteScroll.onChangeActiveItem.AddListener((dataIndex, isActive) => {
|
||||
/// Debug.Log($"Item {dataIndex} is now {(isActive ? "active" : "inactive")}");
|
||||
/// });
|
||||
/// </code>
|
||||
/// </example>
|
||||
public ItemActiveEvent onChangeActiveItem = new ItemActiveEvent();
|
||||
|
||||
/// <summary>
|
||||
/// 스크롤이 시작 지점에 도달했을 때 발생하는 이벤트입니다.
|
||||
/// 매개변수: bool (시작 라인 여부)
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// infiniteScroll.onStartLine.AddListener((isAtStart) => {
|
||||
/// if (isAtStart) {
|
||||
/// Debug.Log("Scrolled to the beginning.");
|
||||
/// }
|
||||
/// });
|
||||
/// </code>
|
||||
/// </example>
|
||||
public StateChangeEvent onStartLine = new StateChangeEvent();
|
||||
|
||||
/// <summary>
|
||||
/// 스크롤이 끝 지점에 도달했을 때 발생하는 이벤트입니다.
|
||||
/// 매개변수: bool (끝 라인 여부)
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// infiniteScroll.onEndLine.AddListener((isAtEnd) => {
|
||||
/// if (isAtEnd) {
|
||||
/// Debug.Log("Scrolled to the end.");
|
||||
/// }
|
||||
/// });
|
||||
/// </code>
|
||||
/// </example>
|
||||
public StateChangeEvent onEndLine = new StateChangeEvent();
|
||||
|
||||
// 데이터 필터링에 사용되는 델리게이트입니다.
|
||||
private Predicate<InfiniteScrollData> onFilter = null;
|
||||
|
||||
private void Awake()
|
||||
@@ -26,6 +85,10 @@ namespace Gpm.Ui
|
||||
Initialize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// InfiniteScroll을 초기화합니다.
|
||||
/// ScrollRect 컴포넌트를 가져오고, 이벤트를 설정하며, 초기 아이템을 생성합니다.
|
||||
/// </summary>
|
||||
protected void Initialize()
|
||||
{
|
||||
if (isInitialize == false)
|
||||
@@ -55,6 +118,17 @@ namespace Gpm.Ui
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 스크롤에 단일 데이터 항목을 삽입합니다.
|
||||
/// </summary>
|
||||
/// <param name="data">삽입할 데이터입니다.</param>
|
||||
/// <param name="immediately">true이면 즉시 레이아웃을 업데이트합니다.</param>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// InfiniteScrollData myData = new InfiniteScrollData();
|
||||
/// infiniteScroll.InsertData(myData, true);
|
||||
/// </code>
|
||||
/// </example>
|
||||
public void InsertData(InfiniteScrollData data, bool immediately = false)
|
||||
{
|
||||
if (isInitialize == false)
|
||||
@@ -67,6 +141,18 @@ namespace Gpm.Ui
|
||||
UpdateAllData(immediately);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 지정된 인덱스에 단일 데이터 항목을 삽입합니다.
|
||||
/// </summary>
|
||||
/// <param name="data">삽입할 데이터입니다.</param>
|
||||
/// <param name="insertIndex">데이터를 삽입할 인덱스입니다.</param>
|
||||
/// <param name="immediately">true이면 즉시 레이아웃을 업데이트합니다.</param>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// InfiniteScrollData myData = new InfiniteScrollData();
|
||||
/// infiniteScroll.InsertData(myData, 0, true);
|
||||
/// </code>
|
||||
/// </example>
|
||||
public void InsertData(InfiniteScrollData data, int insertIndex, bool immediately = false)
|
||||
{
|
||||
if (insertIndex < 0 || insertIndex > dataList.Count)
|
||||
@@ -84,6 +170,18 @@ namespace Gpm.Ui
|
||||
UpdateAllData(immediately);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 스크롤에 여러 데이터 항목을 삽입합니다.
|
||||
/// </summary>
|
||||
/// <param name="datas">삽입할 데이터 배열입니다.</param>
|
||||
/// <param name="immediately">true이면 즉시 레이아웃을 업데이트합니다.</param>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// InfiniteScrollData[] myDataArray = new InfiniteScrollData[10];
|
||||
/// // myDataArray 초기화
|
||||
/// infiniteScroll.InsertData(myDataArray, true);
|
||||
/// </code>
|
||||
/// </example>
|
||||
public void InsertData(InfiniteScrollData[] datas, bool immediately = false)
|
||||
{
|
||||
if (isInitialize == false)
|
||||
@@ -98,6 +196,20 @@ namespace Gpm.Ui
|
||||
|
||||
UpdateAllData(immediately);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 지정된 인덱스부터 여러 데이터 항목을 삽입합니다.
|
||||
/// </summary>
|
||||
/// <param name="datas">삽입할 데이터 배열입니다.</param>
|
||||
/// <param name="insertIndex">데이터 삽입을 시작할 인덱스입니다.</param>
|
||||
/// <param name="immediately">true이면 즉시 레이아웃을 업데이트합니다.</param>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// InfiniteScrollData[] myDataArray = new InfiniteScrollData[10];
|
||||
/// // myDataArray 초기화
|
||||
/// infiniteScroll.InsertData(myDataArray, 5, true);
|
||||
/// </code>
|
||||
/// </example>
|
||||
public void InsertData(InfiniteScrollData[] datas, int insertIndex, bool immediately = false)
|
||||
{
|
||||
if (insertIndex < 0 || insertIndex > dataList.Count)
|
||||
@@ -118,6 +230,19 @@ namespace Gpm.Ui
|
||||
UpdateAllData(immediately);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 지정된 데이터 항목을 제거합니다.
|
||||
/// </summary>
|
||||
/// <param name="data">제거할 데이터입니다.</param>
|
||||
/// <param name="immediately">true이면 즉시 레이아웃을 업데이트합니다.</param>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// InfiniteScrollData dataToRemove = infiniteScroll.GetData(0);
|
||||
/// if (dataToRemove != null) {
|
||||
/// infiniteScroll.RemoveData(dataToRemove, true);
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public void RemoveData(InfiniteScrollData data, bool immediately = false)
|
||||
{
|
||||
if (isInitialize == false)
|
||||
@@ -130,6 +255,16 @@ namespace Gpm.Ui
|
||||
RemoveData(dataIndex, immediately);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 지정된 인덱스의 데이터 항목을 제거합니다.
|
||||
/// </summary>
|
||||
/// <param name="dataIndex">제거할 데이터의 인덱스입니다.</param>
|
||||
/// <param name="immediately">true이면 즉시 레이아웃을 업데이트합니다.</param>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// infiniteScroll.RemoveData(0, true);
|
||||
/// </code>
|
||||
/// </example>
|
||||
public void RemoveData(int dataIndex, bool immediately = false)
|
||||
{
|
||||
if (isInitialize == false)
|
||||
@@ -182,6 +317,15 @@ namespace Gpm.Ui
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 모든 데이터와 아이템 상태를 초기화하지만, 생성된 아이템 객체는 유지합니다.
|
||||
/// </summary>
|
||||
/// <param name="immediately">true이면 즉시 레이아웃을 업데이트합니다.</param>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// infiniteScroll.ClearData(true);
|
||||
/// </code>
|
||||
/// </example>
|
||||
public void ClearData(bool immediately = false)
|
||||
{
|
||||
if (isInitialize == false)
|
||||
@@ -221,6 +365,14 @@ namespace Gpm.Ui
|
||||
UpdateAllData(immediately);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 모든 데이터와 아이템 객체를 완전히 제거하고 스크롤을 초기 상태로 되돌립니다.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// infiniteScroll.Clear();
|
||||
/// </code>
|
||||
/// </example>
|
||||
public void Clear()
|
||||
{
|
||||
if (isInitialize == false)
|
||||
@@ -257,6 +409,19 @@ namespace Gpm.Ui
|
||||
ClearScrollContent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 기존 데이터 항목을 업데이트합니다.
|
||||
/// </summary>
|
||||
/// <param name="data">업데이트할 데이터입니다. 기존 데이터와 동일한 키를 가져야 합니다.</param>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// InfiniteScrollData existingData = infiniteScroll.GetData(0);
|
||||
/// if (existingData != null) {
|
||||
/// // 데이터 속성 수정
|
||||
/// infiniteScroll.UpdateData(existingData);
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public void UpdateData(InfiniteScrollData data)
|
||||
{
|
||||
if (isInitialize == false)
|
||||
@@ -273,6 +438,15 @@ namespace Gpm.Ui
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 모든 데이터를 업데이트하고 스크롤 뷰를 새로 고칩니다.
|
||||
/// </summary>
|
||||
/// <param name="immediately">true이면 즉시 레이아웃을 업데이트합니다.</param>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// infiniteScroll.UpdateAllData(true);
|
||||
/// </code>
|
||||
/// </example>
|
||||
public void UpdateAllData(bool immediately = true)
|
||||
{
|
||||
if (isInitialize == false)
|
||||
@@ -291,17 +465,38 @@ namespace Gpm.Ui
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 스크롤에 표시될 데이터를 필터링하는 조건을 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="onFilter">필터링을 위한 Predicate입니다. true를 반환하는 데이터만 표시됩니다.</param>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// infiniteScroll.SetFilter(data => {
|
||||
/// MyCustomData myData = data as MyCustomData;
|
||||
/// return myData.someValue > 10;
|
||||
/// });
|
||||
/// infiniteScroll.UpdateAllData(true);
|
||||
/// </code>
|
||||
/// </example>
|
||||
public void SetFilter(Predicate<InfiniteScrollData> onFilter)
|
||||
{
|
||||
this.onFilter = onFilter;
|
||||
needUpdateItemList = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 뷰포트의 크기를 가져옵니다.
|
||||
/// </summary>
|
||||
/// <returns>뷰포트의 주 축 크기입니다.</returns>
|
||||
public float GetViewportSize()
|
||||
{
|
||||
return layout.GetMainSize(viewport);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 콘텐츠의 전체 크기를 가져옵니다.
|
||||
/// </summary>
|
||||
/// <returns>콘텐츠의 주 축 크기입니다.</returns>
|
||||
public float GetContentSize()
|
||||
{
|
||||
UpdateContentSize();
|
||||
@@ -309,11 +504,18 @@ namespace Gpm.Ui
|
||||
return layout.GetMainSize(content);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 콘텐츠의 현재 위치를 가져옵니다.
|
||||
/// </summary>
|
||||
/// <returns>콘텐츠의 주 축 위치입니다.</returns>
|
||||
public float GetContentPosition()
|
||||
{
|
||||
return layout.GetAxisPosition(content);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 스크롤 뷰의 크기를 다시 계산하고 업데이트합니다.
|
||||
/// </summary>
|
||||
public void ResizeScrollView()
|
||||
{
|
||||
if (isInitialize == false)
|
||||
@@ -323,7 +525,12 @@ namespace Gpm.Ui
|
||||
|
||||
UpdateContentSize();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 지정된 아이템 인덱스의 위치를 가져옵니다.
|
||||
/// </summary>
|
||||
/// <param name="itemIndex">위치를 가져올 아이템의 인덱스입니다.</param>
|
||||
/// <returns>아이템의 위치입니다.</returns>
|
||||
public float GetItemPosition(int itemIndex)
|
||||
{
|
||||
float distance = GetItemDistance(itemIndex);
|
||||
@@ -331,6 +538,10 @@ namespace Gpm.Ui
|
||||
return -layout.GetAxisPostionFromOffset(distance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 스크롤 뷰를 수동으로 새로 고칩니다.
|
||||
/// 아이템 목록이나 레이아웃에 변경이 있을 때 호출합니다.
|
||||
/// </summary>
|
||||
public void RefreshScroll()
|
||||
{
|
||||
if (isInitialize == false)
|
||||
@@ -350,17 +561,26 @@ namespace Gpm.Ui
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 콘텐츠의 교차 축 크기를 가져옵니다.
|
||||
/// </summary>
|
||||
protected float GetCrossSize()
|
||||
{
|
||||
return layout.GetCrossSize(content.rect);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 모든 아이템의 총 크기에 따라 콘텐츠의 크기를 조절합니다.
|
||||
/// </summary>
|
||||
protected void ResizeContent()
|
||||
{
|
||||
cachedData.contentSize = GetItemTotalSize();
|
||||
content.sizeDelta = layout.GetAxisVector(-layout.padding, cachedData.contentSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 필요한 경우 레이아웃을 다시 빌드하여 콘텐츠 크기를 업데이트합니다.
|
||||
/// </summary>
|
||||
protected void UpdateContentSize()
|
||||
{
|
||||
if (needReBuildLayout == true)
|
||||
@@ -370,6 +590,9 @@ namespace Gpm.Ui
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 아이템을 업데이트해야 하는지 확인합니다.
|
||||
/// </summary>
|
||||
protected bool NeedUpdateItem()
|
||||
{
|
||||
CheckScrollData();
|
||||
@@ -384,6 +607,9 @@ namespace Gpm.Ui
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 지정된 위치가 현재 콘텐츠 위치보다 이전에 있는지 확인합니다. (화면 위/왼쪽)
|
||||
/// </summary>
|
||||
protected bool IsShowBeforePosition(float position, float contentPosition)
|
||||
{
|
||||
float viewPosition = position - contentPosition;
|
||||
@@ -395,6 +621,9 @@ namespace Gpm.Ui
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 지정된 위치가 현재 뷰포트 크기를 벗어나는지 확인합니다. (화면 아래/오른쪽)
|
||||
/// </summary>
|
||||
protected bool IsShowAfterPosition(float position, float contentPosition, float viewportSize)
|
||||
{
|
||||
float viewPosition = position - contentPosition;
|
||||
@@ -414,12 +643,15 @@ namespace Gpm.Ui
|
||||
}
|
||||
}
|
||||
|
||||
// Inspector에서 값이 변경될 때 호출됩니다.
|
||||
private void OnValidate()
|
||||
{
|
||||
layout.SetDefaults();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 스크롤 값 변경 시 발생하는 이벤트를 위한 클래스입니다.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class ChangeValueEvent : UnityEvent<int, int, bool, bool>
|
||||
{
|
||||
@@ -428,6 +660,9 @@ namespace Gpm.Ui
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 아이템 활성 상태 변경 시 발생하는 이벤트를 위한 클래스입니다.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class ItemActiveEvent : UnityEvent<int, bool>
|
||||
{
|
||||
@@ -436,6 +671,9 @@ namespace Gpm.Ui
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 스크롤 상태 변경(시작/끝 도달) 시 발생하는 이벤트를 위한 클래스입니다.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class StateChangeEvent : UnityEvent<bool>
|
||||
{
|
||||
|
||||
@@ -4,13 +4,33 @@ namespace Gpm.Ui
|
||||
using UnityEngine;
|
||||
using DataContext = InfiniteScroll.DataContext;
|
||||
|
||||
public class InfiniteScrollData
|
||||
{
|
||||
|
||||
}
|
||||
/// <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;
|
||||
@@ -18,6 +38,10 @@ namespace Gpm.Ui
|
||||
protected bool activeItem;
|
||||
protected InfiniteScroll scroll = null;
|
||||
|
||||
/// <summary>
|
||||
/// 이 아이템의 RectTransform 컴포넌트를 가져옵니다.
|
||||
/// 성능을 위해 처음 접근할 때 캐시됩니다.
|
||||
/// </summary>
|
||||
public RectTransform rectTransform
|
||||
{
|
||||
get
|
||||
@@ -31,6 +55,10 @@ namespace Gpm.Ui
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이 아이템에 바인딩된 데이터를 가져옵니다.
|
||||
/// 데이터는 InfiniteScrollData를 상속한 사용자 정의 클래스의 인스턴스입니다.
|
||||
/// </summary>
|
||||
protected InfiniteScrollData scrollData
|
||||
{
|
||||
get
|
||||
@@ -52,14 +80,23 @@ namespace Gpm.Ui
|
||||
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)
|
||||
@@ -72,6 +109,10 @@ namespace Gpm.Ui
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 전체 데이터 목록에서 이 아이템이 사용하는 데이터의 인덱스를 반환합니다.
|
||||
/// </summary>
|
||||
/// <returns>전체 데이터 인덱스. 데이터가 없으면 -1을 반환합니다.</returns>
|
||||
public int GetDataIndex()
|
||||
{
|
||||
if (dataContext != null)
|
||||
@@ -84,25 +125,86 @@ namespace Gpm.Ui
|
||||
}
|
||||
}
|
||||
|
||||
/// <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;
|
||||
@@ -118,6 +220,10 @@ namespace Gpm.Ui
|
||||
this.autoApplySize = autoApplySize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 아이템이 선택되었을 때 호출됩니다.
|
||||
/// 주로 버튼의 OnClick 이벤트에 연결하여 사용합니다.
|
||||
/// </summary>
|
||||
protected void OnSelect()
|
||||
{
|
||||
if (selectCallback != null)
|
||||
@@ -126,6 +232,12 @@ namespace Gpm.Ui
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 아이템의 활성 상태를 설정합니다.
|
||||
/// GameObject를 활성화/비활성화하고, 필요한 경우 부모 스크롤에 상태 변경을 알립니다.
|
||||
/// </summary>
|
||||
/// <param name="active">활성화 여부</param>
|
||||
/// <param name="notifyEvent">부모 InfiniteScroll에 상태 변경 이벤트를 보낼지 여부</param>
|
||||
public virtual void SetActive(bool active, bool notifyEvent = true)
|
||||
{
|
||||
activeItem = active;
|
||||
@@ -141,6 +253,12 @@ namespace Gpm.Ui
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 동적 아이템 크기 모드에서 아이템의 크기를 설정합니다.
|
||||
/// InfiniteScroll의 dynamicItemSize가 true일 때만 작동합니다.
|
||||
/// </summary>
|
||||
/// <param name="itemSize">새로운 아이템 크기 (주 축 기준)</param>
|
||||
/// <param name="notity">크기 변경을 즉시 스크롤에 알릴지 여부</param>
|
||||
public void SetSize(float itemSize, bool notity = true)
|
||||
{
|
||||
if (scrollData == null)
|
||||
@@ -168,6 +286,12 @@ namespace Gpm.Ui
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
@@ -185,6 +309,9 @@ namespace Gpm.Ui
|
||||
SetSize(itemSize, notity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 아이템의 데이터를 초기화하고 비활성화합니다. (내부용)
|
||||
/// </summary>
|
||||
internal void ClearData(bool notifyEvent = true)
|
||||
{
|
||||
SetActive(false, activeItem && notifyEvent);
|
||||
@@ -196,6 +323,9 @@ namespace Gpm.Ui
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 아이템의 모든 데이터와 콜백을 초기화합니다. (내부용)
|
||||
/// </summary>
|
||||
internal void Clear()
|
||||
{
|
||||
ClearData(false);
|
||||
@@ -203,6 +333,10 @@ namespace Gpm.Ui
|
||||
selectCallback = null;
|
||||
updateSizeCallback = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 새로운 데이터 컨텍스트로 아이템을 업데이트합니다. (내부용)
|
||||
/// </summary>
|
||||
internal void UpdateItem(DataContext context)
|
||||
{
|
||||
this.dataContext = context;
|
||||
@@ -215,17 +349,29 @@ namespace Gpm.Ui
|
||||
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 &&
|
||||
@@ -245,6 +391,10 @@ namespace Gpm.Ui
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// `autoApplySize` 기능이 현재 실행될 수 있는 상태인지 확인합니다.
|
||||
/// 아이템이 활성화되어 있고, 스크롤이 처리 중이 아닐 때 true를 반환합니다.
|
||||
/// </summary>
|
||||
protected bool CanAutoSizeCheck()
|
||||
{
|
||||
if (autoApplySize == false ||
|
||||
@@ -271,6 +421,12 @@ namespace Gpm.Ui
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unity의 UI 시스템에 의해 RectTransform의 크기가 변경될 때 호출됩니다.
|
||||
/// autoApplySize가 true일 때, 이 메서드는 변경된 크기를 InfiniteScroll에 자동으로 전달하여
|
||||
/// 스크롤 레이아웃을 다시 계산하도록 합니다.
|
||||
/// </summary>
|
||||
protected void OnRectTransformDimensionsChange()
|
||||
{
|
||||
if (CanAutoSizeCheck() == true)
|
||||
|
||||
@@ -1,14 +1,37 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace Gpm.Ui
|
||||
{
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
[ExecuteAlways]
|
||||
[RequireComponent(typeof(RectTransform))]
|
||||
/// <summary>
|
||||
/// 이 컴포넌트가 부착된 UI 요소의 크기가 변경될 때, 부모 UI 요소의 레이아웃을 강제로 다시 계산하도록 요청하는 역할을 합니다.
|
||||
/// 주로 중첩된 레이아웃 그룹(Layout Group)이나 ContentSizeFitter 사용 시, 자식의 크기 변경이 부모에게 제대로 전달되지 않아
|
||||
/// UI가 깨져 보일 때 사용하면 유용합니다.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <b>UI 계층 구조 예시:</b>
|
||||
/// <code>
|
||||
/// - VerticalPanel (VerticalLayoutGroup)
|
||||
/// - HorizontalPanel (HorizontalLayoutGroup, ContentSizeFitter, LayoutUpdater)
|
||||
/// - Item1
|
||||
/// - Item2
|
||||
/// </code>
|
||||
/// 위 구조에서 'HorizontalPanel'에 아이템이 추가되어 너비가 변경될 때, 'ContentSizeFitter'가 'HorizontalPanel'의 크기를 조절합니다.
|
||||
/// 이때 'HorizontalPanel'에 부착된 'LayoutUpdater'는 자신의 크기 변경을 감지하고,
|
||||
/// 부모인 'VerticalPanel'에게 "자식 크기가 바뀌었으니 너의 레이아웃을 다시 정렬해!"라고 알려주는 역할을 합니다.
|
||||
/// 결과적으로 'VerticalPanel'은 변경된 'HorizontalPanel'의 크기에 맞춰 레이아웃을 올바르게 갱신하게 됩니다.
|
||||
/// </example>
|
||||
[ExecuteAlways] // 에디터 모드에서도 스크립트가 실행되어, UI 변경을 실시간으로 확인할 수 있게 합니다.
|
||||
[RequireComponent(typeof(RectTransform))] // 이 컴포넌트는 RectTransform이 필수적으로 필요함을 명시합니다.
|
||||
public class LayoutUpdater : UIBehaviour
|
||||
{
|
||||
// 성능 최적화를 위해 부모 RectTransform 컴포넌트를 캐싱하는 변수입니다.
|
||||
private RectTransform m_Parent;
|
||||
|
||||
/// <summary>
|
||||
/// 이 컴포넌트가 부착된 GameObject의 부모 RectTransform에 대한 참조입니다.
|
||||
/// </summary>
|
||||
public RectTransform rectParent
|
||||
{
|
||||
get
|
||||
@@ -25,6 +48,10 @@ namespace Gpm.Ui
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이 컴포넌트가 부착된 RectTransform의 크기나 앵커 등이 변경될 때 호출됩니다.
|
||||
/// 부모의 레이아웃을 즉시 다시 계산하도록 강제합니다.
|
||||
/// </summary>
|
||||
protected override void OnRectTransformDimensionsChange()
|
||||
{
|
||||
base.OnRectTransformDimensionsChange();
|
||||
@@ -32,6 +59,10 @@ namespace Gpm.Ui
|
||||
SetDirty(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 부모 레이아웃을 다시 계산해야 함을 시스템에 알립니다.
|
||||
/// </summary>
|
||||
/// <param name="force">true일 경우, 즉시 레이아웃을 다시 계산합니다. false일 경우, 다음 프레임에 다시 계산하도록 예약합니다.</param>
|
||||
protected void SetDirty(bool force = false)
|
||||
{
|
||||
RectTransform parent = rectParent;
|
||||
@@ -42,10 +73,12 @@ namespace Gpm.Ui
|
||||
|
||||
if (force == true)
|
||||
{
|
||||
// 즉시 레이아웃을 강제로 다시 계산합니다.
|
||||
LayoutRebuilder.ForceRebuildLayoutImmediate(parent);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 다음 레이아웃 계산 주기에 맞춰 다시 계산하도록 표시만 해둡니다.
|
||||
LayoutRebuilder.MarkLayoutForRebuild(parent);
|
||||
}
|
||||
}
|
||||
@@ -56,6 +89,10 @@ namespace Gpm.Ui
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// (Unity 에디터 전용) 스크립트가 로드되거나 인스펙터에서 값이 변경될 때 호출됩니다.
|
||||
/// 에디터에서 실시간으로 레이아웃 변경을 확인할 수 있도록 업데이트를 요청합니다.
|
||||
/// </summary>
|
||||
protected override void OnValidate()
|
||||
{
|
||||
SetDirty(false);
|
||||
|
||||
@@ -1,49 +1,100 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Gpm.Ui
|
||||
{
|
||||
using System;
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
/// <summary>
|
||||
/// 스크롤 뷰의 스크롤 방향과 콘텐츠 정렬 방식을 정의합니다.
|
||||
/// </summary>
|
||||
public enum ScrollAxis
|
||||
{
|
||||
DEFAULT = 0,
|
||||
VERTICAL_TOP,
|
||||
VERTICAL_CENTER,
|
||||
VERTICAL_BOTTOM,
|
||||
HORIZONTAL_LEFT,
|
||||
HORIZONTAL_CENTER,
|
||||
HORIZONTAL_RIGHT,
|
||||
DEFAULT = 0, // ScrollRect의 설정을 따름
|
||||
VERTICAL_TOP, // 수직, 위쪽 정렬
|
||||
VERTICAL_CENTER, // 수직, 중앙 정렬
|
||||
VERTICAL_BOTTOM, // 수직, 아래쪽 정렬
|
||||
HORIZONTAL_LEFT, // 수평, 왼쪽 정렬
|
||||
HORIZONTAL_CENTER, // 수평, 중앙 정렬
|
||||
HORIZONTAL_RIGHT, // 수평, 오른쪽 정렬
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이 클래스는 InfiniteScroll과 같은 스크롤 컴포넌트의 레이아웃 규칙을 정의합니다.
|
||||
/// 스크롤 방향, 정렬, 간격, 그리드 설정 등 UI 항목을 어떻게 배치할지에 대한 모든 정보를 담고 있습니다.
|
||||
/// MonoBehaviour가 아니며, 다른 컴포넌트의 필드로 선언되어 인스펙터에서 값을 설정하는 방식으로 사용됩니다.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <b>사용 예시 (InfiniteScroll.cs 내에서):</b>
|
||||
/// <code>
|
||||
/// public class InfiniteScroll : MonoBehaviour
|
||||
/// {
|
||||
/// // ScrollLayout을 필드로 선언하여 인스펙터에서 레이아웃을 상세하게 설정할 수 있습니다.
|
||||
/// public ScrollLayout layout;
|
||||
///
|
||||
/// // ... 스크롤 로직 ...
|
||||
/// }
|
||||
/// </code>
|
||||
/// 위와 같이 `InfiniteScroll` 컴포넌트가 `ScrollLayout`을 멤버 변수로 가지고,
|
||||
/// 사용자는 Unity 인스펙터에서 `layout` 변수의 세부 항목(축, 간격, 그리드 등)을 설정하여 스크롤 뷰의 동작을 커스터마이징할 수 있습니다.
|
||||
/// </example>
|
||||
[Serializable]
|
||||
public class ScrollLayout
|
||||
{
|
||||
/// <summary>
|
||||
/// 그리드 레이아웃에서 각 열 또는 행의 크기 정보를 담는 클래스입니다.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class LayoutValue
|
||||
{
|
||||
public enum ValueType
|
||||
{
|
||||
DEFAULT,
|
||||
RATE,
|
||||
DEFAULT, // 기본값
|
||||
RATE, // 비율
|
||||
}
|
||||
|
||||
[HideInInspector]
|
||||
public ValueType valueType;
|
||||
public float value;
|
||||
public float value; // 크기 값 (비율)
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 스크롤의 주 축(수직 또는 수평)과 정렬 기준을 설정합니다.
|
||||
/// </summary>
|
||||
public ScrollAxis axis;
|
||||
|
||||
/// <summary>
|
||||
/// 콘텐츠 전체의 상하좌우 여백입니다.
|
||||
/// Vector2(가로 여백, 세로 여백)
|
||||
/// </summary>
|
||||
public Vector2 padding;
|
||||
|
||||
/// <summary>
|
||||
/// UI 항목들 사이의 간격입니다.
|
||||
/// Vector2(가로 간격, 세로 간격)
|
||||
/// </summary>
|
||||
public Vector2 space;
|
||||
|
||||
/// <summary>
|
||||
/// true일 경우, 수직 스크롤에서 위에서 아래로 항목을 채웁니다.
|
||||
/// </summary>
|
||||
public bool topToBotton = true;
|
||||
|
||||
/// <summary>
|
||||
/// true일 경우, 수평 스크롤에서 왼쪽에서 오른쪽으로 항목을 채웁니다.
|
||||
/// </summary>
|
||||
public bool leftToRight = true;
|
||||
|
||||
/// <summary>
|
||||
/// 그리드 레이아웃을 설정하는 데 사용됩니다.
|
||||
/// 리스트에 항목이 2개 이상이면 그리드로 동작하며, 각 항목의 `value`는 그리드 행/열의 상대적 크기 비율을 나타냅니다.
|
||||
/// 예: 수직 스크롤에서 values 리스트에 3개의 항목을 넣으면 3열 그리드가 됩니다.
|
||||
/// </summary>
|
||||
public List<LayoutValue> values = new List<LayoutValue>();
|
||||
|
||||
/// <summary>
|
||||
/// 주어진 축이 수직 스크롤인지 확인합니다.
|
||||
/// </summary>
|
||||
static public bool IsVertical(ScrollAxis axis)
|
||||
{
|
||||
return axis == ScrollAxis.VERTICAL_TOP ||
|
||||
@@ -51,6 +102,10 @@ namespace Gpm.Ui
|
||||
axis == ScrollAxis.VERTICAL_BOTTOM;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 설정된 `axis`에 맞게 `ScrollRect`의 스크롤 방향(vertical/horizontal)을 강제로 설정합니다.
|
||||
/// `axis`가 `DEFAULT`이면 `ScrollRect`의 기존 설정을 따릅니다.
|
||||
/// </summary>
|
||||
public void CheckAxis(ScrollRect scrollRect)
|
||||
{
|
||||
if (axis == ScrollAxis.DEFAULT)
|
||||
@@ -75,13 +130,18 @@ namespace Gpm.Ui
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 현재 레이아웃이 수직 스크롤인지 확인합니다.
|
||||
/// </summary>
|
||||
public bool IsVertical()
|
||||
{
|
||||
return IsVertical(axis);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 설정된 `axis`에 따라 콘텐츠 `RectTransform`의 피벗(pivot)을 계산하여 반환합니다.
|
||||
/// 이는 콘텐츠 정렬의 기준점이 됩니다.
|
||||
/// </summary>
|
||||
public Vector2 GetAxisPivot()
|
||||
{
|
||||
Vector2 pivot = Vector2.zero;
|
||||
@@ -113,6 +173,10 @@ namespace Gpm.Ui
|
||||
return pivot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 그리드 레이아웃 값(`values`)을 기본 상태(비율 1)로 초기화합니다.
|
||||
/// </summary>
|
||||
|
||||
public void SetDefaults()
|
||||
{
|
||||
foreach (LayoutValue layoutValue in values)
|
||||
@@ -125,6 +189,9 @@ namespace Gpm.Ui
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 콘텐츠 RectTransform의 현재 스크롤 위치를 주 축 기준으로 가져옵니다.
|
||||
/// </summary>
|
||||
public float GetAxisPosition(RectTransform content)
|
||||
{
|
||||
if (IsVertical() == true)
|
||||
@@ -137,6 +204,9 @@ namespace Gpm.Ui
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 주어진 오프셋 값을 스크롤 방향에 맞는 위치 값으로 변환합니다.
|
||||
/// </summary>
|
||||
public float GetAxisPostionFromOffset(float offset)
|
||||
{
|
||||
if (IsVertical() == true)
|
||||
@@ -149,11 +219,21 @@ namespace Gpm.Ui
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 스크롤 아이템의 주 축 방향 크기를 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="rectTransform">크기를 설정할 아이템의 RectTransform</param>
|
||||
/// <param name="itemIndex">아이템의 인덱스 (현재 사용되지 않음)</param>
|
||||
/// <param name="size">설정할 크기 값</param>
|
||||
public void FitItemSize(RectTransform rectTransform, int itemIndex, float size)
|
||||
{
|
||||
rectTransform.sizeDelta = GetAxisVector(size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 그리드 레이아웃에서 각 항목이 교차 축(cross axis) 방향으로 올바른 위치에 배치되도록 `RectTransform`의 앵커(anchor)를 조절합니다.
|
||||
/// 예를 들어, 수직 스크롤의 3열 그리드라면 각 항목이 1, 2, 3번째 열 중 어디에 위치할지를 계산합니다.
|
||||
/// </summary>
|
||||
public void FitItemInlinePosition(RectTransform rectTransform, int itemIndex, float crossSize)
|
||||
{
|
||||
float min = 0;
|
||||
@@ -220,11 +300,17 @@ namespace Gpm.Ui
|
||||
rectTransform.anchorMax = anchorMax;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 그리드 레이아웃의 행 또는 열의 개수를 반환합니다.
|
||||
/// </summary>
|
||||
public int GridCount()
|
||||
{
|
||||
return values.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 레이아웃이 그리드인지 확인합니다. (행/열 개수가 2개 이상)
|
||||
/// </summary>
|
||||
public bool IsGrid()
|
||||
{
|
||||
if (GridCount() > 1)
|
||||
@@ -235,11 +321,19 @@ namespace Gpm.Ui
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 스크롤 아이템의 피벗(pivot)을 계산하여 반환합니다.
|
||||
/// 아이템 정렬 방향(topToBottom, leftToRight)에 따라 결정됩니다.
|
||||
/// </summary>
|
||||
public Vector2 GetItemPivot()
|
||||
{
|
||||
return new Vector2(leftToRight ? 0.0f : 1.0f, topToBotton ? 1.0f : 0.0f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 스크롤 아이템의 앵커(anchor)를 계산하여 반환합니다.
|
||||
/// 주 축 방향으로 앵커를 피벗 위치에 고정시켜 아이템이 해당 라인을 따라 배치되도록 합니다.
|
||||
/// </summary>
|
||||
public Rect GetItemAnchor()
|
||||
{
|
||||
Vector2 pivot = GetItemPivot();
|
||||
@@ -259,11 +353,17 @@ namespace Gpm.Ui
|
||||
return anchor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 주어진 값을 주 축 방향의 값으로 하는 Vector2를 생성합니다. 교차 축의 값은 0이 됩니다.
|
||||
/// </summary>
|
||||
public Vector2 GetAxisVector(float value)
|
||||
{
|
||||
return GetAxisVector(Vector2.zero, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 주어진 Vector2에서 주 축에 해당하는 값을 새로운 값으로 설정하여 반환합니다.
|
||||
/// </summary>
|
||||
public Vector2 GetAxisVector(Vector2 vector, float value)
|
||||
{
|
||||
if (IsVertical() == true)
|
||||
@@ -278,40 +378,67 @@ namespace Gpm.Ui
|
||||
return vector;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 주어진 RectTransform에서 스크롤의 주 축(Main Axis)에 해당하는 크기를 반환합니다.
|
||||
/// (예: 수직 스크롤이면 높이, 수평 스크롤이면 너비)
|
||||
/// </summary>
|
||||
public float GetMainSize(RectTransform transform)
|
||||
{
|
||||
return GetMainSize(transform.rect);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 주어진 Rect에서 스크롤의 주 축에 해당하는 크기를 반환합니다.
|
||||
/// </summary>
|
||||
public float GetMainSize(Rect rect)
|
||||
{
|
||||
return IsVertical() == true ? rect.height : rect.width;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 주어진 Rect에서 스크롤의 교차 축(Cross Axis)에 해당하는 크기를 반환합니다.
|
||||
/// (예: 수직 스크롤이면 너비, 수평 스크롤이면 높이)
|
||||
/// </summary>
|
||||
public float GetCrossSize(Rect rect)
|
||||
{
|
||||
return IsVertical() == true ? rect.width : rect.height;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 주어진 Vector2에서 스크롤의 주 축에 해당하는 크기를 반환합니다.
|
||||
/// </summary>
|
||||
public float GetMainSize(Vector2 delta)
|
||||
{
|
||||
return IsVertical() == true ? delta.y : delta.x;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 주어진 Vector2에서 스크롤의 교차 축에 해당하는 크기를 반환합니다.
|
||||
/// </summary>
|
||||
public float GetCrossSize(Vector2 delta)
|
||||
{
|
||||
return IsVertical() == true ? delta.x : delta.y;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 주 축에 해당하는 Vector2의 인덱스를 반환합니다. (x=0, y=1)
|
||||
/// </summary>
|
||||
public int GetMainIndex()
|
||||
{
|
||||
return IsVertical() ? 1 : 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 교차 축에 해당하는 Vector2의 인덱스를 반환합니다. (x=0, y=1)
|
||||
/// </summary>
|
||||
public int GetCrossIndex()
|
||||
{
|
||||
return IsVertical() ? 0 : 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 주 축 방향의 여백(Padding)입니다.
|
||||
/// </summary>
|
||||
public float MainPadding
|
||||
{
|
||||
get
|
||||
@@ -325,6 +452,9 @@ namespace Gpm.Ui
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 교차 축 방향의 여백(Padding)입니다.
|
||||
/// </summary>
|
||||
public float CrossPadding
|
||||
{
|
||||
get
|
||||
@@ -338,6 +468,9 @@ namespace Gpm.Ui
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 주 축 방향의 항목 간 간격(Space)입니다.
|
||||
/// </summary>
|
||||
public float MainSpace
|
||||
{
|
||||
get
|
||||
@@ -351,6 +484,9 @@ namespace Gpm.Ui
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 교차 축 방향의 항목 간 간격(Space)입니다.
|
||||
/// </summary>
|
||||
public float CrossSpace
|
||||
{
|
||||
get
|
||||
|
||||
@@ -1,26 +1,78 @@
|
||||
namespace Gpm.Ui
|
||||
{
|
||||
using UnityEngine;
|
||||
|
||||
using MoveToType = InfiniteScroll.MoveToType;
|
||||
|
||||
/// <summary>
|
||||
/// 스크롤 위치를 제어하기 위한 인터페이스입니다.
|
||||
/// InfiniteScroll과 같이 스크롤 기능이 있는 클래스에서 이 인터페이스를 구현합니다.
|
||||
/// </summary>
|
||||
public interface IMoveScroll
|
||||
{
|
||||
/// <summary>
|
||||
/// 현재 스크롤 위치를 반환합니다.
|
||||
/// </summary>
|
||||
Vector2 GetScrollPosition();
|
||||
|
||||
/// <summary>
|
||||
/// 스크롤 위치를 설정합니다.
|
||||
/// </summary>
|
||||
void SetScrollPosition(Vector2 position);
|
||||
|
||||
/// <summary>
|
||||
/// 특정 아이템 인덱스로 이동할 위치를 계산하여 반환합니다.
|
||||
/// </summary>
|
||||
/// <param name="itemIndex">이동할 아이템의 인덱스</param>
|
||||
/// <param name="moveToType">아이템을 정렬할 위치(상단, 중앙, 하단)</param>
|
||||
Vector2 GetMovePosition(int itemIndex, MoveToType moveToType);
|
||||
|
||||
/// <summary>
|
||||
/// 전체 스크롤 범위의 특정 비율로 이동할 위치를 계산하여 반환합니다.
|
||||
/// </summary>
|
||||
/// <param name="scrollRate">이동할 비율 (0.0 ~ 1.0)</param>
|
||||
Vector2 GetMovePosition(float scrollRate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 특정 위치로 스크롤을 애니메이션과 함께 부드럽게 이동시키는 컴포넌트입니다.
|
||||
/// InfiniteScroll과 같이 IMoveScroll 인터페이스를 구현한 컴포넌트가 있는 게임 오브젝트에 추가하여 사용합니다.
|
||||
///
|
||||
/// --- 샘플 코드 ---
|
||||
/// <code>
|
||||
/// // 이동을 원하는 스크롤이 있는 GameObject를 찾습니다.
|
||||
/// GameObject scrollObject = GameObject.Find("InfiniteScroll");
|
||||
///
|
||||
/// // ScrollMoveTo 컴포넌트가 없으면 추가하고, 있으면 가져옵니다.
|
||||
/// ScrollMoveTo moveTo = scrollObject.GetComponent<ScrollMoveTo>();
|
||||
/// if (moveTo == null)
|
||||
/// {
|
||||
/// moveTo = scrollObject.AddComponent<ScrollMoveTo>();
|
||||
/// }
|
||||
///
|
||||
/// // 예제 1: 10번 아이템의 중앙으로 0.5초 동안 이동
|
||||
/// moveTo.Set(10, MoveToType.MOVE_TO_CENTER, 0.5f);
|
||||
/// moveTo.Play();
|
||||
///
|
||||
/// // 예제 2: 스크롤 전체의 80% 위치로 0.3초 동안 이동
|
||||
/// moveTo.Set(0.8f, 0.3f);
|
||||
/// moveTo.Play();
|
||||
/// </code>
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public class ScrollMoveTo : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// 스크롤 이동 목표를 지정하는 방식입니다.
|
||||
/// </summary>
|
||||
public enum ScrollType
|
||||
{
|
||||
/// <summary>
|
||||
/// 아이템의 인덱스를 기준으로 이동합니다.
|
||||
/// </summary>
|
||||
INDEX,
|
||||
/// <summary>
|
||||
/// 전체 스크롤의 비율(0.0~1.0)을 기준으로 이동합니다.
|
||||
/// </summary>
|
||||
RATE
|
||||
}
|
||||
|
||||
@@ -34,23 +86,59 @@ namespace Gpm.Ui
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 스크롤 이동 목표를 설정합니다. (인덱스 또는 비율)
|
||||
/// </summary>
|
||||
[Tooltip("스크롤 이동 목표를 설정합니다. (인덱스 또는 비율)")]
|
||||
public ScrollType scrollType;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 이동할 대상 아이템의 인덱스입니다. (scrollType이 INDEX일 때 사용)
|
||||
/// </summary>
|
||||
[Tooltip("이동할 대상 아이템의 인덱스입니다. (scrollType이 INDEX일 때 사용)")]
|
||||
public int itemIndex;
|
||||
|
||||
/// <summary>
|
||||
/// 아이템을 화면의 어느 위치에 맞출지 결정합니다. (scrollType이 INDEX일 때 사용)
|
||||
/// </summary>
|
||||
[Tooltip("아이템을 화면의 어느 위치에 맞출지 결정합니다. (scrollType이 INDEX일 때 사용)")]
|
||||
public MoveToType moveToType;
|
||||
|
||||
/// <summary>
|
||||
/// 이동할 스크롤의 비율입니다. (0.0 = 시작, 1.0 = 끝) (scrollType이 RATE일 때 사용)
|
||||
/// </summary>
|
||||
[Tooltip("이동할 스크롤의 비율입니다. (0.0 = 시작, 1.0 = 끝) (scrollType이 RATE일 때 사용)")]
|
||||
[Range(0f, 1f)]
|
||||
public float scrollRate = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 목표 위치까지 이동하는 데 걸리는 시간(초)입니다.
|
||||
/// </summary>
|
||||
[Tooltip("목표 위치까지 이동하는 데 걸리는 시간(초)입니다.")]
|
||||
public float time = 0.3f;
|
||||
|
||||
/// <summary>
|
||||
/// 이동 애니메이션의 속도 변화를 제어하는 커브입니다.
|
||||
/// </summary>
|
||||
[Tooltip("이동 애니메이션의 속도 변화를 제어하는 커브입니다.")]
|
||||
public AnimationCurve curve = AnimationCurve.EaseInOut(0, 0, 1, 1);
|
||||
|
||||
/// <summary>
|
||||
/// 이동이 완료된 후 이 컴포넌트를 자동으로 파괴할지 여부입니다.
|
||||
/// </summary>
|
||||
[Tooltip("이동이 완료된 후 이 컴포넌트를 자동으로 파괴할지 여부입니다.")]
|
||||
public bool autoDestory = false;
|
||||
|
||||
private float rate = 0;
|
||||
|
||||
private Vector2 start;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 특정 아이템 인덱스로 이동하도록 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="itemIndex">이동할 아이템 인덱스</param>
|
||||
/// <param name="moveToType">아이템 정렬 위치</param>
|
||||
/// <param name="time">이동 시간(초)</param>
|
||||
public void Set(int itemIndex, MoveToType moveToType, float time)
|
||||
{
|
||||
this.scrollType = ScrollType.INDEX;
|
||||
@@ -60,6 +148,11 @@ namespace Gpm.Ui
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 특정 비율 위치로 이동하도록 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="scrollRate">이동할 비율 (0.0 ~ 1.0)</param>
|
||||
/// <param name="time">이동 시간(초)</param>
|
||||
public void Set(float scrollRate, float time)
|
||||
{
|
||||
this.scrollType = ScrollType.RATE;
|
||||
@@ -68,6 +161,9 @@ namespace Gpm.Ui
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 설정된 값에 따라 스크롤 이동 애니메이션을 시작합니다.
|
||||
/// </summary>
|
||||
public void Play()
|
||||
{
|
||||
rate = 0;
|
||||
|
||||
Reference in New Issue
Block a user