關於Xamarin、Qml、數據綁定、MVC、MVVM 相關的散講 SURFSKY 2017.02 最近又在學習Xamarin了?爲何是「又」?有幾個利好消息,讓我從新拾起它: (1)微軟去年收購了Xamarin,並且免費。原先的費用會嚇死人,並且按人頭+平臺來收費。 (2)Xamarin.Forms 的出現,UI層也能夠複用了,不像原先只能邏輯層代碼複用。 (3)IDE加強: Xamarin Forms Previewer,能夠直接預覽 XAML 的外觀。 XAML 智能感應的加強,如今編寫XAML舒服多了。原先編寫XAML可不是件輕鬆的事情,因此以前的教材所有都是用C#寫的UI,這很不利於視圖和邏輯的分離。 Visual Studio for mac 發佈,可替代原先的 Xamarin Studio for mac。畢竟,在mac下編寫ios/Android app不管性能仍是便利性都要比在windows上好得多。 不愧是宇宙最強IDE開發商,一接手Xamarin,編輯器就加強了:) (4)真正的一個語言能夠編寫跨平臺移動app,包括後臺、ios、android、windows uwp 等 數據接口和後臺:用 asp.net 寫便可 移動客戶端API:Xamarin 已用C#封裝好,若是須要平臺專用功能,直接調用便可。 這很利於節省成本、積累技術、簡化員工招招聘和培訓。 (5)性能 對於ios平臺,xamarin是將c#編譯成原生的代碼。android是嵌入了.NET運行時。 實測試了下,ios下沒有任何問題,3秒左右進入主界面,進去後性能就和原生的沒有區別了 android的啓動略慢,估計要5秒,進去後性能沒有問題。 加個歡迎屏能夠稍微減緩客戶等待的焦慮感。 固然還有缺陷和指望 缺陷 Xamarin.Forms 的控件是轉化爲各平臺原生控件的,也就是說,這些控件在各平臺上外觀都有區別! IED 複雜的 XAML previewer展現不出來。 沒法拖拽控件快速設計頁面。 VS MAC 有時候屏幕刷新不過來,一片白。 常常出現「不是在活動配置中生成的項目」,沒法加載項目。 啓動速度仍是慢了點,位於可容忍的邊緣。 沒有官方的、統一的移動平臺功能 API 庫。 相似PhoneGap那樣的,訪問攝像頭、傳感器等API,一套代碼各平臺通用。 現有的這些功能API是分散在各平臺本身的庫裏的,學習一個統一庫與學習多平臺類庫,工做量固然不同。 早些年有Xamarin.Mobile項目,但與Xamarin.Forms不兼容已經廢棄了。 如今這些功能API插件處於散亂瑣碎狀態,要本身一個個去Xamarin Compoents/GitHub上找。 指望 不知何時能夠直接拖拽 Xamarin Forms 控件,一個平臺也行啊。 加快 Xamarin Form Android app 啓動速度。 須要一套外觀一致的 Xamarin Forms UI 庫,但短時間內不會有,要咱們直接寫了(*) 官方推出統一的跨平臺移動功能 API 庫:Xamarin.Forms.Mobile(*) 備註 固然 Visual Studio for mac 仍是預覽版本,等正式版估計BUG會少一些。 (*)打算啓動這兩個項目,歡迎有共同志向的同窗參與。 相比較 Qt 性能仍是Qt佔優的,畢竟底層是C++寫的。但Xamarin的性能也在可容忍範圍內,主要是啓動時間稍慢了點。 APP 大小。 都在20M以上, Qt V-Play apps 基於Qt的遊戲引擎 52.4M 新發布的 Qt 5.8聽說能夠裁剪庫的大小,將APP壓縮到四、5M內 Xamarin 我也搞不清楚了。 Xuni Explorer 只有 5.6 M Evlove 16 有46.1M 隨便編譯個debug版本的,估計有60多M,release的還沒試驗 雖說理論上 Qml 能夠擺平一切界面問題,但咱們不可避免的要涉及到各平臺的插件,那麼你真正須要學習的語言有: QML : Javascript : C++ : Qt類庫 Object-C : Android : ps.真但願Qml能發展成獨立的開發語言(參照 swift) 強類型 可編譯 簡化建立組件語法,直接用new MyItem(),而不是複雜的Qt.CreateComponent..... 摒棄js、v4引擎,提供工具將js遷移爲標準Qml 獨立發展 Qml 類庫 更便利的方式訪問c++類庫,相似swift那樣,只需Import便可使用 綁定的前世此生 若要監控一個對象的若干屬性變化狀況,傳統寫法是爲每一個要監控的屬性加上一個值變動事件,而後讓調用者添加這個事件 public event Event FirstNameChanged; public string FirstName {get; set;} sample.FirstNameChanged += (o, value)=>txtbox.Text = value; 這樣極可能要寫不少的屬性變動事件,並且不能自動給訂購者賦值 什麼是綁定?它事實上作了兩件事 (1)監聽對象指定屬性,若變化,則通知給訂購者 (2)給訂購者賦值 微軟給出的官方方案是 (1)綁定系統維護了一個表格(在綁定表達式中指定),字段大體包括: object SourceObject : 監聽對象 string SourcePropertyName : 監聽對象的屬性名稱 object TargetObject : 訂購者 string ValuePath : 賦值路徑 (2)讓數據實現INotifyPropertyChanged接口 public event PropertyChangedEventHandler PropertyChanged; 若監聽對象的屬性有變更,則觸發這個事件,通知綁定系統 這個事件的參數中包含了監聽對象和變動的屬性名稱(SourceObject + SourcePropertyName) (3)綁定系統去查表,給訂購者進行賦值 TargetObject = SourceObject.SourcePropertyName.ValuePath 屬性變動通知 INotifyPropertyChanged接口(屬性變動的時候通知下綁定系統,個人哪一個屬性變動了,快處理啊) class Sample : INotifyPropertyChanged { // INotifyPropertyChanged 接口成員 public event PropertyChangedEventHandler PropertyChanged; // 屬性 private string firstName; public string FirstName { get { return firstName; } set { firstName = value; if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("FirstName")); } } } // 綁定(textBox.Text <= sourceObject.FirstName) Sample sourceObject = new Sample(); textbox.DataBindings.Add("Text", sourceObject, "FirstName"); sourceObject.FirstName = "Stack"; 咱們作一下封裝,加上值相等判斷。 定義 ViewModelBase using System; using System.ComponentModel; using System.Runtime.CompilerServices; namespace Xamarin.FormsBook.Toolkit { public class ViewModelBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected bool SetProperty<T>(ref T storage, T value, string propertyName = null) { if (Object.Equals(storage, value)) return false; storage = value; if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); return true; } } } 讓數據模型類繼承自ViewModelBase public class InformationViewModel : ViewModelBase { string name; public string Name { get { return name; } set { if (SetProperty(ref name, value, "Name")) { ...屬性已經更新,該乾點啥 }; } } } Xamarin 的方案(使用 BindableObject) 代碼 public RadioButtonGroup : BindableObject { public static readonly BindableProperty TextProperty = BindableProperty.Create("Text", typeof(string), typeof(Radio), "", propertyChanged: TextChanged); private static void TextChanged(BindableObject bindable, object oldValue, object newValue) { var radio = (Radio)bindable; radio.Lbl.Text = (string)newValue; } public string Text { get {return (string)this.GetValue(TextProperty);} set {this.SetValue(TextProperty, value);} } } this.Icon.SetBinding(Image.IsVisibleProperty, new Binding("ShowRadio", source: this)); 說明 這裏的BindableProperty是用於描述這個屬性的元信息,如名稱、類型、默認值、綁定方向、屬性值變動事件 BindableObject 是 INotifyPropertyChanged 的封裝,增長了綁定上下文、屬性變動中、屬性變動完畢事件等邏輯 public abstract class BindableObject : INotifyPropertyChanged, IDynamicResourceHandler { public static readonly BindableProperty BindingContextProperty; public object BindingContext { get; set; } protected BindableObject (); // Methods protected void ApplyBindings (object oldContext = null); public void ClearValue (BindableProperty property); public void ClearValue (BindablePropertyKey propertyKey); public object GetValue (BindableProperty property); protected virtual void OnBindingContextChanged (); protected virtual void OnPropertyChanged ([CallerMemberName] string propertyName = null); protected virtual void OnPropertyChanging ([CallerMemberName] string propertyName = null); public void RemoveBinding (BindableProperty property); public void SetBinding (BindableProperty targetProperty, BindingBase binding); public void SetValue (BindablePropertyKey propertyKey, object value); public void SetValue (BindableProperty property, object value); protected void UnapplyBindings (); // Events public event EventHandler BindingContextChanged { add; remove;} public event PropertyChangedEventHandler PropertyChanged {add; remove;} public event PropertyChangingEventHandler PropertyChanging {add; remove;} } 單向數據綁定和雙向數據綁定 單向:model的變動會刷新view,view上作任何操做都不影響model 雙向:model的變動會刷新view,view的變動會刷新model 我的只喜歡用單向的,理由呢: 喜歡實打實的代碼來控制數據刷新,雙向的數據綁定總以爲不放心 view的交互,經常帶了邏輯控制,不只僅是給model賦值,這不是雙向數據綁定能解決的。 其它 Qt QML的屬性、綁定、屬性變動事件實現起來很是優美和簡單 A { property int Property1; // 編譯器會自動生成相似如下方法 // bool onProperty1Changing(o, oldValue, newValue){...} // void onProperty1Changed(o, oldValue, newValue) {....} } b.Property2 = a.Property1; // 綁定這兩個對象的屬性(單向綁定) 建議 C# 優化編譯器,或者提供語法糖之類的東西(如propety關鍵字) public readonly property string Name; 編譯器自動實現: getter、setter INotifyPropertyChanged 接口 PropertyChangingEvent PropertyChangedEvent 綁定邏輯 事實上,這個功能10來年前的 Delphi 就實現了 什麼是 MVC 這是將界面、邏輯、數據隔離的一種方案,這其實也是綁定的一種實現,不過是頁面級別的。 Model : 數據 View : 視圖 Controller : 控制器 視圖的呈現由控制器控制,視圖的數據(模型)也由控制器提供 TODO: 補一個簡單的示例 ASP.NET MVC 是一個controller對應多個view 它的controller主要就是個跳轉器,根據客戶端請求,組裝數據並提供給某個視圖,渲染到客戶端 疑問: 大的頁面用controller跳轉好理解 但小的交互,如按鈕點擊頁面上的某個label變個文字,這個也交給controll是否是太瑣碎了? 這個仍是交給頁面客戶端本身處理會更合適,但這要寫js了 其實傳統的 Windows Form 也能夠看爲一種MVC View部分可由設計器建立 View的數據展現、交互(如點擊事件)在後臺代碼中,這個能夠當作Controller 不過這種方式是一個view對應一個controller,這個controller是這個view專用的 ios-objectc 的MVC 這貨和 Windows Form 差很少,也是一個視圖對應一個專用的控制器 掛MVC的頭銜,行Windows Form的行當。 什麼是 MVVM 這是將界面和數據徹底隔離的一種方案 Model : 模型 View : 視圖 ViewModel : 視圖模型 傳統的頁面數據展現,能夠是混雜的,既有數據綁定,也能夠摻雜代碼指定。如: TODO:來段 MVVM 的示例代碼 能夠看出,MVVM是一種更極端的、界面和數據徹底隔離的方案: 它定義了一個叫 ViewModel 的東西,這個也是一種Model,但專用於視圖 視圖的賦值只能用 ViewModel,不能用其它方式 咱們必須爲每一個 View 定製一個 ViewModel。 評價 好處:徹底隔離視圖和數據、只要約定好數據接口(ViewModel)就行 壞處:必須爲每一個視圖都定製 ViewModel,這會增長代碼量 ps 2017-05 出了個 Xamarin Live Player 的東東,能夠在手機上展現程序運行效果 android版本的能夠直接下載,ios版本的還在testflight中 https://docs.microsoft.com/zh-cn/xamarin/tools/live-player/