Unity应用架构设计(3)——构建View和ViewModel的生命周期

时间:2022-03-28 19:33:30

对于一个View而言,本质上是一个MonoBehaviour。它本身就具备生命周期这个概念,比如,Awake,Start,Update,OnDestory等。这些是非常好的方法,可以让开发者在各个阶段去执行自定义的代码。但唯一遗憾的事,这些方法是有引擎调用,并且颗粒度不够细。本文将谈谈怎样构建View和ViewModel的生命周期。

View的生命周期

举个栗子,一个View的显示会有如下过程:

  • 初始化操作
  • 激活当前对象,SetActive(true)
  • 显示当前对象,包括localScale=Vector3.one,并且alpha从0->1
  • 当View显示之后,执行某些callBack方法,OnCompleted或者OnSuccess

再举个栗子,一个View隐藏会有如下过程:

  • 隐藏当前对象,包括localScale=Vector3.zero,并且alpha从1->0
  • 当View隐藏之后,执行某些callBack方法,OnCompleted或者OnSuccess
  • 不激活当前对象,SetActive(false)
  • Destory 当前对象时的处理方法

ViewModel的生命周期

对于View而言,它并不处理复杂的业务逻辑,View只负责显示。比如在哪个阶段去数据库或者其他地方去拿数据,这不归View来处理。这理所应当交给ViewModel去处理,ViewModel只要知道View什么阶段让我去拿数据即可。

所以对应的ViewModel也有生命周期,它对应了View的生命周期,ViewModel的生命周期包括:

  • 初始化操作
  • View在显示前处理的逻辑
  • View在显示后时处理的逻辑
  • View在隐藏前处理的逻辑
  • View在隐藏后处理的逻辑
  • View被销毁时应该处理的逻辑

构建生命周期

有了上述的分析之后,就需要落实,如何去构建View和ViewModel的生命周期了。

Overview图如下所示:

Unity应用架构设计(3)——构建View和ViewModel的生命周期

  • OnInitialize:用来初始化View。结合前几篇文章,OnInitialize 用来注册 OnBindingContextChanged 事件以及属性绑定(Binder.Add)
  • OnAppear:用来激活View
  • OnReveal:用来显示View,比如以动画形式(Fade)显示呢还是直接显示
  • OnRevealed:当View显示完毕时,执行的额外操作,是一个委托(Action)
  • OnHide:开始隐藏View
  • OnHidden:同OnReveal一样,可以以动画形式慢慢隐藏或者直接隐藏
  • OnDisappear:隐藏完毕后SetActive(false)不激活当前对象
  • OnDestory:当View被Detory时自动调用OnDestory方法

将这些方法放入UnityGuiView基类中:

