概述
Xamarin這個使用mono和.net core的跨平台開發框架這幾年在不斷發展。被微軟收購后的Xamarin為個人開發者提供了免費版的Xamarin for Visual Studio,吸引了更多開發人員的關注。
Xamarin.Forms用起來比較方便,因為用這種方式編寫一次就能到處運行。但是不知道為什么,Xamarin目前只完整支持C#。他們宣稱支持的F#實際上只是比C#多了一些文檔和不常用的工具上的支持,缺少Xaml后代碼生成器等重要功能支持。
而VB就更受冷落了。用戶建議Xamarin支持VB的票數截止這篇博客發布已經有397票(查看投票),但是Xamarin並沒有明確表示是否為VB提供支持。
幸運的是,Xamarin並沒有把習慣使用VB的開發人員的路堵死。在Xamarin的GitHub示例庫中藏着幾個VB代碼文件,教程區一個不起眼的角落里有關於在Xamarin使用VB代碼的教程。教程和示例文件都是用C#建立Xamarin.Forms項目,然后把C#可移植類庫替換成VB可移植類庫就可以用VB寫共用的UI代碼和業務邏輯代碼。缺陷是不能使用Xaml文件產生所需的代碼。
現在,Xamarin缺的VB開發實戰教程由我來填補。
第一步,新建項目
按照Xamarin.Forms的教程,先新建C# Xamarin.Forms 可移植 項目。然后把C#可移植類庫刪掉,用相同的目標,相同的項目名稱和命名空間新建一個VB可移植項目替代它。重新在C#項目添加對可移植類庫的引用。如果需要,在VB項目的默認導入添加Xamarin.Forms。
第二步,更新Nuget包
使用Nuget軟件包管理器對每個項目的Xamarin.Forms包進行更新。如果忽略這一步,會導致運行時錯誤。
第三步,建立App.vb文件
默認的模板會創建Class1.vb文件。把它重命名為App.vb,然后填充以下內容:
Public Class App Inherits Application Sub New() MainPage = New NavigationPage(New MainPage) End Sub End Class
使用 NavigationPage 可以方便頁面間的導航,當然也可以不用它。
插入這段代碼后,Intellisense會提示你創建 MainPage 類。點擊在新文件中創建它。
第四步,建立軟件中的頁面
假設第一個頁面是 ContentPage。把自動生成的繼承語句改一下。
Public Class MainPage Inherits ContentPage End Class
既然Xamarin不讓在VB中用Xaml,那我們就不要用類似於UWP的開發方式了。讓我們回歸原生態,尋找十年前用Winform打代碼的感覺。
新建一個名為 MainPage.Designer.vb 的文件。
像當年一樣,這個文件中的 MainPage 類 用 Partial 標記
Option Strict On Partial Public Class MainPage End Class
接下來要做什么想必有讀者可以猜到。寫 構造函數 和 InitializeComponents 方法。
Protected Sub InitializeComponents() End Sub Sub New() InitializeComponents() End Sub
假設我們的頁面頂部要放一個類似於Windows商店的控件,下面放置一組介紹用的橫幅。
首先,下面的東西多的話頁面可能放不下。所以需要一個滾動顯示框。里面的內容是依次向下排列的。最頂上的控件是旋轉木馬。
WithEvents sclContentViewer As New ScrollView, stkContent As New StackLayout, crsBanner As New CarouselControl
由於Xamarin沒有旋轉木馬,下一步是新建用戶控件。
第五步,新建用戶控件
使用Intellisense快速新建 CarouselControl.vb 文件
同樣,手動新建CarouselControl.Designer.vb文件。
旋轉木馬控件具有向左轉,向右轉按鈕,按鈕底下壓着要呈現的內容,使用滑動手勢可以向左轉或向右轉,點擊會觸發點擊事件。控件的底部有個指示器顯示旋轉木馬的旋轉進度。
Partial Public Class CarouselControl Inherits ContentView WithEvents btnLeft As New Button With { .Text = "<", .HorizontalOptions = LayoutOptions.Start, .VerticalOptions = LayoutOptions.Fill, .FontSize = 32, .BackgroundColor = Color.FromRgba(0, 0, 0, 0.01), .BorderWidth = 0, .TextColor = Color.FromRgba(0, 0, 0, 0.7), .BorderColor = Color.Transparent }, btnRight As New Button With { .Text = ">", .HorizontalOptions = LayoutOptions.End, .VerticalOptions = LayoutOptions.Fill, .FontSize = 32, .BackgroundColor = Color.FromRgba(0, 0, 0, 0.01), .BorderWidth = 0, .TextColor = Color.FromRgba(0, 0, 0, 0.7), .BorderColor = Color.Transparent }, cntDetail As New Grid, grdItemView As New Grid, panDetector As New PanGestureRecognizer, layoutRoot As New StackLayout, indicator As New CarouselIndicator With { .HorizontalOptions = LayoutOptions.Center } Sub New() InitializeComponents() End Sub Protected Sub InitializeComponents() With grdItemView.Children .Add(cntDetail) .Add(btnLeft) .Add(btnRight) End With GestureRecognizers.Add(panDetector) layoutRoot.Children.Add(grdItemView) layoutRoot.Children.Add(indicator) Content = layoutRoot End Sub End Class
由於篇幅有限,旋轉木馬的指示器代碼見文章最后的GitHub鏈接。
回到旋轉木馬類沒有分部的那個文件。
這個控件要寫的東西很多,我挑重要的講解。處理事件與使用Winform的時候是一樣的,在下拉列表點控件和事件就可以生成空白的事件處理代碼。
以滑動手勢識別為例,在自動生成的代碼中填充處理滾動的代碼。
Private Sub panDetector_PanUpdated(sender As Object, e As PanUpdatedEventArgs) Handles panDetector.PanUpdated If Math.Abs(e.TotalX) > Math.Abs(e.TotalY) Then Dim threshold = btnLeft.Width If e.TotalX > threshold Then MoveBack() ElseIf e.TotalX < -threshold Then MoveForward() End If End If End Sub
橫幅控件 MediumBanner 也是類似的寫法。如果不想給ContentView控件寫新的模板,新建這個橫幅控件是合適的。
橫幅控件的數據綁定比較簡單,貼一些代碼。
首先是在構造函數設定綁定。這里的概念與UWP的數據綁定是類似的。注意,某些轉換器需要自己寫的情況是沒有變的。
imgIcon.SetBinding(Image.SourceProperty, NameOf(MediumBannerData.IconImage), converter:=New EmbeddedImageLoader)
設定完綁定,在創建控件的時候指定數據上下文。注意,Xamarin的數據上下文屬性是 BindingContext而不是DataContext。
WithEvents btnAbout As New MediumBanner With {.BindingContext = New MediumBannerData("aboutbanner.png", "關於本軟件", "about.png")}
一般的控件沒有點擊事件,那么我們可以給點擊手勢識別器的Command屬性進行數據綁定,也可以利用它的Tapped事件自己寫一個Clicked事件。
btnTapHitTest.SetBinding(TapGestureRecognizer.CommandProperty, NameOf(CarouselControlDefaultModel.OnClick))
第六步,進一步使用Mvvm模式
旋轉木馬控件用Mvvm模式可以省去不少麻煩。
首先是新建Model。
Public Class CarouselControlDefaultModel Sub New(functionName As String, embeddedImageFileName As String, onClick As ICommand) Me.FunctionName = functionName Me.EmbeddedImageFileName = embeddedImageFileName Me.OnClick = onClick End Sub Public Property FunctionName As String Public Property EmbeddedImageFileName As String Public Property OnClick As ICommand End Class
接下來是它對應的 ViewModel
集合用什么類型取決於相應的控件實現到什么程度。
Public Class HoneyCarouselViewModel Sub New(items As List(Of CarouselControlDefaultModel)) Me.Items = items End Sub Public Property Items As New List(Of CarouselControlDefaultModel) End Class
數據最好放到資源文件里。如果數據少,可以直接寫到代碼里。
View與ViewModel的綁定代碼是在相應的頁面里寫的。
Sub New() InitializeComponents() crsBanner.ItemsSource = vm.Items End Sub
重復前幾步直到首頁寫完。
第七步,處理頁面間的導航
導航的代碼很簡單。
不管是導航到某個頁面還是導航回來,都是使用Navigation對象進行操作。
Private Sub btnAbout_Clicked(sender As Object, e As EventArgs) Handles btnAbout.Clicked Navigation.PushAsync(New AboutPage) End Sub
第八步,處理平台特定的功能
我搜索了Xamarin.Forms的教程,結果沒發現打開瀏覽器這個功能。缺少這個功能的話,就得自己分別在其它項目實現了。
新建一個類或者是一個模塊,包含打開瀏覽器用的事件和引發事件的過程。
Public Class HyperlinkNavigationService Public Shared Event NavigationRequested(link As Uri) Public Shared Sub NavigateTo(link As Uri) RaiseEvent NavigationRequested(link) End Sub End Class
在安卓項目里處理瀏覽器導航請求事件
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsApplicationActivity { protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); HyperlinkNavigationService.NavigationRequested += HyperlinkNavigationService_NavigationRequested; global::Xamarin.Forms.Forms.Init(this, bundle); LoadApplication(new App()); } private void HyperlinkNavigationService_NavigationRequested(Uri link) { StartActivity(new Intent("android.intent.action.VIEW", Android.Net.Uri.Parse(link.OriginalString))); } protected override void OnDestroy() { base.OnDestroy(); HyperlinkNavigationService.NavigationRequested -= HyperlinkNavigationService_NavigationRequested; } }
類似地,在UWP, Win 8.1, WP 8.1, IOS 項目處理導航事件
其它問題也可以這樣處理,通過事件實現特定平台上的功能的調用。
第九步,處理資源和應用程序定義
發布之前,需要將自帶的資源替換為自己重新設計的資源。一般圖像資源和應用程序定義都是要改的。
嵌入到可移植項目的資源最好用文件夾歸類好,並且確保沒有重名。
詳細的示例
示例項目的地址: https://github.com/NWU1902/ZhongTianSale
示例項目僅包含可移植類庫,沒有特定平台的項目。