AGENT SKILL
11-ui-scroll-frame-grid
暂无简介
适用职业设计师
- License 许可
- MIT
- 最近更新
- 2026-04-12
- Stars
- ★ 0
- Forks
- 0
02 资源文件 2
- 脚本scripts/edit_music.py10.2 KB
- 脚本scripts/score_joins.py6.9 KB
03 SKILL.md 正文
11-ui-scroll-frame-grid
Status: ACTIVE AppliesTo: v3
Overview
UIScrollGridFrame는 UIScrollContainer가 계산한 logical row enter/exit 요청만 받아
row 내부 cell을 spawn/show/hide/despawn 하는 grid section renderer다.
virtualization owner는 아니다.
현재 구현 원칙:
UIScrollGridFrame는 layout, pool, lifecycle bridge만 담당한다.- 실제 데이터 bind/reset은
UIScrollGridCell.onShow(int cellIndex)/onHide()가 담당한다. - scroll 쪽은 별도 init semantics를 만들지 않고
UIBaseFrame._Init()/_InitComplete()를 재사용한다.
SSOT
Code Path
framework-cs/upm/com.devian.foundation/Samples~/UIPackage/Runtime/Scroll/
├── UIScrollGridFrame.cs
└── UIScrollGridCell.cs
3-Path Mirror
| 경로 | 역할 |
|---|---|
framework-cs/upm/com.devian.foundation/Samples~/UIPackage/ |
UPM 정본 |
framework-cs/apps/UnityExample/Packages/com.devian.foundation/Samples~/UIPackage/ |
Packages mirror |
framework-cs/apps/UnityExample/Assets/Samples/Devian Foundation/{version}/UIPackage/ |
Assets/Samples 구현 |
Public Model
UIScrollGridCell
namespace Devian
{
[RequireComponent(typeof(RectTransform))]
public class UIScrollGridCell : UIBaseFrame, IPoolable
{
public int CellIndex { get; }
public void Show(int cellIndex);
public void Hide();
public void OnPoolSpawned();
public void OnPoolDespawned();
protected virtual void onInit();
protected virtual void onInitComplete();
protected virtual void onShow(int cellIndex);
protected virtual void onHide();
protected virtual void onPoolSpawned();
protected virtual void onPoolDespawned();
}
}
의미:
onInit()/onInitComplete()= semantic initonShow(int cellIndex)= 현재 index 기준 full bindonHide()= reset / unbindCellIndex는 현재 bind index다
UIScrollGridFrame
namespace Devian
{
public class UIScrollGridFrame : UIBaseFrame, IUIScrollSection
{
public string CellPrefabName { get; set; }
public int ColumnCount { get; set; }
public int MinimumLineCount { get; set; }
public Vector2 CellSize { get; set; }
public float RowSpacing { get; set; }
public RectOffset Padding { get; set; }
public bool FillEmptyCells { get; set; } // 빈 칸 cell spawn 여부 (기본: false)
public int CellCount { get; }
public int DataRowCount { get; }
public int RowCount { get; }
public int RenderCellCount { get; }
public void SetCellCount(int count);
public void SetMinimumLineCount(int lineCount);
public bool HasDataAt(int cellIndex);
public UIScrollGridCell Spawn(...);
public T Spawn<T>(...) where T : UIScrollGridCell;
public void Despawn<T>(T cell) where T : UIScrollGridCell;
protected virtual UIScrollGridCell createCell(Transform parent);
}
}
Typed Alias
namespace Devian
{
public abstract class UIScrollGridFrame<TCell> : UIScrollGridFrame
where TCell : UIScrollGridCell
{
}
}
UIScrollGridFrame<TCell>는 타입 의미만 주는 thin wrapper다.
bind hook을 추가하지 않는다.
Init Semantics
UIScrollGridFrame와 UIScrollGridCell 모두 별도 scroll init 시스템을 만들지 않는다.
기존 UIBaseFrame._Init() / _InitComplete()가 semantic boundary다.
해석 규칙:
- owner tree가 이미 initialized 상태면 cell spawn 직후
cell._Init(canvas)가 즉시 수행될 수 있다 - owner frame이 이미 init complete 상태면 같은 경로에서
cell._InitComplete()도 즉시 수행될 수 있다 - frame이 아직 init complete 전이면 cell은
onInit()까지만 먼저 수행되고, frameonInitComplete()시점에 active cell들이_InitComplete()된다
실무 규칙:
- child UI 구조 생성 / ref wiring은
cell.onInit()에 둔다 cell.onInit()안에서 새UIComponentBase/UIBaseFramesubtree를 만들었다면InitDynamicSubtree(root)를 호출해 기존 init 체계에 편입한다- 현재 데이터 bind는
cell.onShow(int cellIndex)에 둔다 - 이전 bind 정리는
cell.onHide()에 둔다
Runtime Flow
Row Bind
internal IUIScrollSection.BindRow(...) 흐름:
- row index 유효성 검사
- 이미 active면 기존 row unbind
- row마다
ColumnCount개 cell 생성 createCell(transform)호출 — cell은 frame의 child로 생성된다- spawn 직후
ensureCellInitialized()로cell._Init(canvas)/ 필요 시cell._InitComplete()연결 - anchor/pivot/size 적용
- position은 frame 로컬좌표로 계산:
localMainPos = mainPos - sectionMainAxisPosition cell.Show(cellIndex)호출
Row Unbind
internal IUIScrollSection.UnbindRow(...) 흐름:
- active row lookup
- 각 cell에 대해
Hide()후BundlePool.Despawn(cell)
Row Refresh
active row만 처리한다.
cell.Hide()cell.Show(cellIndex)
즉 refresh에서도 onHide()가 reset 경계다.
Section Clear
- 모든 active row cell을 hide/despawn
_activeRows.Clear()- holder는 active 상태를 유지한다
Layout Rules
GetWidth()는UIBaseFramebase 구현 사용GetHeight()=padding.top + rowCount * cellSize.y + (rowCount - 1) * rowSpacing + padding.bottom
- X축 spacing은 frame width 기준 자동 계산:
(frameWidth - padding.left - padding.right - columnCount * cellSize.x) / max(1, columnCount - 1)
left/right를 제외한 내부 영역에서 cell 간격을 균등 분배한다- row 0은
padding.top, 마지막 row는padding.bottom을 흡수한다 - 마지막 row도 partial row가 아니라 full row renderer다
- 데이터가 없는 slot은 placeholder로 남고, consumer는
HasDataAt(cellIndex)로 판정한다
Pool Rules
Spawn()은_cellPrefabIdprefab의 실제 pool type을 해석한 뒤 그 concrete type으로 spawn한다- prefab 대표
IPoolablecomponent는 반드시UIScrollGridCell하위 타입이어야 한다 Spawn<T>()는 typed facade다. 실제 생성은 항상 prefab concrete type 기준으로 수행된다OnPoolSpawned()는CellIndex = -1후_HandlePoolSpawned()bridge를 호출한다OnPoolDespawned()는CellIndex = -1후_HandlePoolDespawned()bridge를 호출한다- pooled cell은 despawn 시
UIBaseFrame._ResetForPool()로 init state가 reset된다
Recommended Usage
Frame
frame subclass는 보통 layout과 count만 설정한다.
public sealed class HeroEquipGridFrame : UIScrollGridFrame<HeroEquipGridCell>
{
protected override void onInit()
{
SetCellCount(InventoryManager.Instance.EquippedItems.Count);
}
}
Cell
실제 데이터 bind는 cell이 cellIndex로 직접 조회한다.
public sealed class HeroEquipGridCell : UIScrollGridCell
{
protected override void onInit()
{
// child UI 생성 / ref wiring
}
protected override void onShow(int cellIndex)
{
var items = InventoryManager.Instance.EquippedItems;
if (cellIndex < 0 || cellIndex >= items.Count)
return;
var item = items[cellIndex];
// bind
}
protected override void onHide()
{
// reset
}
}
즉:
- frame은 데이터 전달하지 않는다
- cell이
cellIndex로 직접 조회한다 onShow(int cellIndex)만으로 bind를 해결한다
Reference
- Parent:
skills/devian-unity/23-ui-package/SKILL.md - Scroll container:
skills/devian-unity/23-ui-package/21-ui-scroll-system/10-ui-scroll-container/SKILL.md - Frame lifecycle base:
skills/devian-unity/23-ui-package/10-base-system/11-ui-canvas-system/SKILL.md