[RequireComponent(typeof(CanvasGroup))]
public abstract class UnityGuiView<T>:MonoBehaviour,IView<T> where T:ViewModelBase
{
private bool _isInitialized;
public bool destroyOnHide;
protected readonly PropertyBinder<T> Binder=new PropertyBinder<T>();
public readonly BindableProperty<T> ViewModelProperty = new BindableProperty<T>();
/// <summary>
/// 显示之后的回掉函数
/// </summary>
public Action RevealedAction { get; set; }
/// <summary>
/// 隐藏之后的回掉函数
/// </summary>
public Action HiddenAction { get; set; } public T BindingContext
{
get { return ViewModelProperty.Value; }
set
{
if (!_isInitialized)
{
OnInitialize();
_isInitialized = true;
}
//触发OnValueChanged事件
ViewModelProperty.Value = value;
}
} public void Reveal(bool immediate = false, Action action = null)
{
if (action!=null)
{
RevealedAction += action;
}
OnAppear();
OnReveal(immediate);
OnRevealed();
} public void Hide(bool immediate = false, Action action = null)
{
if (action!=null)
{
HiddenAction += action;
}
OnHide(immediate);
OnHidden();
OnDisappear();
} /// <summary>
/// 初始化View,当BindingContext改变时执行
/// </summary>
protected virtual void OnInitialize()
{
//无所ViewModel的Value怎样变化,只对OnValueChanged事件监听(绑定)一次
ViewModelProperty.OnValueChanged += OnBindingContextChanged;
} /// <summary>
/// 激活gameObject,Disable->Enable
/// </summary>
public virtual void OnAppear()
{
gameObject.SetActive(true);
BindingContext.OnStartReveal();
}
/// <summary>
/// 开始显示
/// </summary>
/// <param name="immediate"></param>
private void OnReveal(bool immediate)
{
if (immediate)
{
//立即显示
transform.localScale = Vector3.one;
GetComponent<CanvasGroup>().alpha = 1;
}
else
{
StartAnimatedReveal();
}
}
/// <summary>
/// alpha 0->1 之后执行
/// </summary>
public virtual void OnRevealed()
{
BindingContext.OnFinishReveal();
//回掉函数
if (RevealedAction!=null)
{
RevealedAction();
}
} private void OnHide(bool immediate)
{
BindingContext.OnStartHide();
if (immediate)
{
//立即隐藏
transform.localScale = Vector3.zero;
GetComponent<CanvasGroup>().alpha = 0;
}
else
{
StartAnimatedHide();
}
}
/// <summary>
/// alpha 1->0时
/// </summary>
public virtual void OnHidden()
{
//回掉函数
if (HiddenAction!=null)
{
HiddenAction();
}
}
/// <summary>
/// 消失 Enable->Disable
/// </summary>
public virtual void OnDisappear()
{
gameObject.SetActive(false);
BindingContext.OnFinishHide();
if (destroyOnHide)
{
//销毁
Destroy(this.gameObject);
} }
/// <summary>
/// 当gameObject将被销毁时,这个方法被调用
/// </summary>
public virtual void OnDestroy()
{
if (BindingContext.IsRevealed)
{
Hide(true);
}
BindingContext.OnDestory();
BindingContext = null;
ViewModelProperty.OnValueChanged = null;
} /// <summary>
/// scale:1,alpha:1
/// </summary>
protected virtual void StartAnimatedReveal()
{
var canvasGroup = GetComponent<CanvasGroup>();
canvasGroup.interactable = false;
transform.localScale = Vector3.one; canvasGroup.DOFade(1, 0.2f).SetDelay(0.2f).OnComplete(() =>
{
canvasGroup.interactable = true;
});
}
/// <summary>
/// alpha:0,scale:0
/// </summary>
protected virtual void StartAnimatedHide()
{
var canvasGroup = GetComponent<CanvasGroup>();
canvasGroup.interactable = false;
canvasGroup.DOFade(0, 0.2f).SetDelay(0.2f).OnComplete(() =>
{
transform.localScale = Vector3.zero;
canvasGroup.interactable = true;
});
}
/// <summary>
/// 绑定的上下文发生改变时的响应方法
/// 利用反射+=/-=OnValuePropertyChanged
/// </summary>
private void OnBindingContextChanged(T oldValue, T newValue)
{
Binder.Unbind(oldValue);
Binder.Bind(newValue);
}
}

而ViewMode中就现对而言比较简单了,处理View处理不了的逻辑:

public virtual void OnStartReveal()
{
IsRevealInProgress = true;
//在开始显示的时候进行初始化操作
if (!_isInitialized)
{
OnInitialize();
_isInitialized = true;
}
} public virtual void OnFinishReveal()
{
IsRevealInProgress = false;
IsRevealed = true;
} public virtual void OnStartHide()
{
IsHideInProgress = true; } public virtual void OnFinishHide()
{
IsHideInProgress = false;
IsRevealed = false;
} public virtual void OnDestory()
{ }

值得注意的事,以上不管是View还是ViewModel与生命周期相关的方法,都是虚方法(virtual),这就意味这子类可以Override掉。比如某些场景下需要将View从左边或者右边移入,可以在初始化时指定偏移距离。又或者不想用默认的DoTween特效,你也可以完全Override并使用Animation等。

小结

本文介绍了怎样为View/ViewModel构建自定义的生命周期,MonoBehaviour 虽然有自己的生命周期,但不够细腻,我们完全可以扩展自己的生命周期,实现对需求的定制。

源代码托管在Github上,点击此了解