• UE5 Lyra中的UI层级与资产管理


    一、外部接入接口:UGameUIManagerSubsystem

    主要用来从GameInstance获取玩家的加入退出和销毁通知,并传入UGameUIPolicy
    DefaultUIPolicyClassDefaultGame.ini中进行配置

    UPROPERTY(config, EditAnywhere)
    TSoftClassPtr<UGameUIPolicy> DefaultUIPolicyClass;
    void UGameUIManagerSubsystem::Initialize(FSubsystemCollectionBase& Collection)
    {
       if (!CurrentPolicy && !DefaultUIPolicyClass.IsNull()){
          TSubclassOf<UGameUIPolicy> PolicyClass = DefaultUIPolicyClass.LoadSynchronous();
          SwitchToPolicy(NewObject<UGameUIPolicy>(this, PolicyClass));}
    }
    void UGameUIManagerSubsystem::NotifyPlayerAdded(UCommonLocalPlayer* LocalPlayer){
       if (ensure(LocalPlayer) && CurrentPolicy){
          CurrentPolicy->NotifyPlayerAdded(LocalPlayer);
       }}
    
    void UGameUIManagerSubsystem::NotifyPlayerRemoved(UCommonLocalPlayer* LocalPlayer){
       if (LocalPlayer && CurrentPolicy){
          CurrentPolicy->NotifyPlayerRemoved(LocalPlayer);
       }}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    二、对内总管:UGameUIPolicy

    用来自动控制·UPrimaryGameLayout·不同情况下(主要是Player的改动)的创建与销毁
    LayoutClass可以由GameUIPolicy的蓝图实例来指定

    UPROPERTY(EditAnywhere)
    TSoftClassPtr<UPrimaryGameLayout> LayoutClass;
    
    void UGameUIPolicy::NotifyPlayerAdded(UCommonLocalPlayer* LocalPlayer){
       LocalPlayer->OnPlayerControllerSet.AddWeakLambda(this, [this](UCommonLocalPlayer* LocalPlayer, APlayerController* PlayerController){
          NotifyPlayerRemoved(LocalPlayer);
          if (FRootViewportLayoutInfo* LayoutInfo = RootViewportLayouts.FindByKey(LocalPlayer)){
             AddLayoutToViewport(LocalPlayer, LayoutInfo->RootLayout);
             LayoutInfo->bAddedToViewport = true;}
          else{
             CreateLayoutWidget(LocalPlayer);}
       });
    
       if (FRootViewportLayoutInfo* LayoutInfo = RootViewportLayouts.FindByKey(LocalPlayer)){
          AddLayoutToViewport(LocalPlayer, LayoutInfo->RootLayout);
          LayoutInfo->bAddedToViewport = true;}
       else{
          CreateLayoutWidget(LocalPlayer);
       }}
       
    void UGameUIPolicy::CreateLayoutWidget(UCommonLocalPlayer* LocalPlayer)
    {
       if (APlayerController* PlayerController = LocalPlayer->GetPlayerController(GetWorld())){
          TSubclassOf<UPrimaryGameLayout> LayoutWidgetClass = GetLayoutWidgetClass(LocalPlayer);
          if (ensure(LayoutWidgetClass && !LayoutWidgetClass->HasAnyClassFlags(CLASS_Abstract))){
             UPrimaryGameLayout* NewLayoutObject = CreateWidget<UPrimaryGameLayout>(PlayerController, LayoutWidgetClass);
             RootViewportLayouts.Emplace(LocalPlayer, NewLayoutObject, true);
             AddLayoutToViewport(LocalPlayer, NewLayoutObject);
          }}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    三、Layout分层管理:UPrimaryGameLayout

    作为游戏的主UI,负责对整个UI框架逻辑的管理,包括栈状态机、Layout、动态加载等。不参与布局与响应逻辑。继承自UCommonUserWidget

    UPROPERTY(Transient, meta = (Categories = "UI.Layer"))
    TMap<FGameplayTag, UCommonActivatableWidgetContainerBase*> Layers;
    
    template <typename ActivatableWidgetT = UCommonActivatableWidget>
    TSharedPtr<FStreamableHandle> PushWidgetToLayerStackAsync(FGameplayTag LayerName, bool bSuspendInputUntilComplete, TSoftClassPtr<UCommonActivatableWidget> ActivatableWidgetClass, TFunction<void(EAsyncWidgetLayerState, ActivatableWidgetT*)> StateFunc)
    {
       static_assert(TIsDerivedFrom<ActivatableWidgetT, UCommonActivatableWidget>::IsDerived, "Only CommonActivatableWidgets can be used here");
    
       static FName NAME_PushingWidgetToLayer("PushingWidgetToLayer");
       const FName SuspendInputToken = bSuspendInputUntilComplete ? UCommonUIExtensions::SuspendInputForPlayer(GetOwningPlayer(), NAME_PushingWidgetToLayer) : NAME_None;
    
       FStreamableManager& StreamableManager = UAssetManager::Get().GetStreamableManager();
       TSharedPtr<FStreamableHandle> StreamingHandle = StreamableManager.RequestAsyncLoad(ActivatableWidgetClass.ToSoftObjectPath(), FStreamableDelegate::CreateWeakLambda(this,
          [this, LayerName, ActivatableWidgetClass, StateFunc, SuspendInputToken]()
          {
             UCommonUIExtensions::ResumeInputForPlayer(GetOwningPlayer(), SuspendInputToken);
    
             ActivatableWidgetT* Widget = PushWidgetToLayerStack<ActivatableWidgetT>(LayerName, ActivatableWidgetClass.Get(), [StateFunc](ActivatableWidgetT& WidgetToInit) {
                StateFunc(EAsyncWidgetLayerState::Initialize, &WidgetToInit);
             });
    
             StateFunc(EAsyncWidgetLayerState::AfterPush, Widget);
          })
       );
    
       // Setup a cancel delegate so that we can resume input if this handler is canceled.
       StreamingHandle->BindCancelDelegate(FStreamableDelegate::CreateWeakLambda(this,
          [this, StateFunc, SuspendInputToken]()
          {
             UCommonUIExtensions::ResumeInputForPlayer(GetOwningPlayer(), SuspendInputToken);
             StateFunc(EAsyncWidgetLayerState::Canceled, nullptr);
          })
       );
    
       return StreamingHandle;
    }
    template <typename ActivatableWidgetT = UCommonActivatableWidget>
    ActivatableWidgetT* PushWidgetToLayerStack(FGameplayTag LayerName, UClass* ActivatableWidgetClass, TFunctionRef<void(ActivatableWidgetT&)> InitInstanceFunc)
    {
       static_assert(TIsDerivedFrom<ActivatableWidgetT, UCommonActivatableWidget>::IsDerived, "Only CommonActivatableWidgets can be used here");
    
       if (UCommonActivatableWidgetContainerBase* Layer = GetLayerWidget(LayerName))
       {
          return Layer->AddWidget<ActivatableWidgetT>(ActivatableWidgetClass, InitInstanceFunc);
       }
    
       return nullptr;
    }
    
    void UPrimaryGameLayout::RegisterLayer(FGameplayTag LayerTag, UCommonActivatableWidgetContainerBase* LayerWidget)
    {
       if (!IsDesignTime()){
          //过渡相关
          LayerWidget->OnTransitioningChanged.AddUObject(this, &UPrimaryGameLayout::OnWidgetStackTransitioning);
          LayerWidget->SetTransitionDuration(0.0);
          
          Layers.Add(LayerTag, LayerWidget);}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    bSuspendInputUntilComplete:在ui加载完成前暂停布局,防止玩家按下delete等误操作
    RegisterLayer注册新的LayerWidget容器到PrimaryWidget中,并将Tag作为Key
    在这里插入图片描述

    PushWidgetToLayerStackAsyncUCommonActivatableWidget异步载入后推入对应Tag的UCommonActivatableWidgetContainerBase容器(目前是Stack)中
    蓝图节点中与之对应的Task:PushContentTolayerForPlayer
    在这里插入图片描述

    由此,实现了UI的动态加载以及层级显示问题
    蓝图实例W_OverallUILayout
    仅用于确定层级和Layer注册
    在这里插入图片描述
    在这里插入图片描述

    四、Widget管理容器:UCommonActivatableWidgetContainerBase

    Widget基础容器、Layout层级管理来使用一个Container就是一层Layout,而不再使用优先级
    继承自UWidget,不参与布局与响应逻辑。

    所有参数:
    UPROPERTY(Transient)
    TArray<UCommonActivatableWidget*> WidgetList;
    UPROPERTY(Transient)
    FUserWidgetPool GeneratedWidgetsPool;
    UPROPERTY(Transient)
    UCommonActivatableWidget* DisplayedWidget;
    
    TSharedPtr<SCommonAnimatedSwitcher> MySwitcher;
    TSharedPtr<SOverlay> MyOverlay;
    TSharedPtr<SSpacer> MyInputGuard;
    
    UPROPERTY(EditAnywhere, Category = Transition)
    ECommonSwitcherTransition TransitionType;
    UPROPERTY(EditAnywhere, Category = Transition)
    ETransitionCurve TransitionCurveType;
    UPROPERTY(EditAnywhere, Category = Transition)
    float TransitionDuration = 0.4f;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    1. 容器WidgetListGeneratedWidgetsPoolDisplayedWidget
      WidgetList:存储激活的Widget
      GeneratedWidgetsPool:存储整个Widget池并负责Widget的动态创建与销毁,DisplayedWidgetMySwitcher确定当前顶层的Widget
    2. UI布局
      MySwitcher:储存并显示位于顶部的SWidget,其在变化时会更新DisplayedWidget为对应的Widget
      MyOverlay:UI的顶层布局
      MyInputGuard:布局的一部分,没有细看
    3. 过渡动画参数
      不表

    AddWidget

    template <typename ActivatableWidgetT = UCommonActivatableWidget>
    ActivatableWidgetT* AddWidget(TSubclassOf<UCommonActivatableWidget> ActivatableWidgetClass)
    {
       // Don't actually add the widget if the cast will fail
       if (ActivatableWidgetClass && ActivatableWidgetClass->IsChildOf<ActivatableWidgetT>()){
          return Cast<ActivatableWidgetT>(AddWidgetInternal(ActivatableWidgetClass, [](UCommonActivatableWidget&) {}));
       }
       return nullptr;
    }
    UCommonActivatableWidget* UCommonActivatableWidgetContainerBase::AddWidgetInternal(TSubclassOf<UCommonActivatableWidget> ActivatableWidgetClass, TFunctionRef<void(UCommonActivatableWidget&)> InitFunc)
    {
       if (UCommonActivatableWidget* WidgetInstance = GeneratedWidgetsPool.GetOrCreateInstance(ActivatableWidgetClass)){
          InitFunc(*WidgetInstance);
          RegisterInstanceInternal(*WidgetInstance);
          return WidgetInstance;
       }
       return nullptr;
    }
    void UCommonActivatableWidgetContainerBase::RegisterInstanceInternal(UCommonActivatableWidget& NewWidget)
    {
       if (ensure(!WidgetList.Contains(&NewWidget))){
          WidgetList.Add(&NewWidget);
          OnWidgetAddedToList(NewWidget);
    }}
    void UCommonActivatableWidgetStack::OnWidgetAddedToList(UCommonActivatableWidget& AddedWidget)
    {
      unimplamentation();
    }}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    首先创建(已有则略过)并添加Widget的实例到GeneratedWidgetsPool中,然后注册进WidgetList中。
    可以看到,GeneratedWidgetsPoolWidgetList都仅仅是用于存储Widget本体与其对应SWidget的容器,都无法控制容器中Widget的显示与隐藏。所以在这里Widget仅仅是进入了容器,但对Widget的进一步处理被设置为unimplamentation.
    GeneratedWidgetsPool仅仅传入的参数仅仅是Class,如果有多个实例的话不确定会返回哪一个,详情请看GeneratedWidgetsPool

    RemoveWidget

    void UCommonActivatableWidgetContainerBase::RemoveWidget(UCommonActivatableWidget& WidgetToRemove)
    {
       if (&WidgetToRemove == GetActiveWidget()){
          // To remove the active widget, just deactivate it (if it's already deactivated, then we're already in the process of ditching it)
          if (WidgetToRemove.IsActivated()){
             WidgetToRemove.DeactivateWidget();}
          else{
             bRemoveDisplayedWidgetPostTransition = true;
          }}
       else{
          // Otherwise if the widget isn't actually being shown right now, yank it right on out
          TSharedPtr<SWidget> CachedWidget = WidgetToRemove.GetCachedWidget();
          if (CachedWidget && MySwitcher){
             ReleaseWidget(CachedWidget.ToSharedRef());
          }}
    }
    void UCommonActivatableWidgetContainerBase::ReleaseWidget(const TSharedRef<SWidget>& WidgetToRelease)
    {
       if (UCommonActivatableWidget* ActivatableWidget = ActivatableWidgetFromSlate(WidgetToRelease)){
          GeneratedWidgetsPool.Release(ActivatableWidget, true);
          WidgetList.Remove(ActivatableWidget);}
       if (MySwitcher->RemoveSlot(WidgetToRelease) != INDEX_NONE){
          ReleasedWidgets.Add(WidgetToRelease);
          if (ReleasedWidgets.Num() == 1){
             FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateWeakLambda(this,
                [this](float){
                   QUICK_SCOPE_CYCLE_COUNTER(STAT_UCommonActivatableWidgetContainerBase_ReleaseWidget);
                   ReleasedWidgets.Reset();
                   return false;
                }));}}
    }
    void UCommonActivatableWidgetContainerBase::HandleActiveWidgetDeactivated(UCommonActivatableWidget* DeactivatedWidget)
    {
       if (ensure(DeactivatedWidget == DisplayedWidget) && MySwitcher && MySwitcher->GetActiveWidgetIndex() > 0){
          DisplayedWidget->OnDeactivated().RemoveAll(this);
          MySwitcher->TransitionToIndex(MySwitcher->GetActiveWidgetIndex() - 1);
       }
    }
    
    void UCommonActivatableWidgetContainerBase::HandleActiveIndexChanged(int32 ActiveWidgetIndex)
    {
       // 1
       while (MySwitcher->GetNumWidgets() - 1 > ActiveWidgetIndex){
          TSharedPtr<SWidget> WidgetToRelease = MySwitcher->GetWidget(MySwitcher->GetNumWidgets() - 1);
          if (ensure(WidgetToRelease)){
             ReleaseWidget(WidgetToRelease.ToSharedRef());
          }}
    
       //2. Also remove the widget that we just transitioned away from if desired
       if (DisplayedWidget && bRemoveDisplayedWidgetPostTransition)
       {
          if (TSharedPtr<SWidget> DisplayedSlateWidget = DisplayedWidget->GetCachedWidget())
          {
             ReleaseWidget(DisplayedSlateWidget.ToSharedRef());
          }
       }
       bRemoveDisplayedWidgetPostTransition = false;
    
       // Activate the widget that's now being displayed
       DisplayedWidget = ActivatableWidgetFromSlate(MySwitcher->GetActiveWidget());
       if (DisplayedWidget)
       {
          SetVisibility(ESlateVisibility::SelfHitTestInvisible);
    
          DisplayedWidget->OnDeactivated().AddUObject(this, &UCommonActivatableWidgetContainerBase::HandleActiveWidgetDeactivated, DisplayedWidget);
          DisplayedWidget->ActivateWidget();
    
          if (UWorld* MyWorld = GetWorld())
          {
             FTimerManager& TimerManager = MyWorld->GetTimerManager();
             TimerManager.SetTimerForNextTick(FSimpleDelegate::CreateWeakLambda(this, [this]() { InvalidateLayoutAndVolatility(); }));
          }
       }
       else
       {
          SetVisibility(ESlateVisibility::Collapsed);
       }
    
       OnDisplayedWidgetChanged().Broadcast(DisplayedWidget);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 如果是正在激活的,则根据是否是可激活执行取消激活或者标记关闭。否则进行Release,将Widget从GeneratedWidgetsPool和WidgetList移除,并从MySwitcher中进行移除,最后将该Widget对应的的SWidget扔进ReleasedWidgets中。
    • MySwitcher决定了Widget的显示层级。添加时并没有规定进入MySwitcher的方式,而不管什么方式进入MySwitcher的,移除的时候都一样。
      Release时将MySwitcher的ActiveIndex设置为其所在的下一层,而**TransitionToIndex()**会执行绑定的HandleActiveIndexChanged

    HandleActiveIndexChanged:

    1. 将当前激活Index之上的Widget都Deactive
    2. 如果需要的话我们也release刚刚显示的Widget?(需要查看别的地方)
    3. 激活现在在MySwitcher中激活的Widget。这里对DisplayedWidget进行了赋值

    ClearWidget

    void UCommonActivatableWidgetContainerBase::ClearWidgets()
    {
       SetSwitcherIndex(0);
    }
    void UCommonActivatableWidgetContainerBase::SetSwitcherIndex(int32 TargetIndex, bool bInstantTransition /*= false*/)
    {
    if (MySwitcher && MySwitcher->GetActiveWidgetIndex() != TargetIndex){
       if (DisplayedWidget){
          DisplayedWidget->OnDeactivated().RemoveAll(this);
          if (DisplayedWidget->IsActivated()){
             DisplayedWidget->DeactivateWidget();
          }
          else if (MySwitcher->GetActiveWidgetIndex() != 0)
          {
             // The displayed widget has already been deactivated by something other than us, so it should be removed from the container
             // We still need it to remain briefly though until we transition to the new index - then we can remove this entry's slot
             bRemoveDisplayedWidgetPostTransition = true;
          }
       }
    
       MySwitcher->TransitionToIndex(TargetIndex, bInstantTransition);
    }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    首先将正在激活的Widget关闭,然后设置MySwitcher的Index为0。

    GetActiveWidget

    UCommonActivatableWidget* UCommonActivatableWidgetContainerBase::GetActiveWidget() const
    {
       return MySwitcher ? ActivatableWidgetFromSlate(MySwitcher->GetActiveWidget()) : nullptr;
    }
    
    • 1
    • 2
    • 3
    • 4

    返回MySwitcherActiveWidget,而不是自己存储的DisplayedWidget
    可以看出显示的主导在于MySwitcher而不是另外两个容器

    UCommonActivatableWidgetStack

    将进入MySwitcher的方式设置为栈
    给栈提供了一个RootWidget

    UPROPERTY(EditAnywhere, Category = Content)
    TSubclassOf<UCommonActivatableWidget> RootContentWidgetClass;
    UPROPERTY(Transient)
    UCommonActivatableWidget* RootContentWidget;
    
    void UCommonActivatableWidgetStack::SynchronizeProperties()
    {
       Super::SynchronizeProperties();
       
    #if WITH_EDITOR
       if (IsDesignTime() && RootContentWidget && RootContentWidget->GetClass() != RootContentWidgetClass){
          // At design time, account for the possibility of the preview class changing
          if (RootContentWidget->GetCachedWidget()){
             MySwitcher->GetChildSlot(0)->DetachWidget();}
    
          RootContentWidget = nullptr;}
    #endif
    
       if (!RootContentWidget && RootContentWidgetClass){
          // Establish the root content as the blank 0th slot content
          RootContentWidget = CreateWidget<UCommonActivatableWidget>(this, RootContentWidgetClass);
          MySwitcher->GetChildSlot(0)->AttachWidget(RootContentWidget->TakeWidget());
          SetVisibility(ESlateVisibility::SelfHitTestInvisible);
       }
    }
    
    void UCommonActivatableWidgetStack::OnWidgetAddedToList(UCommonActivatableWidget& AddedWidget)
    {
       if (MySwitcher){
          MySwitcher->AddSlot() [AddedWidget.TakeWidget()];
          SetSwitcherIndex(MySwitcher->GetNumWidgets() - 1);
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    SynchronizeProperties
    编辑器下如果RootContentWidget与RootContentWidgetClass的类型不同,直接Detach,见英文注释
    如果当前还没有RootContentWidget则创建并放置到MySwitcher的首位
    OnWidgetAddedToList
    实现基类没有处理的添加到MySwitcher问题,直接将Widget放入,并设置SwitcherIndex为新放入的Widget

    UCommonActivatableWidgetQueue

    将Widget进入MySwitcher的方式设置为队列

    void UCommonActivatableWidgetQueue::OnWidgetAddedToList(UCommonActivatableWidget& AddedWidget)
    {
       if (MySwitcher){
          // Insert after the empty slot 0 and before the already queued widgets
          MySwitcher->AddSlot(1) [AddedWidget.TakeWidget()];
    
          if (MySwitcher->GetNumWidgets() == 2)
          {
             // The queue was empty, so we'll show this one immediately
             SetSwitcherIndex(1);
          }
          else if (DisplayedWidget && !DisplayedWidget->IsActivated() && MySwitcher->GetNumWidgets() == 3)
          {
             //The displayed widget is on its way out and we should not be going to 0 anymore, we should go to the new one
             SetSwitcherIndex(1, true); //and get there fast, we need to finish and evict the old widget before anything else happens
          }
    
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    将Widget插入到index1(index0一直为空),如果之前队列为空则直接显示,之前队列还存在一个但已经Deactive也可以直接显示

    总结:

    该容器一方面负责了Widget的创建与缓存,另一方面也使用Switch来对容器中UI的显示进行了控制。位于Switch顶层的UI则显示,保证了容器中UI是互斥的,而Pool保证了UI在被Switch移除且Deactive后不被销毁。
    而Widget加入时并没有确定进入Switch的方式,而将其放入了子类中实现。
    Stack与Queue分别定义了两种进入Switch的方式

    五、Widget池:FUserWidgetPool

    一个管理Widget创建与销毁的池,主要用Active和Deactive池来存储不同状态下的Widget
    细节以后再看

    六、用于管理Widget显示的UI:SCommonAnimatedSwitcher

    一个实现了过渡动画的SwitcherUI,不表

  • 相关阅读:
    使用 etcdadm 快速、弹性部署 etcd 集群
    29.vuex
    矩阵分析与应用+张贤达
    【nginx根据特定header值设置proxy_set_header】
    Java多态
    华为云HECS安装docker-compose
    CentOS 离线升级Linux的内核并删除多有内核
    pandas.eval()/pandas.Series()/lambda/itertools.product
    django建站过程(3)定义模型与管理页
    【颠覆旧知识】JS的原型链搜索原则;
  • 原文地址:https://blog.csdn.net/qq_40026009/article/details/125544396