⽼司机学新平台-XamarinForms开发框架之MvvmCross插件精选
在前两篇中,简单介绍了Xamarin开发环境的搭建以及Prism和MvvmCross这两个开发框架。不同的框架,往往不仅仅使⽤不同的架构风格,同时社区活跃度不同,各种功能模块和插件数量也会有巨⼤差别。架构风格的好坏,属于仁者见仁,但功能模块和插件的好坏多寡,却实实在在体现了社区的⼒量,是可以实打实拿出来练⼀练的。今天我们就来⼀起玩⼀玩MvvmCross提供的各种功能插件,看看哪些轮⼦可以拿来直接就⽤的。
备注:本⽂主要关注兼容Xamarin Forms xaml并且⾄少⽀持Droid和iOS平台的插件。
官⽅模块和插件
在把视线放到社区贡献的插件之前,我们先来看看,MvvmCross框架官⽅提供了哪些模块、插件:
Localization
Localization⽀持,对任何⼀个需要⽀持多国语⾔的App都必不可少。这个组件其实谈不上MvvmCross特有,基本上是。不过MvvmCross的VS项⽬模版直接包含了这个实现,并且集成到了MvvmCross⾃⼰的IoC容器的服务注册。
在MvvmCross模版⽣成的项⽬中,资源⽂件和获取device当前语⾔的ILocalizeService接⼝定义在PCL Core项⽬中:
特定平台的LocalizeService实现,定义在各⾃平台项⽬的Services⽬录中。在Setup类中默认将ILocalizeService注册成为Singleton:
在任意xaml的View中,通过{res:Translate ResourceKey}这样的格式绑定到控件的属性:
DataBinding
数据绑定,是对任何App的View的显⽰和交互必不可少的功能,这⽅⾯,,就已经⾮常强悍了。,然⽽,⼏乎所有的增强貌似都是针对⾮Xamarin Forms xaml时期的Xamarin UI 的数据绑定⽀持的,随着Forms基于xaml的本⾝的数据绑定能⼒的出现和不断增强,MvvmCross的这部分扩展其实已经过时了。
需要重点提及的是,在上⾯的LocalizeService组件的介绍中提到的{res:Translate ResourceKey}这样的语法,其实是基于Xamarin Forms官⽅的的扩展。⽽另⼀个可以⽤来扩展数据绑定的是IValueConverter接⼝。⾃定义的value converter,⼀般是⽤来在进⾏某个字段的数据的存储和数据的显⽰之间的转换的,但是,应该也可以利⽤它的语法来实现⼀些巧妙的数据绑定扩展。未来对Xamari
n Forms UI组件的数据绑定的真正扩展,我想应该是基于上⾯提到的这两个接⼝来实现。期望以后能看到各种有趣的插件。当然,⾃⼰增加扩展也很容易。
简单举⼀个⾃定义IValueConverter的例⼦。假设我实现了下⾯这个⾃定义converter,⽤来⾃定义显⽰某个model的长度属性:
上⾯这个Converter的意思是,如果⼀个字段在ViewModel⾥⾯的属性值为空,或者为0,显⽰到页⾯上时,显⽰为Default,否则,显⽰原来的值。假设ViewModel包含下⾯这个Length属性,默认值为0:
xaml页⾯的⼀个Label的Text,现在要绑定到上⾯这个Length属性,并且,使⽤上⾯这个converter:
xaml⾥稍微复杂⼀点,不过也不难理解。⾸先是定义⼀个名叫local的xmlns,然后需要将这个⾃定义的converter注册到resource中,最后,在Label的Text属性中指定绑定和converter。
Accelerometer
插件读取Accelerometer传感器的值。因为肯定要访问设备特定的API,它⾃然包含接⼝和对应不同设备的具体实现。在项⽬中启⽤⼀个MvvmCross的plugin,只需引⽤对应的nuget package。这个插件对应的package是MvvmCross.Plugin.Accelerometer,可以从nuget下载。如果为⼀个包含shared P
CL项⽬和Droid,iOS项⽬的solution添加这个插件的nuget package后,会看到,shared项⽬只⾃动引⽤了包含IMvxAccelerometer接⼝的dll,⽽Droid项⽬和iOS项⽬则还⾃动引⽤了实现了IMvxAccelerometer接⼝的平台特定的dll,并且,在Droid和iOS项⽬中的Boostrap⽬录,会⾃动添加⼀个AccelerometerPluginBootstrap类,这个类是⽤于在App启动时⾃动注册插件的,在这⾥,特定来
说,Bootstrap内部会⾃动注册IMvxAccelerometer接⼝的实现。然后,我们就可以在代码⾥访问传感器的数据了。⽐如:
当访问设备特定的api时,还需要留意是否需要申请特定的设备权限。⽐如安卓设备访问Accelerometer就需要在Droid项⽬\l⽂件中添加⼀个urs-feature如下:
可以看到,MvvmCross插件的注册机制⾮常巧妙,我⼀开始以为可能每个插件⾄少需要有shared,droid,ios这样多个nuget package,实际上,它只有⼀个nuget package,不同的项⽬类型⾃动引⽤了同⼀个package⾥的不同target的dll,⽽且还⾃动添加了Bootstrap类,在框架层⾯通过反射和命名约定⾃动载⼊了插件,使得整个插件启⽤过程不需⼿写任何代码,⾮常赞!另外,除了通过Mvx.Resolve()获取⼀个接⼝的实例之外,MvvmCross也⽀持构造函数注⼊,⽐如,可以为ViewModel的构造函数,添加⼀个IMvxAccelerometer参数,当ViewModel被IoC容器初始化时,构造函数的参数都会被⾃动注⼊。
Email
插件,⾃然是⽤来发Email了。安装nuget package:MvvmCross.Plugins.Email。它就包含了下⾯这个接⼝:
public interface IMvxCompoEmailTask
{
void CompoEmail(string to, string cc, string subject, string body, bool isHtml);
}
所以,发邮件⾄需要下⾯这样的代码,不能再简单了:
Mvx.Resolve<IMvxCompoEmailTask>()
.CompoEmail("",
string.Empty,
"MvvmCross Email",
"I <3 MvvmCross",
fal);
洋葱不能和什么一起吃File
插件,提供了读写设备本地⽂件的能⼒。nuget package:MvvmCross.Plugins.File。Droid和iOS读写⽂件时默认的当前⽬录有差异(更多其他平台的当前⽬录,请参见):
Android - Context.FilesDir
iOS - Environment.SpecialFolder.MyDocuments
实现的接⼝如下:
public interface IMvxFileStore
{
bool TryReadTextFile(string path, out string contents);
bool TryReadBinaryFile(string path, out Byte[] contents);
bool TryReadBinaryFile(string path, Func<Stream, bool> readMethod);
void WriteFile(string path, string contents);
void WriteFile(string path, IEnumerable<Byte> contents);
void WriteFile(string path, Action<Stream> writeMethod);
bool TryMove(string from, string to, bool deleteExistingTo);
bool Exists(string path);
bool FolderExists(string folderPath);
string PathCombine(string items0, string items1);
string NativePath(string path);
void EnsureFolderExists(string folderPath);
IEnumerable<string> GetFilesIn(string folderPath);
void DeleteFile(string path);
void DeleteFolder(string folderPath, bool recursive);
}
具体使⽤⽅法类似其他插件,只需要通过Mvx.Resolve()⽅法拿到instance,调⽤相应⽅法即可,这⾥就不详述了。
Location
插件,⽤来获取⽤户当前的GPS位置信息。nuget package: MvvmCross.Plugins.Location。实现了IMvxLocationWatcher接⼝:
public interface IMvxLocationWatcher
{
void Start(
MvxLocationOptions options,
Action<MvxGeoLocation> success,
Action<MvxLocationError> error);
void Stop();
bool Started { get; }
MvxGeoLocation CurrentLocation { get; }
MvxGeoLocation LastSeenLocation { get; }
event EventHandler<MvxValueEventArgs<MvxLocationPermission>> OnPermissionChanged;
}
使⽤⽅式和Accelerometer类似,就不详述了。
Mesnger
插件提供了基于内存的组件间基于消息的pub/sub功能。⾮常有⽤的⼀个插件。nuget package:Mvv
mCross.Plugins.Mesnger。这是⼀个纯PCL插件,没有平台特定代码。实现了IMvxMesnger接⼝:
public interface IMvxMesnger
{
MvxSubscriptionToken Subscribe<TMessage>(Action<TMessage> deliveryAction, MvxReference reference = MvxReference.Weak, string tag = null) where TMessage : MvxMessage;
MvxSubscriptionToken SubscribeOnMainThread<TMessage>(Action<TMessage> deliveryAction, MvxReference reference = MvxReference.Weak, string tag = null) where TMessage : MvxMessage;
MvxSubscriptionToken SubscribeOnThreadPoolThread<TMessage>(Action<TMessage> deliveryAction, MvxReference reference = MvxReference.Weak, string tag = null) where TMessage : MvxMessage;
void Unsubscribe<TMessage>(MvxSubscriptionToken mvxSubscriptionId) where TMessage : MvxMessage;
bool HasSubscriptionsFor<TMessage>() where TMessage : MvxMessage;
int CountSubscriptionsFor<TMessage>() where TMessage : MvxMessage;
bool HasSubscriptionsForTag<TMessage>(string tag) where TMessage : MvxMessage;
int CountSubscriptionsForTag<TMessage>(string tag) where TMessage : MvxMessage;
IList<string> GetSubscriptionTagsFor<TMessage>() where TMessage : MvxMessage;
void Publish<TMessage>(TMessage message) where TMessage : MvxMessage;
void Publish(MvxMessage message);
void Publish(MvxMessage message, Type messageType);
void RequestPurge(Type messageType);
void RequestPurgeAll();
}
Network
插件本来只是设计来检测某个⽹络主机是否能连通。结果,功能被热⼼⽹友不断扩充,就成了⼀个轻量级的REST Http Client。nuget package:MvvmCross.Plugins.Network。它提供了下⾯这些接⼝的实现:
public interface IMvxReachability
{
柯震东身高bool IsHostReachable(string host);
}
public interface IMvxRestClient
{
void ClearSetting(string key);
void SetSetting(string key, object value);
IMvxAbortable MakeRequest(MvxRestRequest restRequest, Action<MvxRestRespon> successAc
tion, Action<Exception> errorAction);
IMvxAbortable MakeRequest(MvxRestRequest restRequest, Action<MvxStreamRestRespon> successAction, Action<Exception> errorAction);
Task<MvxRestRespon> MakeRequestAsync(MvxRestRequest restRequest, CancellationToken cancellationToken = default(CancellationToken));
Task<MvxStreamRestRespon> MakeStreamRequestAsync(MvxRestRequest restRequest, CancellationToken cancellationToken = default(CancellationToken));
}
public interface IMvxJsonRestClient
{
Func<IMvxJsonConverter> JsonConverterProvider { get; t; }
IMvxAbortable MakeRequestFor<T>(MvxRestRequest restRequest, Action<MvxDecodedRestRespon<T>> successAction, Action<Exception> errorAction);
Task<MvxDecodedRestRespon<T>> MakeRequestForAsync<T>(MvxRestRequest restRequest, CancellationToken cancellationToken = default(CancellationToken));
}
PhoneCall
插件⾃然是⽤来打电话的。nuget package:MvvmCross.Plugins.PhoneCall。实现了IMvxPhoneCallTask接⼝:
public interface IMvxPhoneCallTask
{
void MakePhoneCall(string name, string number);
}
PictureChoor
⽤来从相册选照⽚,或者⽤相机拍照。nuget package:MvvmCross.Plugins.PictureChoor。实现
了IMvxPictureChoorTask接⼝:
public interface IMvxPictureChoorTask
{
void ChooPictureFromLibrary(int maxPixelDimension, int percentQuality, Action<Stream> pictureAvailable, Action assumeCancelled);
void TakePicture(int maxPixelDimension, int percentQuality, Action<Stream> pictureAvailable, Action assumeCancelled);
}
简单的使⽤代码如下:
var task = Mvx.Resolve<IMvxPictureChoorTask>();
task.ChooPictureFromLibrary(500, 90,
什么全其美的成语stream => {
// u the stream
// expect the stream to be dispod after immediately this method returns.
},
() => {
// perform any cancelled operation
});
Share
插件⽤来发表分享。nuget package:MvvmCross.Plugins.Share。实现了IMvxShareTask接⼝:
public interface IMvxShareTask
{
void ShareShort(string message);
void ShareLink(string title, string message, string link);
}
SQLite
插件是⼜⼀款必备插件,⽤来访问Sqlite数据库。nuget package:MvvmCross.Plugin.SQLitePCL。实现了IMvxSqliteConnectionFactory接⼝:
public interface IMvxSqliteConnectionFactory
{
SQLiteConnection GetConnection(string databaName, bool prefixPlatformPath = true);
SQLiteConnection GetConnection(SqLiteConfig config, bool prefixPlatformPath = true);
SQLiteAsyncConnection GetAsyncConnection(string databaName, bool prefixPlatformPath = true);
SQLiteAsyncConnection GetAsyncConnection(SqLiteConfig config, bool prefixPlatformPath = true);
}
WebBrowr
插件调⽤外部浏览器显⽰⼀个页⾯。nuget package:MvvmCross.Plugins.WebBrowr。实现了IMvxWebBrowrTask接⼝:
public interface IMvxWebBrowrTask
{
鸡蛋红糖水void ShowWebPage(string url);
}
MvvmCross提供的官⽅插件,推荐的就这些了。貌似还有点不过瘾,好像很多期望的必备功能还没看到?下⾯来看看社区的贡献。
第三⽅插件
Settings
插件提供了⼀个通⽤⽅案,⽤来持久化保存key/value键值对到各平台默认的App本地配置⽂件。nuget package:Cheebaron.MvxPlugins.Settings。实现了ISettings接⼝:public interface ISettings
{
/// <param name="roaming">Roam ttings (only for WindowsCommon)</param>
T GetValue<T>(string key, T defaultValue = default(T), bool roaming = fal);
bool AddOrUpdateValue<T>(string key, T value = default(T), bool roaming = fal);
bool DeleteValue(string key, bool roaming = fal);
bool Contains(string key, bool roaming = fal);
bool ClearAllValues(bool roaming = fal);
}
DeviceInfo
插件⽀持获取包括设备分辨率,设备id,固件版本,内存⼤⼩等等设备信息。nuget package:Cheebaron.MvxPlugins.DeviceInfo。实现了IDeviceInfo和IDisplay接⼝:public interface IDeviceInfo
{
string DeviceId { get; }
string Name { get; }
string FirmwareVersion { get; }
string HardwareVersion { get; }
string Manufacturer { get; }
string LanguageCode { get; }
double TimeZoneOfft { get; }
string TimeZone { get; }
Orientation Orientation { get; }
long TotalMemory { get; }
bool IsTablet { get; }
DeviceType DeviceType { get; }
}
public interface IDisplay
{
int Height { get; }
int Width { get; }
double Xdpi { get; }
double Ydpi { get; }
double Scale { get; }
}
Connectivity
插件返回当前设备是否联⽹,当前使⽤WIFI还是移动⽹络。nuget package:Cheebaron.MvxPlugins.Connectivity。实现了IConnectivity接⼝:
秋葵炒鸡蛋的做法
public interface IConnectivity: INotifyPropertyChanged
{
bool IsConnected { get; }
bool IsWifi { get; }
bool IsCellular { get; }
Task<bool> GetHostReachableAsync(string host, CancellationToken token = default(CancellationToken));
}
SMS
插⽤来发送短信。nuget package:Cheebaron.MvxPlugins.SMS。实现了ISmsTask接⼝:
public interface ISmsTask
{
void SendSMS(string body, string phoneNumber);
}
我的课堂SecureStorage
插⽤⽤户保存敏感数据key/value键值对到平台特定的安全数据存储,⽐如Keychain,Password Vault等。nuget package:Beezy.MvvmCross.Plugins.SecureStorage。实现了IMvxProtectedData接⼝:
public interface IMvxProtectedData
{
void Protect(string key, string value);
string Unprotect(string key);
void Remove(string key);
}
InfiniteScrollPlugin
插⽤提供了⼀个能够根据指定的数据源,⼀直向下滚动分页显⽰数据的能⼒。nuget package:Sequence.Plugins.InfiniteScroll。实现了IIncrementalCollectionFactory接⼝。典型的⽰例可以参见。
Fingerprint
提供指纹识别验证⽀持。nuget package:MvvmCross.Plugins.Fingerprint。实现了IFingerprint接⼝:
Task<FingerprintAvailability> GetAvailabilityAsync();
Task<bool> IsAvailableAsync();
Task<FingerprintAuthenticationResult> AuthenticateAsync(string reason, CancellationToken cancellationToken = default(CancellationToken));
Task<FingerprintAuthenticationResult> AuthenticateAsync(AuthenticationRequestConfiguration authRequestConfig, CancellationToken cancellationToken = default(CancellationToken));
调⽤IFingerprint接⼝的⽰例:
var fpService = Mvx.Resolve<IFingerprint>(); // or u dependency injection and inject IFingerprint
var result = await fpService.AuthenticateAsync("Prove you have mvx fingers!");
if (result.Authenticated)
{
// do cret stuff :)
}
el
{
// not allowed to do cret stuff :(
}
UrInteraction
插件实现了最常⽤的⼏个UrDialogs,包括:Alert,Confirm和Input。nuget package:Chance.MvvmCross.Plugins.UrInteraction。它实现了IUrInteraction接⼝:
public interface IUrInteraction
{
void Confirm(string message, Action okClicked, string title = null, string okButton = "OK", string cancelButton = "Cancel");
void Confirm(string message, Action<bool> answer, string title = null, string okButton = "OK", string cancelButton = "Cancel");
Task<bool> ConfirmAsync(string message, string title = "", string okButton = "OK", string cancelButton = "Cancel");
void Alert(string message, Action done = null, string title = "", string okButton = "OK");
Task AlertAsync(string message, string title = "", string okButton = "OK");
void Input(string message, Action<string> okClicked, string placeholder = null, string title = null, string okButton = "OK", string cancelButton = "Cancel", string initialText = null);
void Input(string message, Action<bool, string> answer, string placeholder = null, string title = null, string okButton = "OK", string cancelButton = "Cancel", string initialText = null);
Task<InputRespon> InputAsync(string message, string placeholder = null, string title = null, string okButton = "OK", string cancelButton = "Cancel", string initialText = null);
void ConfirmThreeButtons(string message, Action<ConfirmThreeButtonsRespon> answer, string title = null, string positive = "Yes", string negative = "No", string neutral = "Maybe");
Task<ConfirmThreeButtonsRespon> ConfirmThreeButtonsAsync(string message, string title = null, string positive = "Yes", string negative = "No", string neutral = "Maybe");
}
Ur Dialogs
严格来说并不是⼀个标准的MvvmCross插件。但是它提供了相⽐上⾯的UrInteraction更丰富的Ur Dialogs功能,所以,有需要的朋友也可以看⼀下。nuget package: Acr.UrDialogs。它可以和MvvmCrossy⼀起⼯作,但是没有follow MvvmCross插件⾃动初始化机制,因此,需要特别配置。引⽤package之后,需要在App类中注册IUrDialog到Mvx的IoC容器:
大一自我介绍Mvx.RegisterSingleton<IUrDialogs>(() => UrDialogs.Instance);
同时,对于Droid项⽬(iOS项⽬不需要),还必须在MvxFormsApplicationActivity类的OnCreate()⽅法⾥,执⾏下⾯的初始化代码:
UrDialogs.Init(this);
然后,就可以在任意Command⾥调⽤IUrDialogs接⼝的⽅法弹出Dialogs了。IUrDialogs接⼝⽀持下⾯这些Dialogs⽅法:
public interface IUrDialogs
{
IDisposable Alert(string message, string title = null, string okText = null);
IDisposable Alert(AlertConfig config);
Task AlertAsync(string message, string title = null, string okText = null, CancellationToken? cancelToken = null);
Task AlertAsync(AlertConfig config, CancellationToken? cancelToken = null);
IDisposable ActionSheet(ActionSheetConfig config);
Task<string> ActionSheetAsync(string title, string cancel, string destructive, CancellationToken? cancelToken = null, params string[] buttons);
IDisposable Confirm(ConfirmConfig config);
Task<bool> ConfirmAsync(string message, string title = null, string okText = null, string cancelText = null, CancellationToken? cancelToken = null);
Task<bool> ConfirmAsync(ConfirmConfig config, CancellationToken? cancelToken = null);
IDisposable DatePrompt(DatePromptConfig config);
Task<DatePromptResult> DatePromptAsync(DatePromptConfig config, CancellationToken? cancelToken = null);
Task<DatePromptResult> DatePromptAsync(string title = null, DateTime? lectedDate = null, CancellationToken? cancelToken = null);
IDisposable TimePrompt(TimePromptConfig config);
Task<TimePromptResult> TimePromptAsync(TimePromptConfig config, CancellationToken? cancelToken = null);
Task<TimePromptResult> TimePromptAsync(string title = null, TimeSpan? lectedTime = null, CancellationToken? cancelToken = null);
IDisposable Prompt(PromptConfig config);
Task<PromptResult> PromptAsync(string message, string title = null, string okText = null, string cancelText = null, string placeholder = "", InputType inputType = InputType.Default, CancellationToken? cancelToken = null); Task<PromptResult> PromptAsync(PromptConfig config, CancellationToken? cancelToken = null);
IDisposable Login(LoginConfig config);
Task<LoginResult> LoginAsync(string title = null, string message = null, CancellationToken? cancelToken = null);
Task<LoginResult> LoginAsync(LoginConfig config, CancellationToken? cancelToken = null);
IProgressDialog Progress(ProgressDialogConfig config);
IProgressDialog Loading(string title = null, Action onCancel = null, string cancelText = null, bool show = true, MaskType? maskType = null);
IProgressDialog Progress(string title = null, Action onCancel = null, string cancelText = null, bool show = true, MaskType? maskType = null);
void ShowLoading(string title = null, MaskType? maskType = null);
void HideLoading();
void ShowImage(IBitmap image, string message, int timeoutMillis = 2000);
void ShowSuccess(string message, int timeoutMillis = 2000);
void ShowError(string message, int timeoutMillis = 2000);
IDisposable Toast(string title, TimeSpan? dismissTimer = null);
IDisposable Toast(ToastConfig cfg);
}
AudioPlay
⽐较遗憾的是没有看到⽀持⾳乐播放(⽐如播放在线或本地的mp3/wav)的插件。这个应该是⼤多数App都需要的功能。搜索了⼀些⽹上的实现⽅案,先给需要的朋友做个参
考,要不下次咱把它改造成标准的MvvmCross插件?
2016-10-22 Update: 兑现设想,
2016-10-23 Update: 新增⼀些⾮MvvmCross,但是Xamarin下的通⽤第三⽅组件:
- 看上去是Xamarin界最⼤的⼀个第三⽅组件库,包含了⼤量UI和Service等类型的好东西,组件列表太长了,不贴在这⾥,⼤家可以点过去看。
- DL Toolkit提供的⼏个⾮常酷的组件,包括图⽚转换、增强的ListView和tag标签功能等。
- ⽼牌⼤⼚DevExpress出的Xamarin Forms数据表格控件,功能强⼤,竟然完全免费?!
暂时就这些了。如果您发现有别的优秀插件,欢迎推荐,我也会持续补充进来。
>香港复活节