Unity應用架構設計(3)——構建View和ViewModel的生命週期
對於一個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上,點選此瞭解