接口化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;
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);
}

View File

@ -29,6 +29,11 @@ namespace AlicizaX.UI.Runtime
{
CreateMetaUI(metaInfo);
await UIHolderFactory.CreateUIResourceAsync(metaInfo, UICacheLayer);
if (metaInfo.View == null || metaInfo.State == UIState.Uninitialized || metaInfo.State == UIState.Destroyed)
{
return null;
}
FinalizeShow(metaInfo, userDatas);
await UpdateVisualState(metaInfo, metaInfo.CancellationToken);
return metaInfo.View;
@ -38,6 +43,11 @@ namespace AlicizaX.UI.Runtime
{
CreateMetaUI(metaInfo);
UIHolderFactory.CreateUIResourceSync(metaInfo, UICacheLayer);
if (metaInfo.View == null || metaInfo.State == UIState.Uninitialized || metaInfo.State == UIState.Destroyed)
{
return null;
}
FinalizeShow(metaInfo, userDatas);
UpdateVisualState(metaInfo).Forget();
return metaInfo.View;
@ -45,13 +55,36 @@ namespace AlicizaX.UI.Runtime
private async UniTask CloseUIImpl(UIMetadata meta, bool force)
{
if (meta.State == UIState.Uninitialized || meta.State == UIState.CreatedUI)
if (meta.State == UIState.Uninitialized)
{
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();
await meta.View.InternalClose();
if (meta.State != UIState.Closed)
{
return;
}
Pop(meta);
SortWindowVisible(meta.MetaInfo.UILayer);
SortWindowDepth(meta.MetaInfo.UILayer);
@ -85,6 +118,8 @@ namespace AlicizaX.UI.Runtime
case UIState.Loaded:
Push(meta);
break;
case UIState.Opening:
case UIState.Closing:
case UIState.Opened:
MoveToTop(meta);
break;
@ -186,7 +221,7 @@ namespace AlicizaX.UI.Runtime
for (int i = count - 1; i >= 0; i--)
{
var meta = list[i];
if (meta.MetaInfo.FullScreen && meta.State == UIState.Opened)
if (meta.MetaInfo.FullScreen && UIStateMachine.IsDisplayActive(meta.State))
{
fullscreenIdx = i;
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 GraphicRaycaster _raycaster;
private int _lifecycleVersion;
internal UIState _state = UIState.Uninitialized;
internal UIState State => _state;
@ -217,31 +218,67 @@ namespace AlicizaX.UI.Runtime
internal async UniTask InternalOpen(CancellationToken cancellationToken = default)
{
if (_state == UIState.Opened)
return; // Already open
if (_state == UIState.Opened || _state == UIState.Opening)
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;
_state = UIState.Opened;
Visible = true;
Holder.OnWindowBeforeShowEvent?.Invoke();
await OnOpenAsync(cancellationToken);
Holder.OnWindowAfterShowEvent?.Invoke();
}
internal async UniTask InternalClose(CancellationToken cancellationToken = default)
{
if (_state != UIState.Opened)
if (_state == UIState.Closed || _state == UIState.Closing)
return;
if (!UIStateMachine.ValidateTransition(GetType().Name, _state, UIState.Closed))
if (!UIStateMachine.ValidateTransition(GetType().Name, _state, UIState.Closing))
return;
int lifecycleVersion = BeginLifecycleTransition();
_state = UIState.Closing;
Holder.OnWindowBeforeClosedEvent?.Invoke();
try
{
await OnCloseAsync(cancellationToken);
_state = UIState.Closed;
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;
_state = UIState.Closed;
Holder.OnWindowAfterClosedEvent?.Invoke();
}
@ -257,8 +294,9 @@ namespace AlicizaX.UI.Runtime
if (!UIStateMachine.ValidateTransition(GetType().Name, _state, UIState.Destroying))
return;
InterruptLifecycleTransition();
_state = UIState.Destroying;
Holder.OnWindowDestroyEvent?.Invoke();
Holder?.OnWindowDestroyEvent?.Invoke();
await DestroyAllChildren();
OnDestroy();
ReleaseEventListenerProxy();
@ -271,6 +309,23 @@ namespace AlicizaX.UI.Runtime
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
}
}

View File

@ -1,4 +1,5 @@
using System;
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;
@ -15,27 +16,14 @@ namespace AlicizaX.UI.Runtime
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 IUITransitionPlayer _transitionPlayer;
/// <summary>
/// UI实例资源对象。
/// </summary>
public GameObject Target => _target ??= gameObject;
private RectTransform _rectTransform;
/// <summary>
@ -53,30 +41,68 @@ namespace AlicizaX.UI.Runtime
internal set { _target.SetActive(value); }
}
#if ALICIZAX_UI_ANIMATION_SUPPORT
private AnimationFlow.Runtime.AnimationFlow AnimationFlow;
#endif
public virtual void Awake()
{
_target = gameObject;
#if ALICIZAX_UI_ANIMATION_SUPPORT
AnimationFlow = GetComponent<AnimationFlow.Runtime.AnimationFlow>();
#endif
}
private bool _isAlive = true;
public bool IsValid()
{
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()
{
_isAlive = false;
_transitionPlayer = null;
}
}
}

View File

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

View File

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