接口化UIAnimationTransition

This commit is contained in:
陈思海 2026-03-17 17:15:31 +08:00
parent c692b47a62
commit 9e42167e72
10 changed files with 287 additions and 40 deletions

View File

@ -56,6 +56,16 @@ namespace AlicizaX.UI.Runtime
{ {
if (meta.State != UIState.CreatedUI) return; if (meta.State != UIState.CreatedUI) return;
GameObject obj = await LoadUIResourcesAsync(meta.ResInfo, parent); GameObject obj = await LoadUIResourcesAsync(meta.ResInfo, parent);
if (meta.CancellationToken.IsCancellationRequested || meta.View == null || meta.State != UIState.CreatedUI)
{
if (obj != null)
{
Object.Destroy(obj);
}
return;
}
ValidateAndBind(meta, obj, owner); ValidateAndBind(meta, obj, owner);
} }

View File

@ -29,6 +29,11 @@ namespace AlicizaX.UI.Runtime
{ {
CreateMetaUI(metaInfo); CreateMetaUI(metaInfo);
await UIHolderFactory.CreateUIResourceAsync(metaInfo, UICacheLayer); await UIHolderFactory.CreateUIResourceAsync(metaInfo, UICacheLayer);
if (metaInfo.View == null || metaInfo.State == UIState.Uninitialized || metaInfo.State == UIState.Destroyed)
{
return null;
}
FinalizeShow(metaInfo, userDatas); FinalizeShow(metaInfo, userDatas);
await UpdateVisualState(metaInfo, metaInfo.CancellationToken); await UpdateVisualState(metaInfo, metaInfo.CancellationToken);
return metaInfo.View; return metaInfo.View;
@ -38,6 +43,11 @@ namespace AlicizaX.UI.Runtime
{ {
CreateMetaUI(metaInfo); CreateMetaUI(metaInfo);
UIHolderFactory.CreateUIResourceSync(metaInfo, UICacheLayer); UIHolderFactory.CreateUIResourceSync(metaInfo, UICacheLayer);
if (metaInfo.View == null || metaInfo.State == UIState.Uninitialized || metaInfo.State == UIState.Destroyed)
{
return null;
}
FinalizeShow(metaInfo, userDatas); FinalizeShow(metaInfo, userDatas);
UpdateVisualState(metaInfo).Forget(); UpdateVisualState(metaInfo).Forget();
return metaInfo.View; return metaInfo.View;
@ -45,13 +55,36 @@ namespace AlicizaX.UI.Runtime
private async UniTask CloseUIImpl(UIMetadata meta, bool force) private async UniTask CloseUIImpl(UIMetadata meta, bool force)
{ {
if (meta.State == UIState.Uninitialized || meta.State == UIState.CreatedUI) if (meta.State == UIState.Uninitialized)
{ {
return; return;
} }
if (meta.State == UIState.CreatedUI)
{
meta.CancelAsyncOperations();
meta.Dispose();
return;
}
if (meta.State == UIState.Loaded || meta.State == UIState.Initialized)
{
meta.CancelAsyncOperations();
Pop(meta);
SortWindowVisible(meta.MetaInfo.UILayer);
SortWindowDepth(meta.MetaInfo.UILayer);
meta.View.Visible = false;
CacheWindow(meta, force);
return;
}
meta.CancelAsyncOperations(); meta.CancelAsyncOperations();
await meta.View.InternalClose(); await meta.View.InternalClose();
if (meta.State != UIState.Closed)
{
return;
}
Pop(meta); Pop(meta);
SortWindowVisible(meta.MetaInfo.UILayer); SortWindowVisible(meta.MetaInfo.UILayer);
SortWindowDepth(meta.MetaInfo.UILayer); SortWindowDepth(meta.MetaInfo.UILayer);
@ -85,6 +118,8 @@ namespace AlicizaX.UI.Runtime
case UIState.Loaded: case UIState.Loaded:
Push(meta); Push(meta);
break; break;
case UIState.Opening:
case UIState.Closing:
case UIState.Opened: case UIState.Opened:
MoveToTop(meta); MoveToTop(meta);
break; break;
@ -186,7 +221,7 @@ namespace AlicizaX.UI.Runtime
for (int i = count - 1; i >= 0; i--) for (int i = count - 1; i >= 0; i--)
{ {
var meta = list[i]; var meta = list[i];
if (meta.MetaInfo.FullScreen && meta.State == UIState.Opened) if (meta.MetaInfo.FullScreen && UIStateMachine.IsDisplayActive(meta.State))
{ {
fullscreenIdx = i; fullscreenIdx = i;
break; break;

View File

@ -0,0 +1,14 @@
using System.Threading;
using Cysharp.Threading.Tasks;
namespace AlicizaX.UI.Runtime
{
public interface IUITransitionPlayer
{
UniTask PlayOpenAsync(CancellationToken cancellationToken = default);
UniTask PlayCloseAsync(CancellationToken cancellationToken = default);
void Stop();
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b746e052b3511314993f07f959473956
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,74 @@
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;
namespace AlicizaX.UI.Runtime
{
[DisallowMultipleComponent]
public sealed class UIAnimationFlowTransition : MonoBehaviour, IUITransitionPlayer
{
#if ALICIZAX_UI_ANIMATION_SUPPORT
[SerializeField] private AnimationFlow.Runtime.AnimationFlow animationFlow;
[SerializeField] private string openClip = "Open";
[SerializeField] private string closeClip = "Close";
#endif
public UniTask PlayOpenAsync(CancellationToken cancellationToken = default)
{
#if ALICIZAX_UI_ANIMATION_SUPPORT
return PlayAsync(openClip, cancellationToken);
#else
return UniTask.CompletedTask;
#endif
}
public UniTask PlayCloseAsync(CancellationToken cancellationToken = default)
{
#if ALICIZAX_UI_ANIMATION_SUPPORT
return PlayAsync(closeClip, cancellationToken);
#else
return UniTask.CompletedTask;
#endif
}
public void Stop()
{
#if ALICIZAX_UI_ANIMATION_SUPPORT
ResolveAnimationFlow()?.Stop();
#endif
}
#if ALICIZAX_UI_ANIMATION_SUPPORT
private UniTask PlayAsync(string clipName, CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested || string.IsNullOrWhiteSpace(clipName))
{
return UniTask.CompletedTask;
}
AnimationFlow.Runtime.AnimationFlow flow = ResolveAnimationFlow();
return flow == null ? UniTask.CompletedTask : flow.PlayAsync(clipName);
}
private AnimationFlow.Runtime.AnimationFlow ResolveAnimationFlow()
{
if (animationFlow == null)
{
animationFlow = GetComponent<AnimationFlow.Runtime.AnimationFlow>();
}
return animationFlow;
}
#if UNITY_EDITOR
private void OnValidate()
{
if (animationFlow == null)
{
animationFlow = GetComponent<AnimationFlow.Runtime.AnimationFlow>();
}
}
#endif
#endif
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1ad79303854072f4798edbea92187a26
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -22,6 +22,7 @@ namespace AlicizaX.UI.Runtime
internal Canvas _canvas; internal Canvas _canvas;
internal GraphicRaycaster _raycaster; internal GraphicRaycaster _raycaster;
private int _lifecycleVersion;
internal UIState _state = UIState.Uninitialized; internal UIState _state = UIState.Uninitialized;
internal UIState State => _state; internal UIState State => _state;
@ -217,31 +218,67 @@ namespace AlicizaX.UI.Runtime
internal async UniTask InternalOpen(CancellationToken cancellationToken = default) internal async UniTask InternalOpen(CancellationToken cancellationToken = default)
{ {
if (_state == UIState.Opened) if (_state == UIState.Opened || _state == UIState.Opening)
return; // Already open return;
if (!UIStateMachine.ValidateTransition(GetType().Name, _state, UIState.Opened)) if (!UIStateMachine.ValidateTransition(GetType().Name, _state, UIState.Opening))
return;
int lifecycleVersion = BeginLifecycleTransition();
_state = UIState.Opening;
Visible = true;
Holder.OnWindowBeforeShowEvent?.Invoke();
try
{
await OnOpenAsync(cancellationToken);
if (!IsCurrentLifecycleTransition(lifecycleVersion, UIState.Opening))
return;
cancellationToken.ThrowIfCancellationRequested();
await Holder.PlayOpenTransitionAsync(cancellationToken);
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
return;
}
if (!IsCurrentLifecycleTransition(lifecycleVersion, UIState.Opening))
return; return;
_state = UIState.Opened; _state = UIState.Opened;
Visible = true;
Holder.OnWindowBeforeShowEvent?.Invoke();
await OnOpenAsync(cancellationToken);
Holder.OnWindowAfterShowEvent?.Invoke(); Holder.OnWindowAfterShowEvent?.Invoke();
} }
internal async UniTask InternalClose(CancellationToken cancellationToken = default) internal async UniTask InternalClose(CancellationToken cancellationToken = default)
{ {
if (_state != UIState.Opened) if (_state == UIState.Closed || _state == UIState.Closing)
return; return;
if (!UIStateMachine.ValidateTransition(GetType().Name, _state, UIState.Closed)) if (!UIStateMachine.ValidateTransition(GetType().Name, _state, UIState.Closing))
return; return;
int lifecycleVersion = BeginLifecycleTransition();
_state = UIState.Closing;
Holder.OnWindowBeforeClosedEvent?.Invoke(); Holder.OnWindowBeforeClosedEvent?.Invoke();
await OnCloseAsync(cancellationToken); try
_state = UIState.Closed; {
await OnCloseAsync(cancellationToken);
if (!IsCurrentLifecycleTransition(lifecycleVersion, UIState.Closing))
return;
cancellationToken.ThrowIfCancellationRequested();
await Holder.PlayCloseTransitionAsync(cancellationToken);
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
return;
}
if (!IsCurrentLifecycleTransition(lifecycleVersion, UIState.Closing))
return;
Visible = false; Visible = false;
_state = UIState.Closed;
Holder.OnWindowAfterClosedEvent?.Invoke(); Holder.OnWindowAfterClosedEvent?.Invoke();
} }
@ -257,8 +294,9 @@ namespace AlicizaX.UI.Runtime
if (!UIStateMachine.ValidateTransition(GetType().Name, _state, UIState.Destroying)) if (!UIStateMachine.ValidateTransition(GetType().Name, _state, UIState.Destroying))
return; return;
InterruptLifecycleTransition();
_state = UIState.Destroying; _state = UIState.Destroying;
Holder.OnWindowDestroyEvent?.Invoke(); Holder?.OnWindowDestroyEvent?.Invoke();
await DestroyAllChildren(); await DestroyAllChildren();
OnDestroy(); OnDestroy();
ReleaseEventListenerProxy(); ReleaseEventListenerProxy();
@ -271,6 +309,23 @@ namespace AlicizaX.UI.Runtime
this._userDatas = userDatas; this._userDatas = userDatas;
} }
private int BeginLifecycleTransition()
{
InterruptLifecycleTransition();
return _lifecycleVersion;
}
private void InterruptLifecycleTransition()
{
_lifecycleVersion++;
Holder?.StopTransition();
}
private bool IsCurrentLifecycleTransition(int lifecycleVersion, UIState state)
{
return lifecycleVersion == _lifecycleVersion && _state == state;
}
#endregion #endregion
} }
} }

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Threading;
using Cysharp.Threading.Tasks; using Cysharp.Threading.Tasks;
using UnityEngine; using UnityEngine;
@ -15,27 +16,14 @@ namespace AlicizaX.UI.Runtime
public Action OnWindowDestroyEvent; public Action OnWindowDestroyEvent;
#if ALICIZAX_UI_ANIMATION_SUPPORT
public async UniTask PlayAnimtion(string name)
{
if (AnimationFlow == null)
{
AnimationFlow = transform.GetComponent<AnimationFlow.Runtime.AnimationFlow>();
}
await AnimationFlow.PlayAsync(name);
}
#endif
private GameObject _target; private GameObject _target;
private IUITransitionPlayer _transitionPlayer;
/// <summary> /// <summary>
/// UI实例资源对象。 /// UI实例资源对象。
/// </summary> /// </summary>
public GameObject Target => _target ??= gameObject; public GameObject Target => _target ??= gameObject;
private RectTransform _rectTransform; private RectTransform _rectTransform;
/// <summary> /// <summary>
@ -53,30 +41,68 @@ namespace AlicizaX.UI.Runtime
internal set { _target.SetActive(value); } internal set { _target.SetActive(value); }
} }
#if ALICIZAX_UI_ANIMATION_SUPPORT
private AnimationFlow.Runtime.AnimationFlow AnimationFlow;
#endif
public virtual void Awake() public virtual void Awake()
{ {
_target = gameObject; _target = gameObject;
#if ALICIZAX_UI_ANIMATION_SUPPORT
AnimationFlow = GetComponent<AnimationFlow.Runtime.AnimationFlow>();
#endif
} }
private bool _isAlive = true; private bool _isAlive = true;
public bool IsValid() public bool IsValid()
{ {
return this != null && _isAlive; return this != null && _isAlive;
} }
internal UniTask PlayOpenTransitionAsync(CancellationToken cancellationToken = default)
{
return TryGetTransitionPlayer(out var transitionPlayer)
? transitionPlayer.PlayOpenAsync(cancellationToken)
: UniTask.CompletedTask;
}
internal UniTask PlayCloseTransitionAsync(CancellationToken cancellationToken = default)
{
return TryGetTransitionPlayer(out var transitionPlayer)
? transitionPlayer.PlayCloseAsync(cancellationToken)
: UniTask.CompletedTask;
}
internal void StopTransition()
{
if (TryGetTransitionPlayer(out var transitionPlayer))
{
transitionPlayer.Stop();
}
}
private bool TryGetTransitionPlayer(out IUITransitionPlayer transitionPlayer)
{
if (_transitionPlayer != null)
{
transitionPlayer = _transitionPlayer;
return true;
}
MonoBehaviour[] behaviours = GetComponents<MonoBehaviour>();
for (int i = 0; i < behaviours.Length; i++)
{
if (behaviours[i] is IUITransitionPlayer player)
{
_transitionPlayer = player;
transitionPlayer = player;
return true;
}
}
transitionPlayer = null;
return false;
}
private void OnDestroy() private void OnDestroy()
{ {
_isAlive = false; _isAlive = false;
_transitionPlayer = null;
} }
} }
} }

View File

@ -6,7 +6,9 @@
CreatedUI, CreatedUI,
Loaded, Loaded,
Initialized, Initialized,
Opening,
Opened, Opened,
Closing,
Closed, Closed,
Destroying, Destroying,
Destroyed, Destroyed,

View File

@ -9,11 +9,13 @@ namespace AlicizaX.UI.Runtime
private static readonly Dictionary<UIState, HashSet<UIState>> _validTransitions = new() private static readonly Dictionary<UIState, HashSet<UIState>> _validTransitions = new()
{ {
[UIState.Uninitialized] = new() { UIState.CreatedUI }, [UIState.Uninitialized] = new() { UIState.CreatedUI },
[UIState.CreatedUI] = new() { UIState.Loaded }, [UIState.CreatedUI] = new() { UIState.Loaded, UIState.Destroying },
[UIState.Loaded] = new() { UIState.Initialized }, [UIState.Loaded] = new() { UIState.Initialized, UIState.Destroying },
[UIState.Initialized] = new() { UIState.Opened }, [UIState.Initialized] = new() { UIState.Opening, UIState.Destroying },
[UIState.Opened] = new() { UIState.Closed, UIState.Destroying }, [UIState.Opening] = new() { UIState.Opened, UIState.Closing, UIState.Destroying },
[UIState.Closed] = new() { UIState.Opened, UIState.Destroying }, [UIState.Opened] = new() { UIState.Closing, UIState.Destroying },
[UIState.Closing] = new() { UIState.Opening, UIState.Closed, UIState.Destroying },
[UIState.Closed] = new() { UIState.Opening, UIState.Destroying },
[UIState.Destroying] = new() { UIState.Destroyed }, [UIState.Destroying] = new() { UIState.Destroyed },
[UIState.Destroyed] = new() { } [UIState.Destroyed] = new() { }
}; };
@ -48,12 +50,19 @@ namespace AlicizaX.UI.Runtime
UIState.CreatedUI => "UI logic created, awaiting resource load", UIState.CreatedUI => "UI logic created, awaiting resource load",
UIState.Loaded => "Resources loaded, awaiting initialization", UIState.Loaded => "Resources loaded, awaiting initialization",
UIState.Initialized => "Initialized, ready to open", UIState.Initialized => "Initialized, ready to open",
UIState.Opening => "Opening transition is running",
UIState.Opened => "Currently visible and active", UIState.Opened => "Currently visible and active",
UIState.Closing => "Closing transition is running",
UIState.Closed => "Hidden but cached", UIState.Closed => "Hidden but cached",
UIState.Destroying => "Being destroyed", UIState.Destroying => "Being destroyed",
UIState.Destroyed => "Fully destroyed", UIState.Destroyed => "Fully destroyed",
_ => "Unknown state" _ => "Unknown state"
}; };
} }
public static bool IsDisplayActive(UIState state)
{
return state == UIState.Opening || state == UIState.Opened || state == UIState.Closing;
}
} }
} }