Xamarin.Form 项目总结

离上一篇 用Xamarin.Form做个技术预览版的APP  有一个月零几天了. 现在这个APP在业务方面的代码由另外一个同事全权处理, 我又闲了下来, 要去弄另外一个项目.

这个把月, 踩过 Xamarin.Form 很多坑, 填过很多坑, 也造了很坑...

这里做个总结, 给想跳坑的你做个参考.

ListView 的下拉刷新

下拉刷新还是很简单的, 首先要设置 listView.IsPullToRefreshEnabled 为 true

  1. 当下刷新时, RefreshCommand 会被调用,Refreshing 事件会被执行。IsRefreshing 会被设置为 true
  2. 实现 RefreshCommand 或 Refreshing 事件。
  3. 当刷新完成,调用 EndRefresh 或设置 IsRefreshing 为 false, 来告诉 listView 刷新已经完成。
 1 /// <summary>
 2 /// 下拉刷新事件
 3 /// </summary>
 4 /// <param name="sender"></param>
 5 /// <param name="e"></param>
 6 public void RefreshData(object sender, EventArgs e) {
 7     this.PageIdx = 0;
 8     this.LoadConsigns(true);
 9     var lst = (ListView)sender;
10     lst.IsRefreshing = false;
11 }

ListView 的 Footer

下拉刷新有自带事件可以处理, 但是加载更多却没有原生支持.
一开始,我在 ListView 的 Footer 中加了一个 Button , 点击的时候加载更多的数据, 在 WP 和 Android 下表现完美.
但是在 iOS 下:

  • 采用 Xamarin.Forms 1.4.3.6358-pre2 时, Footer 错位, 具体是因为 HasUnevenRows 为 True, 行高不是默认的行高, 而计算 Footer 的位置的时候, 是用的默认的行高. 如果设置行高的话,就不会错位, 但是场景中不能设置行高.
  • 采用 Xamarin.Forms 1.4.2.6359 直接报错.

无奈之下,只好放弃这种做法.

ListView 的 ItemAppearing事件

上面说放弃在 ListView 中加Footer, 但是加载更多这个功能还是要完成的.
先来看一下如何用 ItemAppearing 来处理加载更多的:

 1 /// <summary>
 2 /// 最后一条数据展现时,加载更多
 3 /// </summary>
 4 /// <param name="sender"></param>
 5 /// <param name="e"></param>
 6 public void LastAppear(object sender, ItemVisibilityEventArgs e) {
 7     if (this.Datas.Last().Equals(e.Item)) {
 8         this.LoadConsigns();
 9     }
10 }

即拿事件参数中的 e.Item 来和数据源中的最后一条来比较, 如果是同一个对象, 说明最后一条已经展现在当前可见屏幕范围之内, 可以加载更多数据了.
这样做,在 iOS 和 Android 下表现很完美.

但是在 WP 下却有可能加载完了第一页,紧接着加载第二页的问题.
因为在 WP 下是用 LongListSelector 来模拟的 ListView, LongListSelector 用了UI虚化技术, 虚化的条数貌似和一屏能显示的条数有关, 具体多少条不能设置, LongListSelector 自己维护.
相同的情况还发生在 TabbedPage 的子页中, 假设一个 TabbedPage 有4个ContentPage , 在初次进入App 的时候, 前两个 ContentPage 的 OnAppearing 事件都会触发.

ListView 的 Group Header

ListView 的Group Header 如果用 Template, 在 WP / Android 下, 表现完美.
但是 iOS 7.1 下直接不显示, 在 8.1时而显示,时而不显示, 并伴有稍微的错位.
如果不用 Template , 表现是挺好的.

同一个页面,多个 ListView

如果有这样的页面, 那请直接换 TableView , 将原来想放到两个 ListView 中的数据分别写到一个 TableView 的不同 TableSection 中.
因为多个 ListView 上下显示, 并不会自动"流式布局".

提升 ListView 的性能

这部分是 Xamarin.form 官方的文档读书笔记, 贴出来给你做个参考, 因为它很重要:
有两点会使 ListView 性能受损:
1,using ViewCell‘s with lots of views, and
2,using layouts that require lots of measurement.
1,在 cell 中使用过多的 views(控件)
2,过多的布局测量

