1. 程式人生 > >Unity應用架構設計(3)——構建View和ViewModel的生命週期

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上,點選此瞭解