Files
EnglewoodLAB/Assets/Scripts/NHN/InfiniteScroll.Layout.cs

1055 lines
37 KiB
C#

namespace Gpm.Ui
{
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();
offset = 0;
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;
contentSize = 0;
viewportSize = 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 firstItemIndex = 0; // 화면에 보이는 첫 번째 아이템의 인덱스
private int lastItemIndex = 0; // 화면에 보이는 마지막 아이템의 인덱스
// 레이아웃 업데이트 상태를 관리하는 플래그
private bool isUpdateArea = false; // 스크롤 영역(위치, 크기)이 변경되었는지 여부
private bool isRebuildLayout = false; // 레이아웃을 처음부터 다시 빌드했는지 여부
protected bool needReBuildLayout = true; // 레이아웃을 다시 빌드해야 하는지 여부 (데이터 변경, 설정 변경 시 true)
private bool isStartLine = true; // 스크롤이 시작 지점에 있는지 여부
private bool isEndLine = true; // 스크롤이 끝 지점에 있는지 여부
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;
CheckScrollAxis();
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;
padding = 0;
}
#pragma warning restore 618
return layout.MainPadding;
}
/// <summary>
/// 스크롤 주 축 방향의 아이템 간 간격을 가져옵니다.
/// </summary>
/// <returns>주 축 간격</returns>
public float GetMainSpace()
{
#pragma warning disable 618
// 구버전 호환성 코드
if (space > 0)
{
layout.MainSpace = space;
space = 0;
}
#pragma warning restore 618
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; // 보이는 영역이 변경되었으므로 업데이트 필요
}
if (cachedData.contentSize != contentSize)
{
cachedData.contentSize = contentSize;
isUpdateArea = true;
}
if (cachedData.viewportSize != viewportSize)
{
cachedData.viewportSize = viewportSize;
isUpdateArea = true;
}
// 간격(space) 변경 감지
if (cachedData.space != layout.space)
{
cachedData.space = layout.space;
needReBuildLayout = true; // 레이아웃 구조가 변경되었으므로 재계산 필요
}
// 그리드 수 또는 그리드 설정값 변경 감지
if (cachedData.padding != layout.padding)
{
cachedData.padding = layout.padding;
needReBuildLayout = true;
}
if (cachedData.gridCount != layout.values.Count)
{
cachedData.gridCount = layout.values.Count;
needReBuildLayout = true;
}
if (layout.values.Count > 0)
{
for (int i = 0; i < layout.values.Count; i++)
{
if (cachedData.values.Count < i + 1)
{
cachedData.values.Add(new ScrollLayout.LayoutValue());
needReBuildLayout = true;
}
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;
cachedData.values[i].value = layout.values[i].value;
needReBuildLayout = true;
}
}
}
bool isUpdateLayout = false;
// 스크롤 축, 방향 등 레이아웃의 근본적인 구조 변경 감지
if (cachedData.axis != layout.axis)
{
cachedData.axis = layout.axis;
bool IsVertical = layout.IsVertical();
if (cachedData.IsVertical != IsVertical)
{
cachedData.IsVertical = IsVertical;
// ScrollRect의 스크롤 방향을 동기화
if (layout.IsVertical() == true)
{
scrollRect.vertical = true;
}
else
{
scrollRect.horizontal = true;
}
}
isUpdateLayout = true;
}
if (cachedData.topToBotton != layout.topToBotton)
{
cachedData.topToBotton = layout.topToBotton;
isUpdateLayout = true;
}
if (cachedData.leftToRight != layout.leftToRight)
{
cachedData.leftToRight = layout.leftToRight;
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);
cachedData.itemPivot = layout.GetItemPivot();
cachedData.contentPivot = layout.GetAxisPivot();
// 변경된 축 정보를 실제 RectTransform에 적용
UpdateAxis();
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;
}
AddItem(context);
}
// 계산된 레이아웃 크기에 맞게 콘텐츠(Content)의 크기를 조절
ResizeContent();
isRebuildLayout = true; // 재빌드가 완료되었음을 표시
}
/// <summary>
/// 현재 스크롤 위치를 기준으로 화면에 보여야 할 라인(line)의 범위를 계산합니다.
/// `showLineIndex`와 `showLineCount`를 설정합니다.
/// </summary>
protected void CheckShowLine()
{
bool showLine = false;
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; // 화면에 보이기 시작하는 첫 라인
showLine = true;
showLineCount++;
}
}
else
{
// 뷰포트 하단을 벗어나는 라인이 나오면 중단
if (IsShowAfterPosition(GetLineDistance(line.offset, lineIndex), cachedData.contentPosition, cachedData.viewportSize) == true)
{
break;
}
showLineCount++;
}
}
// 스크롤바 크기 조절
ResizeScrollView();
}
/// <summary>
/// 화면에 보이는 아이템들을 업데이트하고, 보이지 않는 아이템은 비활성화(재활용)합니다.
/// 스크롤이 움직일 때 주로 호출됩니다.
/// </summary>
/// <param name="forceUpdateData">true이면 보이는 모든 아이템의 데이터를 강제로 새로고침합니다.</param>
protected void UpdateShowItem(bool forceUpdateData = false)
{
// 이미 처리 중이면 중복 실행 방지
if (forceUpdateData == false &&
processing == true)
{
return;
}
// 아이템 업데이트가 필요 없으면 종료
if (NeedUpdateItem() == false)
{
return;
}
processing = true;
// 레이아웃 재빌드가 필요하면 실행
if (forceUpdateData == true ||
needReBuildLayout == true)
{
BuildLayout();
needReBuildLayout = false;
}
// 스크롤 영역이 변경되었거나 레이아웃이 재빌드되었다면, 보이는 라인을 다시 계산
if (forceUpdateData == true ||
isUpdateArea == true ||
isRebuildLayout == true)
{
CheckShowLine();
isUpdateArea = false;
isRebuildLayout = false;
}
// 보이는 아이템의 첫 인덱스와 마지막 인덱스를 계산
int prevFirstItemIndex = firstItemIndex;
firstItemIndex = GetLineFirstItemIndex(showLineIndex);
int prevLastItemIndex = lastItemIndex;
if (showLineCount > 0)
{
int lastLineIndex = showLineIndex + showLineCount - 1;
lastItemIndex = GetLineLastItemIndex(lastLineIndex);
}
else
{
lastItemIndex = firstItemIndex;
}
// 보이는 아이템 범위가 변경되었는지 확인
if (prevFirstItemIndex < firstItemIndex ||
prevLastItemIndex > lastItemIndex)
{
changeValue = true;
}
// ** 아이템 비활성화 (재활용) **
// 현재 활성화된 아이템 객체들 중, 새로 계산된 보이는 범위(first ~ last)를 벗어난 것들을 비활성화
for (int index = 0; index < itemObjectList.Count; ++index)
{
int linkedIndex = itemObjectList[index].GetItemIndex();
if (linkedIndex < firstItemIndex ||
linkedIndex > lastItemIndex)
{
if (itemObjectList[index].IsActive() == true)
{
itemObjectList[index].SetActive(false);
}
}
}
// ** 아이템 활성화 및 업데이트 **
// 화면에 보여야 할 라인들을 순회
for (int lineIndex = showLineIndex; lineIndex < showLineIndex + showLineCount; lineIndex++)
{
if (lineIndex >= GetLineCount())
{
break;
}
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)
{
needUpdateItemData = true;
changeValue = true;
}
// 데이터 업데이트가 필요하거나, 강제 업데이트 모드일 때
if (needUpdateItemData == true || forceUpdateData == true)
{
item.UpdateItem(context);
}
// 아이템이 비활성 상태였다면 활성화
if (item.IsActive() == false)
{
item.SetActive(true, true);
}
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();
layout.FitItemSize(itemTransform, context.itemIndex, size);
item.needUpdateItemSize = false;
}
// 최종 위치 계산 및 적용
FitItemPosition(itemTransform, context.itemIndex);
}
}
if (changeValue == true)
{
onChangeValue.Invoke(firstItemIndex, lastItemIndex, isStartLine, isEndLine);
changeValue = false;
}
ResizeContent();
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();
if (layout.IsVertical() == true)
{
return contentSize * (cachedData.contentPivot.y - cachedData.itemPivot.y);
}
else
{
return contentSize * (cachedData.contentPivot.x - cachedData.itemPivot.x);
}
}
/// <summary>
/// 레이아웃 계산에 사용되는 변수들을 초기화합니다.
/// </summary>
protected void ClearLayout()
{
layoutSize = 0;
lineCount = 0;
}
/// <summary>
/// 데이터를 레이아웃에 추가합니다. 그리드 설정에 따라 자동으로 줄바꿈을 처리합니다.
/// </summary>
/// <param name="context">추가할 아이템의 데이터 컨텍스트</param>
protected void AddItem(DataContext context)
{
bool newLine = false;
LineLayout currentLine = null;
int lineIndex = lineCount - 1;
if (lineCount == 0)
{
// 첫 아이템은 항상 새 라인에서 시작
newLine = true;
}
else if (lineIndex < lineCount)
{
currentLine = lineLayout[lineIndex];
// 현재 라인이 그리드 수만큼 꽉 찼으면 새 라인으로
if (currentLine.GetCount() >= layout.GridCount())
{
newLine = true;
}
}
else
{
newLine = true;
}
if (newLine == true)
{
lineIndex = lineCount;
// 기존에 생성된 LineLayout 객체가 있으면 재사용, 없으면 새로 생성
if (lineIndex < lineLayout.Count)
{
currentLine = lineLayout[lineIndex];
}
else
{
currentLine = new LineLayout(lineIndex);
lineLayout.Add(currentLine);
}
currentLine.Clear();
currentLine.offset = layoutSize; // 새 라인의 시작 위치는 현재까지의 총 레이아웃 크기
newLine = false;
lineCount++;
}
else
{
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; // 라인 내에서의 인덱스
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);
return lineLayout[lineIndex].offset;
}
/// <summary>
/// 아이템 인덱스로 해당 아이템이 속한 라인의 인덱스를 계산합니다.
/// </summary>
protected int GetLineIndex(int itemIndex)
{
if (layout.IsGrid() == true)
{
return itemIndex / layout.GridCount();
}
return itemIndex; // 그리드가 아니면 아이템 인덱스 = 라인 인덱스
}
/// <summary>
/// 해당 아이템이 라인의 마지막 아이템인지 확인합니다.
/// </summary>
protected bool IsLast(int itemIndex)
{
if (layout.IsGrid() == true)
{
if ((itemIndex + 1) % layout.GridCount() == 0)
{
return true;
}
return false;
}
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();
if (lineIndex > 0)
{
distance += lineIndex * GetMainSpace();
}
return distance;
}
/// <summary>
/// 특정 라인에 포함된 첫 번째 아이템의 인덱스를 가져옵니다.
/// </summary>
protected int GetLineFirstItemIndex(int lineIndex)
{
int lineCount = GetLineCount();
if (lineCount == 0)
{
return 0;
}
if (lineIndex >= lineCount)
{
lineIndex = lineCount - 1;
}
int firstItemIndex = lineIndex;
if (layout.IsGrid() == true)
{
firstItemIndex = firstItemIndex * layout.GridCount();
}
if (firstItemIndex < 0)
{
firstItemIndex = 0;
}
return firstItemIndex;
}
/// <summary>
/// 특정 라인에 포함된 마지막 아이템의 인덱스를 가져옵니다.
/// </summary>
protected int GetLineLastItemIndex(int lineIndex)
{
int lineCount = GetLineCount();
if (lineCount == 0)
{
return 0;
}
if (lineIndex >= lineCount)
{
lineIndex = lineCount - 1;
}
int lastItemIndex = 0;
if (layout.IsGrid() == true)
{
if (lineIndex > 0)
{
lastItemIndex = lineIndex * layout.GridCount();
}
lastItemIndex += lineLayout[lineIndex].GetCount() - 1;
}
else
{
lastItemIndex = lineIndex;
}
return lastItemIndex;
}
/// <summary>
/// 특정 라인의 크기(주 축 기준)를 가져옵니다.
/// </summary>
protected float GetLineSize(int lineIndex)
{
int lineCount = GetLineCount();
if (lineCount == 0)
{
return 0;
}
if (lineIndex >= lineCount)
{
lineIndex = lineCount - 1;
}
return lineLayout[lineIndex].size;
}
}
}