1. 程式人生 > >Unity應用架構設計(6)——設計動態資料集合ObservableList

Unity應用架構設計(6)——設計動態資料集合ObservableList

什麼是 『動態資料集合』 ?簡而言之,就是當集合新增、刪除專案或者重置時,能提供一種通知機制,告訴UI動態更新介面。有經驗的程式設計師腦海裡迸出的第一個詞就是 ObservableCollection。沒錯,它在WPF中盛行其道,通過它開發者可以很方便的達到動態更新介面。要在Unity 3D中使用ObservableCollection還是有些許困難的,因為Mono並不提供ObservableCollection類。但實際上,自己動手去構建一個『動態資料集合』也非難事,核心在於怎樣去傳播通知。這也是本篇部落格的主題。

實現自定義的ObservableList

既然核心在於構建通知機制,談到『通知』兩字,最常見的形式就是以委託或者事件形式將訊息廣播給監聽者。遺憾的是,.NET中常見的集合資料結構List並不支援事件的通知。所以我在自定義的ObservableList中增加OnAdd,OnRemove,OnInsert事件,當集合新增或者刪除項時,能廣播通知給客戶端UI介面。

以下圖為例,當點選+時,『以資料驅動介面的形式』,動態的去更新UI介面:

既然要以資料來驅動介面,首先我們需要定義能存放資料的集合,它就是ObservableList,並且是實現了IList 介面:

public class ObservableList<T>:IList<T>
{
    //...省略部分程式碼...
    private List<T> _value=new List<T>();

    public delegate void AddHandler(T instance);
    public AddHandler OnAdd;

    public delegate void InsertHandler(int index,T instance);
    public InsertHandler OnInsert;

    public delegate void RemoveHandler(T instance);
    public RemoveHandler OnRemove;

    public void Add(T item)
    {
        _value.Add(item);
        if (OnAdd != null)
        {
            OnAdd(item);
        }
    }

    public bool Remove(T item)
    {
        if (_value.Remove(item))
        {
            if (OnRemove != null)
            {
                OnRemove(item);
            }
            return true;
        }
        return false;
    }

    public void Insert(int index, T item)
    {
        _value.Insert(index,item);
        if (OnInsert!=null)
        {
            OnInsert(index, item);
        }
    }

}

可以看到,自定義的ObservableList實現了 IList 介面,並以泛型的形式約束了資料項型別。當新增或者刪除項時,提供了以事件的形式告訴客戶端UI介面 ,作為觀察者的UI可以順勢做出相應的更新。

岔開話題說一下,為什麼要用泛型,這是幾天前有同學在群裡問的?

  • 好處1:可以約束資料項的型別,讓我們不用每時每刻去強轉。比如你往ArrayList中添加了若干資料,因為ArrayList的資料項Item是萬能的object,所以你每次取出來都需要將object轉為你想要的物件,麻煩。
  • 好處2:減少執行時錯誤,因為是資料項是object,所以在編譯時你可以將其強轉為任何型別,但萬一這個object實際是Datetime型別,但你強轉為int,編譯時是沒問題的,但一執行就報錯,泛型約束能有效減少這種情況

完善ObservableList

到目前為止,我們自定義的動態資料集合ObservableList是非常好的設計,但唯一不足的事,它不能支援初始化時通知UI介面更新。 『初始化』 這詞可能有點太術語了,我翻譯一下就是一般初始化一個List,我們都是像如下方式進行:

public ObservableList<FaceBox> DataSource = new ObservableList<FaceBox>
{
    new FaceBox
    {
        Name = "Eyes",
        Level = 10,
        Face = "Avatar201_Face",
        Badge = new Badge {Icon = "Icon_WeaponRod", ElementColor = "1CB9FFFF"}
    },
    new FaceBox
    {
        Name = "Jack",
        Level = 8,
        Face = "Avatar202_Face",
        Badge = new Badge {Icon = "Icon_WeaponSpear", ElementColor = "FF5821FF"}
    }
}; 

顯然這即沒有觸發OnAdd,也沒有觸發OnRemove等事件,那麼初始化或者重置列表時,UI介面還是得不到更新。那我們怎麼去解決呢?還記得第一章中BindableProperty嗎?對了,解決方案就是它,對列表的初始化或者重置就是對Value進行改變。而BindableProperty內部提供了對Value值改變的監聽,一旦Value改變了,將訊息廣播出去。

OK,我們增強一下ObservableList:

public class ObservableList<T>:IList<T>
{
    //省略部分程式碼...
    public delegate void ValueChangedHandler(List<T> oldValue, List<T> newValue);
    public ValueChangedHandler OnValueChanged;
   
    //預先初始化,內建的List,防止空異常
    private List<T> _value=new List<T>();
    public List<T> Value
    {
        get { return _value; }
        set
        {
            if (!Equals(_value, value))
            {
                var old = _value;
                _value = value;

                ValueChanged(old, _value);
            }
        }
    }


    private void ValueChanged(List<T> oldValue, List<T> newValue)
    {
        if (OnValueChanged != null)
        {
            OnValueChanged(oldValue, newValue);
        }
    }

}

所以客戶端UI介面只要對ObservableList的OnValueChanged事件進行監聽,當初始化或者重置時,你也可以得到更新,演示效果如下:

小結

自定義的動態資料集合ObservableList看起來小巧,但五臟俱全,能提供通知機制,可以動態的去更新UI介面。 所有的一切都以資料的改變來驅動UI,這是非常重要的轉變。所以看似程式碼複雜了,但實際上你只要關心資料即可。
原始碼託管在Github上,點選此瞭解