为达到最优性能:
1,使用内建的 Cell (TextCell/ ImageCell /SwitchCell /EntryCell)
2,尽量少的元素。
3,避免层级嵌套;可以使用 AbsoluteLayout 或 Grid 来减少嵌套。
4,避免 Fill 之外的 LayoutOptions 选项。

提升打开新页面的体验速度

在 iOS 和 WP 下, 切换页面效果很流畅, Android 表现的有些卡.
做为优化, 我把加载数据从构造函数移到 OnAppearing 事件中, 并做了一个 500 毫秒的延时:

 1 protected override void OnAppearing() {
 2     base.OnAppearing();
 3
 4     if (this.Data == null)
 5         Task.Delay(500).ContinueWith((t) => {
 6             Device.BeginInvokeOnMainThread(() => {
 7                 this.LoadData();
 8             });
 9         });
10
11 }

这样做之后, 切换页面相对来说会流畅很多.

HUD

Android 下使用 AndHUD , iOS 下使用 BTProgressHUD, 这两个在在 NuGet 上都有.
但是并没有 WP 的HUD, 还好, 我用 Popup 做了一个.

1     public interface IHud {
2         void Close();
3         void Show(string msg);
4         void ShowToast(string msg, int delay = 1000);
5     }
 1 [assembly: Dependency(typeof(Hud))]
 2 namespace LBC.Mobi.WinPhone.Services {
 3     public class Hud : IHud {
 4
 5         private System.Windows.Controls.Grid Container = new System.Windows.Controls.Grid();
 6
 7         private Popup Popup = null;
 8
 9         public Hud() {
10             this.Popup = new Popup() {
11                 Child = new Border() {
12                     Background = new SolidColorBrush(Colors.Black),
13                     Opacity = 0.6,
14                     CornerRadius = new CornerRadius(10),
15                     Padding = new System.Windows.Thickness(10),
16                     HorizontalAlignment = System.Windows.HorizontalAlignment.Center,
17                     VerticalAlignment = System.Windows.VerticalAlignment.Center,
18                     Child = this.Container,
19                 }
20             };
21
22             this.Container.SizeChanged += Container_SizeChanged;
23         }
24
25         void Container_SizeChanged(object sender, SizeChangedEventArgs e) {
26             var size = System.Windows.Application.Current.RootVisual.RenderSize;
27             this.Popup.HorizontalOffset = (size.Width - this.Container.ActualWidth) / 2;
28             this.Popup.VerticalOffset = (size.Height - this.Container.ActualHeight) / 2;
29         }
30
31         public void ShowToast(string msg, int delay = 1000) {
32             this.Popup.IsOpen = true;
33             this.Container.Children.Clear();
34             this.Container.Children.Add(new TextBlock() {
35                 Text = msg,
36                 Foreground = new SolidColorBrush(Colors.White),
37                 FontSize = 20
38             });
39
40             var v = Windows.Phone.Devices.Notification.VibrationDevice.GetDefault();
41             v.Vibrate(TimeSpan.FromSeconds(0.1));
42
43             Task.Delay(delay)
44                 .ContinueWith(t =>
45                     Deployment.Current.Dispatcher.BeginInvoke(() => {
46                         this.Popup.IsOpen = false;
47                     })
48                     );
49         }
50
51         public void Close() {
52             this.Popup.IsOpen = false;
53         }
54
55         public void Show(string msg) {
56             this.ShowToast(msg, 60000);
57         }
58     }
59 }

联编设置

前一篇,我说 Xamarin 生成的 Android 的 app 巨大, 主要是因为没有联编 (Linking).
之所以没有联编, 是因为联编就报:

error XA2006: Reference to metadata item ....

之类的错误.

昨天搜索了一下, 联编是因为工程选项没有设置正确:

  • Compile using Android version 选择 Last Platform.
  • Minimum 选择最小支持的版本.

Linking 选项不能选择 Sdk and User Assemblies. 否则生成的 APK 无法运行

如止设置之后, 我这个 APP 的大小只有 5.09 M

AOT JIT 泛型

用 .NET 做App, 服务提供首选 WebApi, 只不过...
直接使用 Asp.net Web API Client 的相关泛型方法读取数据的时候, 在 IOS 下真机(注意,是真机调试, 模拟器不会报错)调试会报错.

