對於一個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圖如下所示:
- 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上,點擊此了解