namespace Gpm.Ui { using System.Collections.Generic; using UnityEngine; /// /// 이 partial 클래스는 InfiniteScroll의 레이아웃 관련 로직을 담당합니다. /// 아이템의 배치, 줄 계산, 스크롤 영역 업데이트 등 UI 레이아웃에 필요한 모든 계산을 처리합니다. /// InfiniteScroll.cs의 일부로, 코드 관리를 용이하게 하기 위해 별도 파일로 분리되었습니다. /// public partial class InfiniteScroll { [Header("Layout", order = 3)] [Tooltip("스크롤 뷰의 레이아웃 설정을 담고 있는 객체입니다. 인스펙터에서 스크롤 방향, 간격, 그리드 등을 설정할 수 있습니다.")] public ScrollLayout layout = new ScrollLayout(); /// /// 스크롤 뷰 내에서 한 줄(Row 또는 Column)의 레이아웃 정보를 관리하는 클래스입니다. /// 예: 수직 스크롤에서는 하나의 가로 줄(Row)을 의미합니다. /// public class LineLayout { /// /// LineLayout 생성자입니다. /// /// 해당 줄의 인덱스입니다. public LineLayout(int index) { this.index = index; } /// /// 줄의 모든 데이터를 초기화합니다. /// public void Clear() { dataList.Clear(); offset = 0; size = 0; } /// /// 현재 줄에 데이터(아이템)를 추가하고, 줄의 전체 크기를 갱신합니다. /// /// 추가할 아이템의 데이터 컨텍스트입니다. /// 아이템 추가 후 갱신된 줄의 끝 위치를 반환합니다. 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; } /// /// 이 줄의 인덱스입니다. /// internal int index = 0; /// /// 이 줄에 포함된 아이템들의 데이터 리스트입니다. /// internal List dataList = new List(); /// /// 스크롤의 주 축(main axis)을 기준으로 이 줄의 시작 위치(offset)입니다. /// internal float offset = 0; /// /// 스크롤의 주 축을 기준으로 이 줄의 크기입니다. /// 이 줄에 포함된 아이템 중 가장 큰 아이템의 크기와 같습니다. /// internal float size = 0; /// /// 이 줄에 포함된 아이템의 개수를 반환합니다. /// /// 아이템 개수 public int GetCount() { return dataList.Count; } } /// /// 스크롤 뷰의 레이아웃 관련 상태를 캐싱하는 클래스입니다. /// 매 프레임마다 모든 값을 다시 계산하는 것을 피하고, 변경이 있을 때만 레이아웃을 업데이트하여 성능을 최적화합니다. /// public class CachedScrollData { /// /// 스크롤 콘텐츠의 현재 위치입니다. /// public float contentPosition = 0; /// /// 스크롤 콘텐츠의 전체 크기입니다. /// public float contentSize = 0; /// /// 스크롤 뷰포트(보여지는 영역)의 크기입니다. /// public float viewportSize = 0; /// /// 캐시된 콘텐츠 여백(padding)입니다. /// public Vector2 padding; /// /// 캐시된 아이템 간 간격(space)입니다. /// public Vector2 space; /// /// 캐시된 그리드의 수입니다. (예: 3열 그리드이면 3) /// public int gridCount = 0; /// /// 캐시된 스크롤 축 정보입니다. /// public ScrollAxis axis; /// /// 캐시된 수직 스크롤 방향입니다. (true: 위에서 아래로) /// public bool topToBotton = true; /// /// 캐시된 수평 스크롤 방향입니다. (true: 왼쪽에서 오른쪽으로) /// public bool leftToRight = true; /// /// 캐시된 그리드 레이아웃 값 리스트입니다. /// public List values = new List(); /// /// 현재 스크롤이 수직인지 여부입니다. /// public bool IsVertical = false; /// /// 캐시된 아이템의 앵커 최소값입니다. /// public Vector2 anchorMin = Vector2.zero; /// /// 캐시된 아이템의 앵커 최대값입니다. /// public Vector2 anchorMax = Vector2.zero; /// /// 캐시된 아이템의 피벗입니다. /// public Vector2 itemPivot = Vector2.zero; /// /// 캐시된 콘텐츠의 피벗입니다. /// public Vector2 contentPivot = Vector2.zero; /// /// 캐시된 모든 데이터를 초기화합니다. /// public void Clear() { contentPosition = 0; contentSize = 0; viewportSize = 0; } } // 레이아웃 계산에 사용되는 내부 변수들 internal List lineLayout = new List(); // 전체 라인들의 레이아웃 정보를 담는 리스트 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; /// /// 스크롤 축(방향)을 설정합니다. /// /// 설정할 스크롤 축 public void SetScrollAxis(ScrollAxis axis) { layout.axis = axis; CheckScrollAxis(); CheckScrollData(); } /// /// 현재 설정된 스크롤 축을 가져옵니다. /// /// 현재 스크롤 축 public ScrollAxis GetScrollAxis() { return layout.axis; } /// /// 콘텐츠의 여백(padding)을 설정합니다. /// /// x: 가로 여백, y: 세로 여백 public void SetPadding(Vector2 padding) { layout.padding = padding; } /// /// 아이템 간의 간격(space)을 설정합니다. /// /// x: 가로 간격, y: 세로 간격 public void SetSpace(Vector2 space) { layout.space = space; } /// /// 현재 설정된 여백(padding)을 가져옵니다. /// /// 현재 여백 public Vector2 GetPadding() { return layout.padding; } /// /// 현재 설정된 아이템 간 간격(space)을 가져옵니다. /// /// 현재 간격 public Vector2 GetSpace() { return layout.space; } /// /// 스크롤 주 축 방향의 여백을 가져옵니다. /// /// 주 축 여백 public float GetMainPadding() { #pragma warning disable 618 // 구버전 호환성 코드 if (padding > 0) { layout.MainPadding = padding; padding = 0; } #pragma warning restore 618 return layout.MainPadding; } /// /// 스크롤 주 축 방향의 아이템 간 간격을 가져옵니다. /// /// 주 축 간격 public float GetMainSpace() { #pragma warning disable 618 // 구버전 호환성 코드 if (space > 0) { layout.MainSpace = space; space = 0; } #pragma warning restore 618 return layout.MainSpace; } /// /// 스크롤 교차 축 방향의 여백을 가져옵니다. /// /// 교차 축 여백 public float GetCrossPadding() { return layout.CrossPadding; } /// /// 스크롤 교차 축 방향의 아이템 간 간격을 가져옵니다. /// /// 교차 축 간격 public float GetCrossSpace() { return layout.CrossSpace; } /// /// 레이아웃 관련 데이터의 변경 사항을 확인하고, 업데이트가 필요한 경우 관련 플래그를 설정합니다. /// 이 메서드는 매 프레임 호출되어 스크롤 위치, 뷰포트 크기, 레이아웃 설정 등의 변경을 감지합니다. /// 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; } } /// /// 캐시된 앵커 및 피벗 값을 콘텐츠와 아이템들의 RectTransform에 적용합니다. /// 스크롤 방향이 변경될 때 호출됩니다. /// 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; } /// /// ScrollRect의 스크롤 가능 여부(vertical/horizontal)를 현재 레이아웃 축 설정에 맞게 조정합니다. /// public void CheckScrollAxis() { layout.CheckAxis(scrollRect); } /// /// 모든 아이템을 포함하는 데 필요한 총 거리를 계산합니다. /// protected float GetItemTotalSize() { return GetTotalDistance(); } /// /// 특정 아이템까지의 거리를 계산합니다. /// /// 아이템의 인덱스 /// 스크롤 시작점부터 해당 아이템이 속한 라인까지의 거리 protected float GetItemDistance(int itemIndex) { int lineIndex = GetLineIndex(itemIndex); return GetLineDistance(GetLineOffset(lineIndex), lineIndex); } /// /// 전체 데이터 리스트를 기반으로 레이아웃을 처음부터 다시 계산하고 빌드합니다. /// 데이터가 변경되거나 레이아웃 설정이 변경될 때 호출됩니다. /// 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; // 재빌드가 완료되었음을 표시 } /// /// 현재 스크롤 위치를 기준으로 화면에 보여야 할 라인(line)의 범위를 계산합니다. /// `showLineIndex`와 `showLineCount`를 설정합니다. /// 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(); } /// /// 화면에 보이는 아이템들을 업데이트하고, 보이지 않는 아이템은 비활성화(재활용)합니다. /// 스크롤이 움직일 때 주로 호출됩니다. /// /// true이면 보이는 모든 아이템의 데이터를 강제로 새로고침합니다. 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; } /// /// 아이템의 RectTransform 위치를 계산하고 적용합니다. /// /// 위치를 설정할 아이템의 RectTransform /// 아이템의 인덱스 protected void FitItemPosition(RectTransform rectTransform, int itemIndex) { // 교차 축 위치 설정 (예: 수직 스크롤에서 좌/우 위치) layout.FitItemInlinePosition(rectTransform, itemIndex, GetCrossSize()); // 주 축 위치 설정 (예: 수직 스크롤에서 상/하 위치) float itemPosition = GetItemPosition(itemIndex); rectTransform.anchoredPosition = layout.GetAxisVector(Vector2.zero, itemPosition); } /// /// 오프셋 값을 실제 anchoredPosition 값으로 변환합니다. /// protected float ItemPostionFromOffset(float offset) { float postion = layout.GetAxisPostionFromOffset(offset); // 콘텐츠와 아이템의 피벗 차이를 보정 postion += GetPivotPostion(); return postion; } /// /// 콘텐츠와 아이템의 피벗 차이로 인해 발생하는 위치 오차를 계산합니다. /// 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); } } /// /// 레이아웃 계산에 사용되는 변수들을 초기화합니다. /// protected void ClearLayout() { layoutSize = 0; lineCount = 0; } /// /// 데이터를 레이아웃에 추가합니다. 그리드 설정에 따라 자동으로 줄바꿈을 처리합니다. /// /// 추가할 아이템의 데이터 컨텍스트 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); } /// /// 아이템 인덱스로 해당 아이템의 데이터 컨텍스트를 가져옵니다. /// 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]; } } /// /// 라인 인덱스로 해당 라인의 LineLayout 객체를 가져옵니다. /// protected LineLayout GetLine(int lineIndex) { return lineLayout[lineIndex]; } /// /// 라인 인덱스로 해당 라인의 시작 위치(offset)를 가져옵니다. /// protected float GetLineOffset(int lineIndex) { return lineLayout[lineIndex].offset; } /// /// 아이템 인덱스로 해당 아이템이 속한 라인의 시작 위치(offset)를 가져옵니다. /// protected float GetItemOffset(int itemIndex) { int lineIndex = GetLineIndex(itemIndex); return lineLayout[lineIndex].offset; } /// /// 아이템 인덱스로 해당 아이템이 속한 라인의 인덱스를 계산합니다. /// protected int GetLineIndex(int itemIndex) { if (layout.IsGrid() == true) { return itemIndex / layout.GridCount(); } return itemIndex; // 그리드가 아니면 아이템 인덱스 = 라인 인덱스 } /// /// 해당 아이템이 라인의 마지막 아이템인지 확인합니다. /// protected bool IsLast(int itemIndex) { if (layout.IsGrid() == true) { if ((itemIndex + 1) % layout.GridCount() == 0) { return true; } return false; } return true; // 그리드가 아니면 모든 아이템이 마지막 아이템 } /// /// 현재 레이아웃의 총 라인 수를 가져옵니다. /// protected int GetLineCount() { return lineCount; } /// /// 모든 아이템, 여백, 간격을 포함한 콘텐츠의 총 거리를 계산합니다. /// protected float GetTotalDistance() { int lineCount = GetLineCount(); // 총 라인 크기 + 상하/좌우 여백 float size = layoutSize + GetMainPadding() * UPDOWN_MULTIPLY; if (lineCount > 1) { // 라인 간의 간격 추가 size += ((lineCount - 1) * GetMainSpace()); } return size; } /// /// 특정 라인까지의 실제 거리를 계산합니다. (여백, 간격 포함) /// /// 라인의 기본 오프셋 /// 라인의 인덱스 /// 스크롤 시작점부터의 실제 거리 protected float GetLineDistance(float offset, int lineIndex) { float distance = offset + GetMainPadding(); if (lineIndex > 0) { distance += lineIndex * GetMainSpace(); } return distance; } /// /// 특정 라인에 포함된 첫 번째 아이템의 인덱스를 가져옵니다. /// 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; } /// /// 특정 라인에 포함된 마지막 아이템의 인덱스를 가져옵니다. /// 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; } /// /// 특정 라인의 크기(주 축 기준)를 가져옵니다. /// protected float GetLineSize(int lineIndex) { int lineCount = GetLineCount(); if (lineCount == 0) { return 0; } if (lineIndex >= lineCount) { lineIndex = lineCount - 1; } return lineLayout[lineIndex].size; } } }