728x90
접기/펼치기 토글로 내용이 스르륵 나타났다 사라지는 메뉴, 흔히 '아코디언 메뉴'라고 불리는 UI를 직접 구현해봤습니다.
굳이 에셋을 사거나 별도 트윈 시스템을 구현하는 게 귀찮아서, 그냥 간단하게 구성했습니다.
아래는 제가 실제로 구현한 과정과 팁을 정리한 내용입니다.
1. UI 세팅
아코디언 메뉴는 다음처럼 3단계로 구성됩니다.
Panel
└ Item
├ Header
└ Content
① Panel
Panel은 전체적인 레이아웃을 관리하는 부모 오브젝트입니다.
- Vertical Layout Group : 아이템들이 세로로 정렬되도록 설정
- Content Size Fitter : 내용물에 따라 자동으로 크기 조정
⚠️ Control Child Size 옵션을 켜서 Content의 높이가 제대로 반영되게 만들어야 합니다.
② Item
각각의 접기/펼치기 단위가 되는 개별 아이템입니다.
- Vertical Layout Group : Header와 Content의 배치 및 크기 균형 유지
- Layout Element : LayoutGroup을 쓴다면, 이걸 통해 사이즈 제어하는 게 중요 (안 그러면 예상치 못한 문제 발생 가능)
✅ Use Child Scale 체크해서, 자식 오브젝트들의 Preferred 크기들이 자동 반영되도록 설정합니다.
③ Header & Content
구성설명
Header | 접기/펼치기 버튼이 들어가는 영역. RectTransform Height로 고정 크기 설정 |
Content | 펼쳐질 내용 영역. LayoutElement의 Preferred Height를 원하는 크기로 설정 (RectTransform도 동기화 필요) |
⚠️ Content의 높이 자동화 방법도 있지만, 이게 최적화 측면에서 좋은지는 고민해볼 부분입니다.
2. 코드 구성
① AccordionPanel (Panel에 부착)
- 추후 아이템 동적 생성 관리까지 고려해서 만든 매니저 역할 클래스
- 기본 기능: 전체 접기/펼치기, 속도 조절, 애니메이션 방식 설정 등
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
[RequireComponent(typeof(LayoutGroup))]
public class AccordionPanel : MonoBehaviour
{
[SerializeField] private List<AccordionItem> items = new();
[field : SerializeField] public float EaseDuration { get; private set; } = 0.3f;
[field: SerializeField] public DG.Tweening.Ease EaseOut { get; private set; } = DG.Tweening.Ease.OutCubic;
[field: SerializeField] public DG.Tweening.Ease EaseIn { get; private set; } = DG.Tweening.Ease.InCubic;
private void Start()
{
FindAllItems();
foreach (var item in items)
{
item.Initialize(this);
}
}
private void FindAllItems()
{
items.Clear();
foreach (Transform child in transform)
{
var item = child.GetComponent<AccordionItem>();
if (item != null)
{
items.Add(item);
}
}
}
}
② AccordionItem (각 Item에 부착)
- 실제 접기/펼치기 기능을 담당
- 크기 변경 후 LayoutRebuilder.ForceRebuildLayoutImmediate() 를 호출해 레이아웃 갱신을 강제합니다.
⚠️ 강제 갱신은 편하지만, 성능상 어떤 영향이 있는지는 더 테스트해봐야 합니다.
using UnityEngine;
using UnityEngine.UI;
using DG.Tweening;
[RequireComponent(typeof(LayoutElement))]
public class AccordionItem : MonoBehaviour
{
[SerializeField] private RectTransform _content;
[SerializeField] private RectTransform _header;
[SerializeField] private Button _toggleButton;
private LayoutElement _layoutElement;
private AccordionPanel _accordionPanel;
private bool _isExpanded = false;
private float _contentPreferredHeight = 0f;
private float _headerHeight = 0f;
public VerticalLayoutGroup verticalLayoutGroup;
public void Initialize(AccordionPanel parent)
{
_accordionPanel = parent;
_layoutElement = GetComponent<LayoutElement>();
if (_content == null)
{
Debug.LogError($"{name}: _content is not assigned!");
}
if (_toggleButton == null)
{
Debug.LogError($"{name}: _toggleButton is not assigned!");
}
if(_header == null)
{
Debug.LogError($"{name}: _header is not assigned!");
}
LayoutRebuilder.ForceRebuildLayoutImmediate(_content);
_contentPreferredHeight = LayoutUtility.GetPreferredHeight(_content);
_headerHeight = _header.sizeDelta.y;
_toggleButton.onClick.AddListener(Toggle);
CollapseImmediate(); // 초기에 content들을 모두 닫아둔다.
}
private void Toggle()
{
if (_isExpanded)
{
Collapse();
}
else
{
Expand();
}
}
public void Expand()
{
_content.DOScaleY(1, 0.3f).SetEase(Ease.OutCubic);
float targetHeight = _contentPreferredHeight + _headerHeight;
_layoutElement.DOPreferredSize(new Vector2(_layoutElement.preferredWidth, targetHeight), _accordionPanel.EaseDuration).SetEase(_accordionPanel.EaseOut);
_isExpanded = true;
}
public void Collapse()
{
_content.DOScaleY(0, 0.3f).SetEase(Ease.InCubic);
float targetHeight = _headerHeight;
_layoutElement.DOPreferredSize(new Vector2(_layoutElement.preferredWidth, targetHeight), _accordionPanel.EaseDuration).SetEase(_accordionPanel.EaseIn);
_isExpanded = false;
}
public void CollapseImmediate()
{
_content.localScale = new Vector3(1, 0, 1);
_layoutElement.preferredHeight = _headerHeight;
_isExpanded = false;
}
}
마무리
Dotween 무료버전으로 충분히 구현가능하지만 코루틴으로 이를 충분히 대체할수 있으니 다들 좋은 글이 되었기를..
피드벡은 언제나 환영입니다.
728x90
'2025 > Unity' 카테고리의 다른 글
Unity3D - indicator 구현 (0) | 2025.03.13 |
---|---|
[Unity] tool 만들기 아코디언 메뉴 (0) | 2025.03.01 |
UnityEditor : 스크립터블오브젝트 <-> json 상호 변환기 (0) | 2025.02.25 |
unity - 2022ver 타임라인 녹화 오류 버그 (0) | 2025.02.23 |
Unity 마우스 위치로 파악하는 Screen, View, Object, World 좌표 (0) | 2025.02.13 |