MVVM回顧###
經過上一篇文章的介紹,相信你對MVVM的設計思想有所了解。MVVM的核心思想就是解耦,View與ViewModel應該感受不到彼此的存在。
View只關心怎樣渲染,而ViewModel只關心怎么處理邏輯,整個架構由數據進行驅動。不僅View與ViewModel彼此解耦,ViewModel與ViewModel之間也是解耦的。
通過消息訂閱-發布機制,解決了ViewModel之間的強依賴關系。
先回顧一下我們已完成的功能,Framework中最核心就是BindableProperty 類,ViewModel 中所有需要被綁定到UI 控件的屬性必須是一個BindableProperty 對象。它是一個職責非常單一的類,監聽Value的數值是否發生變化,當變化時,觸發OnValueChanged 事件,通知View 做出相應的更新。
public class BindableProperty<T>
{
public delegate void ValueChangedHandler(T oldValue, T newValue);
public ValueChangedHandler OnValueChanged;
private T _value;
public T Value
{
get
{
return _value;
}
set
{
if (!object.Equals(_value, value))
{
T old = _value;
_value = value;
ValueChanged(old, _value);
}
}
}
private void ValueChanged(T oldValue, T newValue)
{
if (OnValueChanged != null)
{
OnValueChanged(oldValue, newValue);
}
}
}
那問題來了,View在何時並以怎樣的方式去監聽這些屬性的變化呢?
BindableProperty是一個很好的設計,它不僅可以用在ViewModel中,還可以用在View中,用它來修飾 ViewModel,當ViewModel 改變時,比如初始化時,或者從一個ViewModel變化到另一個ViewModel對象時,在觸發的OnBindingContextChanged 事件中實現對ViewModel中的屬性監聽。如下定義的抽象父類:UnityGuiView
public readonly BindableProperty<ViewModel> ViewModelProperty = new BindableProperty<ViewModel>();
public ViewModel BindingContext
{
get { return ViewModelProperty.Value; }
set { ViewModelProperty.Value = value; }
}
protected virtual void OnBindingContextChanged(ViewModel oldViewModel, ViewModel newViewModel)
{
}
public UnityGuiView()
{
this.ViewModelProperty.OnValueChanged += OnBindingContextChanged;
}
子類SetupView繼承自UnityGuiView,並且Override OnBindingContextChanged,並實現對ViewModel中的屬性監聽。
protected override void OnBindingContextChanged(ViewModel oldViewModel, ViewModel newViewModel)
{
base.OnBindingContextChanged(oldViewModel, newViewModel);
SetupViewModel oldVm = oldViewModel as SetupViewModel;
if (oldVm != null)
{
oldVm.Name.OnValueChanged -= NameValueChanged;
...
}
if (ViewModel!=null)
{
ViewModel.Name.OnValueChanged += NameValueChanged;
...
}
}
進一步抽象###
實際上對於ViewModel而言會有非常多的BindableProperty需要被綁定到UI控件中,從代碼的可讀性而言,如下代碼是非常沉長和啰嗦的:
if (oldVm != null)
{
oldVm.Name.OnValueChanged -= NameValueChanged;
...
}
if (ViewModel!=null)
{
ViewModel.Name.OnValueChanged += NameValueChanged;
...
}
因為+=和-=是成對出現的,所以只要是看到 OnValueChanged,這部份代碼的長度幾乎都是*2。
仔細觀察一下,每個View都會出現 具體的 ViewModel.屬性.OnValueChanged事件+=或者-=具體的處理函數 這樣的固定模板。
那么是否可以將這部分代碼抽象到一個公共類中呢,並且暴露出一個簡單的方法提供給View來初始化這些OnValueChanged事件,比如:
PropertyBindingUtils.Init<string>("Color",OnColorPropertyValueChanged);
然后在Init方法中+=或者-=具體的處理函數。
當然是可以得,定義一個PropertyBinder屬性綁定器,通過反射技術,動態為屬性+=或者-= OnValueChanged 事件,腦海里的 Raw 代碼如下
Init<TProperty>(string propertyName ,OnValueChanged valueChangedHandler)
{
var fieldInfo = typeof(TViewModel).GetField(propertyName, BindingFlags.Instance | BindingFlags.Public);
var value = fieldInfo.GetValue(viewModel);
BindableProperty<TProperty> bindableProperty = value as BindableProperty<TProperty>;
bindableProperty.OnValueChanged += valueChangedHandler;
bindableProperty.OnValueChanged -= valueChangedHandler;
}
最核心的代碼就那么幾步,詳細代碼可以查看源代碼PropertyBinder的實現。
重構視圖基類:UnityGuiView###
想象一下PropertyBinder應該放在哪兒。
它是用來監聽ViewModel中的屬性值變化的,用來替換沉長的 oldVm.Property.OnValueChanged +=和-= NameValueChanged,理所應當應該放在View中,因為每個View都需要,故將它定義在UnityGuiView 中。
又因為PropertyBinder需要知道為哪個ViewModel進行服務(因為需要反射),故通過泛型來約束 UnityGuiView< T >:IView where T:ViewModelBase 。
再對BindingContext稍作改變,當它被賦值時,只初始化一次對OnValueChanged事件的監聽(原先是放在構造函數里)。
public readonly BindableProperty<ViewModelBase> ViewModelProperty = new BindableProperty<ViewModelBase>();
public ViewModelBase BindingContext
{
get { return ViewModelProperty.Value; }
set
{
if (!_isBindingContextInitialized)
{
OnInitialize();
_isBindingContextInitialized = true;
}
//觸發OnValueChanged事件
ViewModelProperty.Value = value;
}
}
/// <summary>
/// 初始化View,當BindingContext改變時執行
/// </summary>
protected virtual void OnInitialize()
{
//無所ViewModel的Value怎樣變化,只對OnValueChanged事件監聽(綁定)一次
ViewModelProperty.OnValueChanged += OnBindingContextChanged;
}
值得注意的事,我定義了一個virtual的OnInitialize,這樣子類可以override它從而實現一些初始化方法,比如:
protected override void OnInitialize()
{
base.OnInitialize();
Binder.Add<string>("Color",OnColorPropertyValueChanged);
}
private void OnColorPropertyValueChanged(string oldValue, string newValue)
{
switch (newValue)
{
case "Red":
buttonImage.color = Color.red;
break;
case "Yellow":
buttonImage.color = Color.yellow;
break;
default:
break;
}
}
小節###
這篇博客基本上是回顧了MVVM模式在Unity 3D上的實踐,結合自己的開發經驗,通過反射的技術可以有效減少沉長的代碼。
源代碼托管在Github上,點擊此了解