MVVM 回顧
經過上一篇文章的介紹,相信你對MVVM的設計思想有所瞭解。MVVM的核心思想就是解耦,View與ViewModel應該感受不到彼此的存在。View只關心怎樣渲染,而ViewModel只關心怎麼處理邏輯,整個架構由資料進行驅動。不僅View與ViewModel彼此解耦,ViewModel與ViewModel之間也是解耦的,通過訊息訂閱-釋出機制,解決了ViewModel之間的強依賴關係。
先回顧一下我們已完成的功能,框架中最核心就是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上,點選此瞭解
歡迎關注我的公眾號: