Files
2026-02-21 16:45:37 +08:00

252 lines
5.8 KiB
C#

using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Serialization;
using UnityEngine.UI;
namespace UIWidgets
{
[AddComponentMenu("UI/Accordion", 350)]
public class Accordion : MonoBehaviour
{
[FormerlySerializedAs("Items")]
[SerializeField]
protected List<AccordionItem> items = new List<AccordionItem>();
[SerializeField]
public bool OnlyOneOpen = true;
[SerializeField]
public bool Animate = true;
[SerializeField]
public float AnimationDuration = 0.5f;
[SerializeField]
public AccordionDirection Direction = AccordionDirection.Vertical;
[SerializeField]
public AccordionEvent OnToggleItem = new AccordionEvent();
protected List<UnityAction> callbacks = new List<UnityAction>();
public List<AccordionItem> Items
{
get
{
return items;
}
protected set
{
if (items != null)
{
RemoveCallbacks();
}
items = value;
if (items != null)
{
AddCallbacks();
}
}
}
protected virtual void Start()
{
AddCallbacks();
UpdateLayout();
}
protected virtual void AddCallback(AccordionItem item)
{
if (item.Open)
{
Open(item, false);
}
else
{
Close(item, false);
}
UnityAction unityAction = delegate
{
ToggleItem(item);
};
item.ToggleObject.AddComponent<AccordionItemComponent>().OnClick.AddListener(unityAction);
item.ContentObjectRect = item.ContentObject.transform as RectTransform;
item.ContentLayoutElement = item.ContentObject.GetComponent<LayoutElement>() ?? item.ContentObject.AddComponent<LayoutElement>();
item.ContentObjectHeight = item.ContentObjectRect.rect.height;
if (item.ContentObjectHeight == 0f)
{
item.ContentObjectHeight = item.ContentLayoutElement.preferredHeight;
}
item.ContentObjectWidth = item.ContentObjectRect.rect.width;
if (item.ContentObjectWidth == 0f)
{
item.ContentObjectWidth = item.ContentLayoutElement.preferredWidth;
}
callbacks.Add(unityAction);
}
protected virtual void AddCallbacks()
{
Items.ForEach(AddCallback);
}
protected virtual void RemoveCallback(AccordionItem item, int index)
{
if (item != null && !(item.ToggleObject == null))
{
AccordionItemComponent component = item.ToggleObject.GetComponent<AccordionItemComponent>();
if (component != null && index < callbacks.Count)
{
component.OnClick.RemoveListener(callbacks[index]);
}
}
}
protected virtual void RemoveCallbacks()
{
Items.ForEach(RemoveCallback);
callbacks.Clear();
}
protected virtual void OnDestroy()
{
RemoveCallbacks();
}
public virtual void ToggleItem(AccordionItem item)
{
if (item.Open)
{
if (!OnlyOneOpen)
{
Close(item);
}
return;
}
if (OnlyOneOpen)
{
Items.Where(IsOpen).ForEach(Close);
}
Open(item);
}
public virtual void Open(AccordionItem item)
{
if (!item.Open)
{
if (OnlyOneOpen)
{
Items.Where(IsOpen).ForEach(Close);
}
Open(item, Animate);
}
}
public virtual void Close(AccordionItem item)
{
if (item.Open)
{
Close(item, Animate);
}
}
protected bool IsOpen(AccordionItem item)
{
return item.Open;
}
protected bool IsHorizontal()
{
return Direction == AccordionDirection.Horizontal;
}
protected virtual void Open(AccordionItem item, bool animate)
{
if (item.CurrentCorutine != null)
{
StopCoroutine(item.CurrentCorutine);
if (IsHorizontal())
{
item.ContentObjectRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, item.ContentObjectWidth);
item.ContentLayoutElement.preferredWidth = item.ContentObjectWidth;
}
else
{
item.ContentObjectRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, item.ContentObjectHeight);
item.ContentLayoutElement.preferredHeight = item.ContentObjectHeight;
}
item.ContentObject.SetActive(false);
}
if (animate)
{
item.CurrentCorutine = StartCoroutine(OpenCorutine(item));
}
else
{
item.ContentObject.SetActive(true);
OnToggleItem.Invoke(item);
}
item.ContentObject.SetActive(true);
item.Open = true;
}
protected virtual void Close(AccordionItem item, bool animate)
{
if (item.CurrentCorutine != null)
{
StopCoroutine(item.CurrentCorutine);
item.ContentObject.SetActive(true);
if (IsHorizontal())
{
item.ContentObjectRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, item.ContentObjectWidth);
item.ContentLayoutElement.preferredWidth = item.ContentObjectWidth;
}
else
{
item.ContentObjectRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, item.ContentObjectHeight);
item.ContentLayoutElement.preferredHeight = item.ContentObjectHeight;
}
}
if (item.ContentObjectRect != null)
{
item.ContentObjectHeight = item.ContentObjectRect.rect.height;
item.ContentObjectWidth = item.ContentObjectRect.rect.width;
}
if (animate)
{
item.CurrentCorutine = StartCoroutine(HideCorutine(item));
return;
}
item.ContentObject.SetActive(false);
item.Open = false;
OnToggleItem.Invoke(item);
}
protected virtual IEnumerator OpenCorutine(AccordionItem item)
{
item.ContentObject.SetActive(true);
item.Open = true;
yield return StartCoroutine(Animations.Open(item.ContentObjectRect, AnimationDuration, IsHorizontal()));
UpdateLayout();
OnToggleItem.Invoke(item);
}
protected void UpdateLayout()
{
Utilites.UpdateLayout(GetComponent<LayoutGroup>());
}
protected virtual IEnumerator HideCorutine(AccordionItem item)
{
yield return StartCoroutine(Animations.Collapse(item.ContentObjectRect, AnimationDuration, IsHorizontal()));
item.Open = false;
item.ContentObject.SetActive(false);
UpdateLayout();
OnToggleItem.Invoke(item);
}
}
}