Xamarin.Forms - 编写一次,到处运行,而且是本机的?

[原文发表地址] Xamarin.Forms - Write Once, Run Everywhere, AND Be Native?

[原文发表时间] 2014/05/28

1

我在Nike工作的很多年,使用java编写了一个可以在四种平台上运行的订单管理应用程序。我们曾经经常开玩笑说:“编写一次,到处调试”。这是早期的java, 但现在的事实是,每一个窗体和控件都是“自绘的”,这意味着一个按钮在所有的地方看起来都是一样的,因为它不是一个真实的按钮,当然这会因为操作系统而显示不一样。它只是一个按钮的图片。我们经常使用Spy++和不同的Windows检查程序来探索我们的应用程序,并且他们永远看不到一个java程序的控件。这意味着应用程序在任何地方都能工作的很好,而且总是看起来像是一个java应用程序。它们没有和底层平台整合。

使用MVVM(模型,视图,视图-模型)模式,以及在Windows Phone 8.1和windows 8.1上工作的通用应用程序技术,对于某些类型应用程序,代码共享最高可以达到90%。然而,即使一个简单的应用程序,针对每一个平台,你仍然得创建一个自定义的本机视图。大多数情况下,这是可取的,但是对于某些应用程序,它很让人厌烦的,很容易出错,而且很冗长。

Xamarin今天宣布了Xamarin.Forms,我认为它有效地

把本地控件抽象到一个更高级别的概念。过去,在我的眼里,这非常类似于我当年在java中写的代码-所有的都是以布局和流程背后的流利代码完成的。你创建一个控件树。

Xamarin.Forms是一个新的类库,对于iOS, Android和windows phone, 你都可以从一个单一的,共享的C# 代码库里生成本地的UIs。它提供了400多种跨平台的控件和布局,在运行时可以映射到本地控件,这意味着你的用户接口完全是本地的。

对于我来说,有趣的是这些“控件/概念”(我的术语)在一个很高的级别被编码,但却被当作本地对应的控件。所以在我2代码中的“选项卡”在移动设备上被描述为最具体的,并且是本地对应的控件,而不是我JAVA例子里的一个普通的选项卡控件。让我们看一个例子。

我的伙伴,James Montemagno,来自Xamarin,一个喜欢辣椒的人,在一个喝了咖啡的深夜,他把最终的跨平台的Hanselman的应用程序放在一起,来对我说明一些观点。这个小的应用程序是用C# 写的,可以运行在本地的Windows Phone,Android和iOS等系统中。它发表在我的博客和tweets上。

image

这是视图之前切换的菜单:

和创建它的代码。为了明确起见,我已经简化了一点,但是想法全是MVVM:

 

这里有几个事情需要注意。看见 ListImageCell了吗? 它是 ImageCell的子类,这是个带有图片的 TextCell,并且可以给文本和图片设置数据绑定。普遍认为每种平台都有文本和图片,但是资源在每一个平台上是不一样的。这是为什么博客和twitter的图片是独一无二的对于各自的平台。概念是共享的,而且是本机实现的,并且看起来是本机的。

那是在UI方面,在逻辑方面,所有加载RSS feed 的代码和Tweets都是在3个平台上互相共享的。对于非阻塞式 I/O,它可以使用异步和等待,而且在twiiter的例子中,它使用了 LinqToTwitter作为一个PCL(便携式类库),这是非常酷的。对于RSS解析,它使用Ling to XML.

 private async Task ExecuteLoadItemsCommand()
 {
     if (IsBusy)
         return;
  
     IsBusy = true;
  
     try{
         var httpClient = new HttpClient();
         var feed = "https://feeds.hanselman.com/ScottHanselman";
         var responseString = await httpClient.GetStringAsync(feed);
  
         FeedItems.Clear();
         var items = await ParseFeed(responseString);
         foreach (var item in items)
         {
             FeedItems.Add(item);
         }
     } catch (Exception ex) {
         var page = new ContentPage();
         var result = page.DisplayAlert ("Error", "Unable to load blog.", "OK", null);
     }
  
     IsBusy = false;
 }

And ParseFeed:

 private async Task<List<FeedItem>> ParseFeed(string rss)
 {
     return await Task.Run(() =>
         {
             var xdoc = XDocument.Parse(rss);
             var id = 0;
             return (from item in xdoc.Descendants("item")
                 select new FeedItem
                 {
                     Title = (string)item.Element("title"),
                     Description = (string)item.Element("description"),
                     Link = (string)item.Element("link"),
                     PublishDate = (string)item.Element("pubDate"),
                     Category = (string)item.Element("category"),
                     Id = id++
                 }).ToList();
         });
 }

再一次,共享所有。当到了 Windows Phone, Android, 和 iPhone列表输出数据的时候,在每一种平台上,它看起来都非常棒(读:本机的),它实际上没有做任何事情在具体的平台上。控件看起来是本机的,因为他们是本机的。 Xamarin.Forms 控件是本机控件的封装,它们本身不是一个新的控件。

image

这里是 BlogView, Xamarin.Forms中像 ActivityIndicator一样的东西,它表现得像是一个本机的控件。

 public BlogView ()
 {
     BindingContext = new BlogFeedViewModel ();
  
     var refresh = new ToolbarItem {
         Command = ViewModel.LoadItemsCommand,
         Icon = "refresh.png",
         Name = "refresh",
         Priority = 0
     };
  
     ToolbarItems.Add (refresh);
  
     var stack = new StackLayout {
         Orientation = StackOrientation.Vertical,
         Padding = new Thickness(0, 8, 0, 8)
     };
  
     var activity = new ActivityIndicator {
         Color = Helpers.Color.DarkBlue.ToFormsColor(),
         IsEnabled = true
     };
     activity.SetBinding (ActivityIndicator.IsVisibleProperty, "IsBusy");
     activity.SetBinding (ActivityIndicator.IsRunningProperty, "IsBusy");
  
     stack.Children.Add (activity);
  
     var listView = new ListView ();
  
     listView.ItemsSource = ViewModel.FeedItems;
  
     var cell = new DataTemplate(typeof(ListTextCell));
  
     cell.SetBinding (TextCell.TextProperty, "Title");
     cell.SetBinding (TextCell.DetailProperty, "PublishDate");
     cell.SetValue(TextCell.StyleProperty, TextCellStyle.Vertical);
  
     listView.ItemTapped +=  (sender, args) => {
         if(listView.SelectedItem == null)
             return;
         this.Navigation.PushAsync(new BlogDetailsView(listView.SelectedItem as FeedItem));
         listView.SelectedItem = null;
     };
  
     listView.ItemTemplate = cell;
  
     stack.Children.Add (listView);
  
     Content = stack;
 }

Xamarin Forms是一个非常灵敏的解决方案,有人可能会说,是优雅的,对于编写一次,到处运行,并且不会有麻烦。好的是当你想要关注底层的时候,你可以关心底层平台,当你不想关注的时候,就可以忽略它。一个隐藏了本机平台的解决方案不是本机,是吗?这将是一共同标准最低的解决方案。这个出现是为了隐藏跨平台和多设备程序的冗长和重复。

image

https://xamarin.com/forms样本代码 ,还有更多的关于 Xamarin and Xamarin Forms信息。同时你可以在https://github.com/jamesmontemagno/Hanselman.Forms上查看 Hanselman 应用程序的代码。