Attempting to JIT compile method ‘System.Net.Http.HttpContentExtensions:ReadAsAsync<LBC.Mobi.WebApiClient.Models.Token> (System.Net.Http.HttpContent,System.Type,System.Collections.Generic.IEnumerable`1<System.Net.Http.Formatting.MediaTypeFormatter>,System.Net.Http.Formatting.IFormatterLogger,System.Threading.CancellationToken)‘ while running with --aot-only. See http://docs.xamarin.com/ios/about/limitations for more information.

因为 IOS 是 AOT (静态编译), 而 Android 和 WP 使用的是 JIT(动态编译).具说 Android 也引入了 AOT ,不懂.

具体的限制可以从以下链接获取:
http://docs.xamarin.com/ios/about/limitations

这个文档没有看太懂, 但是这个错误是因为在调用 WebApi 的时候, 使用了相关的泛型方法而报错.
但是并不是说, 不能使用泛型, 因为在 iOS 项目属性里, 有个 Enable generic value type sharing, 如果不勾上这个, 连最基本的泛型使用都会报错. 然而这个选项在当前我使用的 Xamarin 版本中, 对 WebApi 来说, 没卵用.
不过,不要怕, 有人贴段变通的方法:
https://bugzilla.xamarin.com/show_bug.cgi?id=12746

你也可参考:

  1 using System;
  2 using System.Net;
  3 using System.Net.Http;
  4 using System.Net.Http.Formatting;
  5 using System.Net.Http.Headers;
  6 using System.Threading.Tasks;
  7 using System.Linq;
  8
  9 namespace LBC.Mobi.WebApiClient {
 10
 11     public abstract class MethodBase {
 12
 13         public virtual bool SupportProtoBuf {
 14             get {
 15                 return true;
 16             }
 17         }
 18
 19         public bool HasError {
 20             get;
 21             protected set;
 22         }
 23
 24         public string ErrorReason {
 25             get;
 26             protected set;
 27         }
 28
 29         public HttpStatusCode? Status {
 30             get;
 31             protected set;
 32         }
 33
 34         /// <summary>
 35         /// 方法名,除去基地址,比如 User/Get
 36         /// </summary>
 37         public abstract string MethodName {
 38             get;
 39         }
 40
 41         /// <summary>
 42         /// 如何执行,是Post,get还是 delete 等, 执行的参数等.
 43         /// </summary>
 44         internal abstract Func<HttpClient, Uri, Task<HttpResponseMessage>> Invoke {
 45             get;
 46         }
 47
 48
 49         internal async virtual Task<HttpResponseMessage> GetResult(string token) {
 50             Uri url = new Uri(ApiClient.GetMethodUrl(this.MethodName));
 51
 52             if (this.Invoke != null) {
 53                 using (var client = new OAuthHttpClient(token)) {
 54                     if (this.SupportProtoBuf)
 55                         client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/x-protobuf"));
 56
 57                     return await this.Invoke.Invoke(client, url);
 58                 }
 59             }
 60
 61             return await Task.FromResult<HttpResponseMessage>(null);
 62         }
 63     }
 64
 65     public abstract class MethodBase<T> : MethodBase {
 66
 67         internal async virtual Task<T> Execute(string token) {
 68             var msg = await this.GetResult(token);
 69             var reason = "";
 70             HttpStatusCode? status = null;
 71             if (msg != null) {
 72                 if (msg.IsSuccessStatusCode) {
 73                     //var str = await a.Content.ReadAsStringAsync();
 74                     //var o = JsonConvert.DeserializeObject<T>(str);
 75
 76                     var useProtoBuf = msg.Content.Headers.ContentType.MediaType.Equals("application/x-protobuf", StringComparison.OrdinalIgnoreCase);
 77
 78                     //if (this.SupportProtoBuf) {
 79                     if (useProtoBuf) {
 80                         return await this.Read(typeof(T), msg, new ProtoBufFormatter()).ContinueWith(t => (T)t.Result);
 81                         //return await a.Content.ReadAsAsync<T>(new[] { new ProtoBufFormatter() });
 82                     } else {
 83                         return await this.Read(typeof(T), msg).ContinueWith(t => (T)t.Result);
 84                         //return await a.Content.ReadAsAsync<T>();
 85                     }
 86                 } else {
 87                     reason = msg.ReasonPhrase;
 88                     status = msg.StatusCode;
 89                 }
 90             }
 91
 92             this.HasError = true;
 93             this.ErrorReason = reason;
 94             this.Status = status;
 95
 96             return await Task.FromResult<T>(default(T));
 97         }
 98
 99         //IOS 是 AOT , 不支持 JIT, 在反序列化的时候,要用该方法.
100         private async Task<object> Read(Type outType, HttpResponseMessage msg, params MediaTypeFormatter[] formatters) {
101             if (formatters != null && formatters.Length > 0)
102                 return await msg.Content.ReadAsAsync(outType, formatters);
103             else
104                 return await msg.Content.ReadAsAsync(outType);
105         }
106     }
107 }

Splash

ios 的 Splash 很好设置, 在选项里点点就可以完成了.
WP 的更简单, 放一个 480 X 800 的 SplashScreenImage.jpg 的图片到 WP 项目的根目录就是了. 如果还想适应个分辨率, 延个时啥的,可参考:
http://stackoverflow.com/questions/19450446/how-to-set-splash-screen-in-window-phone-8-application-development

麻烦的就是, Android
请按以下的设置处理:
1, 先新建一个 Activity:

1     [Activity(Label = "货代网", MainLauncher = true, Icon = "@drawable/icon", NoHistory = true, Theme = "@style/Theme.Splash", ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
2     public class SplashScreen : Activity {
3         protected override void OnCreate(Bundle bundle) {
4             base.OnCreate(bundle);
5             var intent = new Intent(this, typeof(MainActivity));
6             StartActivity(intent);
7             Finish();
8         }
9     }

其中, MainLauncher 设为 true

还要设置一下 Theme : Style/Theme

2, 将 MainActivity 上标注的 MainLauncher 改为 false , 或删除.

3, 在 Resources/Velues 目录下新增 Styles.xml:

 1 <resources>
 2   <style name="Theme.Splash"
 3     parent="android:Theme">
 4     <item name="android:windowBackground">
 5       @drawable/splashscreen
 6     </item>
 7     <item name="android:windowNoTitle">true</item>
 8     <item name="android:windowIsTranslucent">false</item>
 9     <item name="android:windowIsFloating">false</item>
10     <item name="android:backgroundDimEnabled">true</item>
11   </style>
12 </resources>

其中, name="Theme.Splash" 就是上面新建的 Activity (SplashScreen) 标注的的值.

4, 在 Resources/drawable 目录下新建一个 SplashScreen.xml, 对应到上面的Xml 中的 <item name="android:windowBackground"> 的值:

 1 <?xml version="1.0" encoding="UTF-8" ?>
 2
 3 <layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
 4   <item>
 5     <shape android:shape="rectangle" >
 6       <solid android:color="#3498db" />
 7     </shape>
 8   </item>
 9   <item>
10     <bitmap xmlns:android="http://schemas.android.com/apk/res/android"
11       android:src="@drawable/Splash"
12       android:gravity="center"
13       android:layout_gravity="center"/>
14   </item>
15 </layer-list>

5, 在 Resources/drawable 目录下放个 Splash.png, 对应到上面的 XML 中的 bitmap 节的 src 属性.

--------------

这个是商业项目, 所以没有源代码共享.

如果你感兴趣, 可以下载 android 的 app 试用 ( Android 4.1 及以上 ), 服务是我本机的调试环境, 每天 9:30 ~ 下午 18:00 开启.

http://files.cnblogs.com/files/xling/LBC.LBC-Signed.apk

用户名/密码 : FRANK / FRANK

时间: 2024-10-09 18:23:15

Xamarin.Form 项目总结的相关文章

C# 移动开发(Xamarin.Form) Plugin.BLE 蓝牙连接

随着Xamarin.Form项目接近尾声,仔细一算才发现过来大半年时间了. 期间除了刚开始有闲情写写,期间各种开发坑,老板坑,状态不好搞得没心情写博了,现在总算有空来总结一下了. 来先说 Plugin.BLE (https://github.com/xabre/xamarin-bluetooth-le),在NuGet里搜索 Bluetooth ,Plugin.BLE是下载最多的Xamarin.Form可用的库,有16.3K(1.63万)人下载(2017-11-23). 几乎可用库都试过 调用 P

FlipView For Xamarin.Form 之 IOS

之前写过两篇博文 是关于 Android 和 Windows Phone 下的 FlipView 的实现. 上上周,有个印度佬通过 GitHub 找到我, 问我有没有打算个 ios 端的,还说比较了相同功能的几个开源项目,我的这个项目值得推荐.说的我心潮澎湃,上周末花了一个周末升级到最新的 XCode 7,顺便也升级到了 OS X EI Capitan, 安装了最新的 Xamarin, 使用了 IOS 9 提供的免费证书,终于于今天凌晨1点(10月25)把 ios 端的 renderer 给写出

Xamarin.Form 实例: Discuz BBS 客户端 源码分享

感谢台风, 这个十一长假让我好好的休息了一回, 睡觉到腰酸背疼, 看电影看到眼发红. 今天最后一天, 不敢出去逛, 不知道哪会还会下暴雨... 嗯嗯..这个项目其实在十一之前就开始了, 工作无聊,没有新任务, 我就搞起它. 至于为什么选 Discuz 的 BBS , 因为我常上的几个网站, 都有一堆的 APP , 官方的, 第三方的 . BBS 虽然已经没落了, 但是官方的 APP 居然用不了! 写这个东西之前, 本来想拿来看 1024 的, 但是 1024 要么不是最新版本, 要么禁用了 AP

挣扎着写 FlipView For Xamarin.Form

Xamarin.Form 中没有 FlipView, 也没有 CarouselView , 有的只是一个 CarouselPage, 它是一个 Page, 不是一个 View ! Windows Phone 8.1 下有个控件叫 FlipView, 但是它不存于在 WP Siliverlight ! 而Xamarin.Form 的 WP 项目又是基于 WP Siliverlight 的. Android 原生也没有 FlipView 类似的控件. IOS 下冒似也没有. 我急切的想弄一个这样的东

Xamarin.Form 初学 之 服务引用-WCF服务引用

最近研究一下Xamarin.Form,感觉这个东西确实不错,之前一直做WPF相关,然后看到Xamarin.Form开发与WPF特别相似的语法.很有兴趣! 可是环境部署对于小白的我,可是费了不少功夫!安装VS2015费了我好些时间!安装部署以后再说!先说说引用WCF服务的坑吧! 官方文档:Xamarin可以调用WCF,可以怎么调用???(满脑子问号)https://developer.xamarin.com/guides/xamarin-forms/web-services/consuming/w

在 Xamarin.Form 使用 ProtoBuf, 提升APP的体验档次

XML被JSON代替的时候,是因为JSON的更小的文件体积.现在移步到手机,json 数据包也愈发显的不可接受了.满眼的都是 json 的属性名,真正有用的属性值却只占整个JSON包的一小部份.如果能不要"属性名称",那可太好了,但是那是不可能的. 老早就听说过 ProtoBuf ,一直没有用过.这两天耗了些时间研究了一下,成功的应用于服务端(WebApi) 和 客户端(Xamarin.Form) 上.先贴两张图感受一下: 能小多少,和具体的类结构及数据完整性有关. 不过单从这份结果对

xamarin.form Tabbed选项卡式页

<?xml version="1.0" encoding="utf-8" ?> <TabbedPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:App1;assembly=App1&qu

Xamarin.Form与Xamarin.Android或Xamarin.IOS的区别简述

原文:Xamarin.Form与Xamarin.Android或Xamarin.IOS的区别简述 Xamarin.Form与Xamarin.Android或Xamarin.IOS的区别简述: 可能刚刚接触Xamarin的人来说,对于这个概念比较的模糊,认为这说的不都是同一个东西吗?事实并不是这样的,我们先来说说Xamarin.Android和Xamarin.IOS吧,这两个其实就是一个单独的工程,在这里面我们可以针对安卓或者IOS进行代码的编写,而且支持原生的代码调用,这对于安卓或者IOS的开发

Xamarin.Form 蓝牙ble

本文介绍Xamarin.Form的蓝牙使用方法,开始之前说说题外话,就Xamarin.Form来说,在国内是没多少人在弄的,但对于我自己来说,是吃了这碗饭,也希望在这行做的人越来越多,越来越好,本人2016年开始入使用Xamarin.Form开发App,那时候刚微软刚收购Xamarin,也正式开源免费给开发者使用,但那时候的本说实话是有多不完善的,直到现在微软依然有很多不完善的功能,所以在国内学这个,一般是由公司高层推这个技术,直接至底向上的非常少.微软的东西在国内环境来说,多不太好,很多大的互