Xamarin Forms:小马过河,王者归来
因为我媳妇的原因,去年下半年从零开始学习Android原生开发,做了一个答题库app。整体给我的感觉是入门难度不大,前期折腾一番,大部分时间都是花在开发上面,其实任何一门语言都是如此。
今年我又有另一个需求想要开发一款app同时支持android和iOS。作为一名.net圈子的人怎么可能不知道Xamarin。只是一直以来国内关于Xamarin的信息太少,而且每年Xamarin社区的发展和完善相关的情况也都没有相应的报道,说白了关注度不高,让想使用它的人没有太多勇气去决定使用它。我也是这样,没办法转投关注国外论坛和相关博客,果然是墙内墙外的环境大不相同。中国还是一大群人围着小马七嘴八舌,而国外已经小马过河,王者归来了。发现了一篇博客极大的鼓舞了我选择Xamarin的勇气,所以决定翻译这篇博文分享给各位。最后插一嘴,我认为.NETer是幸运的,虽然经历过黑暗,但是我认为并非坏事,在国内这种培训机构横飞的年代没有像Java那样泛滥成灾。值得庆幸的是.NET目前依然在手游、医疗、金融、教育等领域占有较大份额。.net依然是高效开发的首选语言。废话不多说,下面是译文:
原文地址:https://www.sharpnado.com/xamarin-forms-works/
2017年7月,我接到了以前的同事打来的电话,问我是否有兴趣和他一起做个APP,客户端是一个可以专门编辑赛马比分结果的巴黎赛马报。
可爱的小马
他们想针对小马训练员们提供一个多平台的新产品叫做 Exalt Training。
为此,他们和一个名叫Souffl获得过创新奖的法国公司合作。
如果你们想自己了解更多关于他们的产品请访问:https://souffl.com/projets/la-premiere-solution-de-tracking-sportif-dediee-aux-entraineurs-de-chevaux-de-course/。
经过了6个月的延期iOS app开发失败之后,他们决定从原生native转投Xamarin native,包括android和iOS平台。
Xamarin Forms成熟并且功能强大
没错我说的,如果你们中任何人质疑,这就是给你们的答案:它简单粗暴、高效快捷,它可以处理各种复杂场景甚至你要想的所有功能。
多说无益!
我们实现的复杂功能包括:
- 渐变的背景
- 多种动画效果
- 在谷歌地图上绘制渐变路径
- 数据可视化曲线图
- 超多视图轮播(views 20+)
- 有吸附效果的水平列表视图
- 可拖放的的表格视图
- 自定义选项卡
- 带隐藏菜单的选项卡组成的自定义导航,向上滑动可显示菜单。
这些功能所用到技术,我会一一回答,如下:
技术 | 作用 |
---|---|
SkiaSharp | Animation / Draw on map / Plots |
Custom renderers | Gradient / Horizontal list views |
Xamarin animation API | Animations |
Xamarin custom components | BottomBar / Tabbar |
Lottie | Animations |
WebSocket | Real time communication |
最后,我们将会在帅气的 Visual Studio应用中心上面添加注释。
注意:方便起见,所有android模拟器捕获包含的gif和png图片,都要确保我们在iOS app中有完全相同的资源可用。
SkiaSharp是一个跨平台的底层API。你可以在canvas中绘制文本、圆形、路径(DrawText, DrawCircle, DrawPath)。虽然很强大,但依然需要通过一些数学技巧来实现你自己的布局。
以下所有的视图都是用SkiaSharp绘制的。
在Goole Maps上绘制
这个有点挑战,因为你必须将gps位置转换到像素坐标,而不通过调用 Google Maps SDK(为了有较好的响应时间)。
幸运的是我已经在10年前工作中的一个项目中解决过这个问题。
这回我们将使用GDI+在web视图里面的虚拟地球上绘制自定义的图标和路径。好了,我已经有了数学墨卡托的投影法术了。
剩下的工作就不是很困难了,因为只需要用着色器绘制一个路径即可,因此我们可以看到不同(渐变)表示的马儿的速度。
这个skiasharp位嵌入到一个的自定义ContentView的sessionmap方法中。
这个颜色代表马奔跑的速度,蓝色是快,红色是快到极限。
绘制曲线
在地图的下面我们想显示马儿的卖力情况(心率和速度)。所以我们需要在马儿训练时间上绘制曲线。
我们为此创建了一个SessionGraphView的ContentView。
将上面两个联系到一起
当然我们还需要一些交互性功能,用户可以拖动时间线的时候同时也更新马儿卖力情况的数值和对应地图上的位置。
这是通过将属性currentTime从sessiongraphview绑定到sessionmap组件来实现的。
自定义控件
另一个界面,我们需要一个根据月为索引的日期选择控件。因为Xamarin Forms没有这个,我们决定用SkiaSharp来实现它。
这是一个简单的控件名为MonthSlidesView,继承自ContentView,具备CurrentLowerDate
and CurrentUpperDate
.两个属性。
当然,这个控件与反映马随着时间的推移前进的曲线相互作用。
可发光的数字动画
这个动画是100%SkiaSharp,包含在名为glowingnumberview的skcancasview中。
它的实现是通过无限循环来进行的,具体技术描述请参考:https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/graphics/skiasharp/basics/animation.
是通过两个圆圈来改变透明半透明值来实现的。
放大的动画
CarouselView
有一个界面的特效是轮播所有正在训练的马儿,我们称之为Live Screen.
所以我们需要一个轮播视图。不幸的是Xamarin.Forms CarouselView一直都是非正式版本pre-alpha(至今都3年了):https://www.nuget.org/packages/Xamarin.Forms.CarouselView.
所以我们找到了一个开源的项目: https://github.com/alexrainman/CarouselView。
非常失望这个项目有性能问题和许多大小重置的问题,所以最终我们forking了它:https://github.com/roubachof/CarouselView. 例如,在android上,我们将PageAdapter
切换到了 FragmentStatePagerAdapter,以避免内存问题。
Horizontal/Grid-ListView
针对这个我们想要一些特定的行为:
- 对于水平布局,应将捕捉效果应用于第一个可见项。
- 使用网格布局,我们应该能够拖放项目以重新排序集合。
- 由于项目布局很复杂,我们需要一些视图预加载来平滑滚动。
Xamarin Forms ListView太简单,无法使用。
因此我们通过挖掘网络找到了这篇博客:(https://causerexception.com/2018/02/06/xamarin-forms-ultimate-horizontal-list-guide/).
遗憾的是实现太过于简单,并且还有性能问题。
最终还是不得不通过ListView下的HorizontalListView来凑合着实现效果。
实现
在android上,我们使用RecyclerView,LinearSnapHelper来实现捕捉触发效果,ItemTouchHelper来实现拖放效果。
为了提高性能,我们对item实现了异步预加载。
在iOS上,我们用了UICollectionView,拖放功能完全支持,我们使用一个简单的技巧来实现捕捉效果。
这个 Xamarin Forms ListView 的实现形式很快会被上传到Github上去。
其他渲染器
- 渐变
- Material 框架(cardView on android custom shadow on ios)
我们大部分时间都在试图创建自定义Xamarin.Forms 视图。
Xamarin Forms 动画效果
我们使用animation api实现多个视觉效果,如下:
我们也可以只用Xamarin.Forms创建一些组件,不使用其他渲染器。
TabBar (android style)
我们将这个组件分为两部分:
- TabHostView 有选中效果的选项卡
- ViewSwitcher 可显示隐藏的视图切换
通过一个可绑定的属性SelectedIndex将两个放到一起,效果如下:
TaskLoaderView
我们创建一个通用的 Xamarin.Forms component,在加载和处理视图的所有错误状态时显示ActivityLoader。
- 为空(无返回内容)
- 错误(错误消息+重试按钮)
- 刷新时出错(带错误消息的snackbar)
- 成功(返回视图)
你可以通过下面看到更详细的内容:
https://github.com/roubachof/Xamarin-Forms-Practices.
BottomBar (ios style)
设计一个自定义导航容器。首先它实际上就是个底部选项卡(tabbar),但是如果你要向上滑动这个选项卡,这个容器会占据显示在整个界面上,并显示一个menu。这个菜单可以操作,包括一些像用户的个人信息和设置功能。这个组件也就是常规用到的TabHostView
和 ViewSwitcher。只是TabHostView是放在屏幕的底部。
当组件的状态为Peek的时候,只显示在底部。
应用一个手势,我们进行简单的转换,将隐藏的menu向上移动并显示。同样的,向下滑动隐藏menu。
Lottie
(Lottie是Airbnb开源的跨平台动画库。官网:http://airbnb.io/lottie/)
我们使用lottie来代替一些太难不能实现的组件功能。
Web socket and communication
我们使用 .net的 ClientWebSocket 来实现直播服务
REST我们用到了:
- Refit
- Polly (用在我们的链接恢复策略)
- InsanePowerPack (改造http缓存)
- Fusillade (低优先级的同步服务和图片加载)
- 自定义的 in-memory cache (缓存失效这个比较难搞)
Visual Studio App Center
我们用应用程序中心来进行持续集成,每次主要推送都会构建android 和 iOS.
使用预构建脚本在分支上运行创建两个不同的应用程序。
如果我们在qa分支上创建了一个app,这个应用程序将会连接到我们的后台。如果在建个qa-mock,那么所有的数据都是模拟数据。
我们也可以通过发行版本让各类用户来测试我们的app。
我最喜欢的还是app center里面的每一个崩溃报告都包括一些text。因为在崩溃的时候,我们可以在应用程序里记录的相关日志并附加到每个报告中去。