老司機學新平台 - Xamarin Forms開發框架之MvvmCross插件精選


在前兩篇老司機學Xamarin系列中,簡單介紹了Xamarin開發環境的搭建以及Prism和MvvmCross這兩個開發框架。不同的框架,往往不僅僅使用不同的架構風格,同時社區活躍度不同,各種功能模塊和插件數量也會有巨大差別。架構風格的好壞,屬於仁者見仁,但功能模塊和插件的好壞多寡,卻實實在在體現了社區的力量,是可以實打實拿出來練一練的。今天我們就來一起玩一玩MvvmCross提供的各種功能插件,看看哪些輪子可以拿來直接就用的。

備注:本文主要關注兼容Xamarin Forms xaml並且至少支持Droid和iOS平台的插件。

官方模塊和插件

在把視線放到社區貢獻的插件之前,我們先來看看,MvvmCross框架官方提供了哪些模塊、插件:

Localization

Localization支持,對任何一個需要支持多國語言的App都必不可少。這個組件其實談不上MvvmCross特有,基本上是Xamarin官方文檔推薦的最佳實踐。不過MvvmCross的VS項目模版直接包含了這個實現,並且集成到了MvvmCross自己的IoC容器的服務注冊。

在MvvmCross模版生成的項目中,資源文件和獲取device當前語言的ILocalizeService接口定義在PCL Core項目中:

特定平台的LocalizeService實現,定義在各自平台項目的Services目錄中。在Setup類中默認將ILocalizeService注冊成為Singleton:

在任意xaml的View中,通過{res:Translate ResourceKey}這樣的格式綁定到控件的屬性:

DataBinding

數據綁定,是對任何App的View的顯示和交互必不可少的功能,這方面,Xamarin Forms本身的支持,就已經非常強悍了。MvvmCross有一個頁面專門介紹它對數據綁定的增強,然而,幾乎所有的增強貌似都是針對非Xamarin Forms xaml時期的Xamarin UI的數據綁定支持的,隨着Forms基於xaml的本身的數據綁定能力的出現和不斷增強,MvvmCross的這部分擴展其實已經過時了。

需要重點提及的是,在上面的LocalizeService組件的介紹中提到的{res:Translate ResourceKey}這樣的語法,其實是基於Xamarin Forms官方的IMarkupExtension接口的擴展。而另一個可以用來擴展數據綁定的是IValueConverter接口。自定義的value converter,一般是用來在進行某個字段的數據的存儲和數據的顯示之間的轉換的,但是,應該也可以利用它的語法來實現一些巧妙的數據綁定擴展。未來對Xamarin 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插件讀取Accelerometer傳感器的值。因為肯定要訪問設備特定的API,它自然包含接口和對應不同設備的具體實現。在項目中啟用一個MvvmCross的plugin,只需引用對應的nuget package。這個插件對應的package是MvvmCross.Plugin.Accelerometer,可以從nuget.org下載。如果為一個包含shared PCL項目和Droid,iOS項目的solution添加這個插件的nuget package后,會看到,shared項目只自動引用了包含IMvxAccelerometer接口的dll,而Droid項目和iOS項目則還自動引用了實現了IMvxAccelerometer接口的平台特定的dll,並且,在Droid和iOS項目中的Boostrap目錄,會自動添加一個AccelerometerPluginBootstrap類,這個類是用於在App啟動時自動注冊插件的,在這里,特定來說,Bootstrap內部會自動注冊IMvxAccelerometer接口的實現。然后,我們就可以在代碼里訪問傳感器的數據了。比如:

當訪問設備特定的api時,還需要留意是否需要申請特定的設備權限。比如安卓設備訪問Accelerometer就需要在Droid項目\Properties\AndroidManifest.xml文件中添加一個users-feature如下:

可以看到,MvvmCross插件的注冊機制非常巧妙,我一開始以為可能每個插件至少需要有shared,droid,ios這樣多個nuget package,實際上,它只有一個nuget package,不同的項目類型自動引用了同一個package里的不同target的dll,而且還自動添加了Bootstrap類,在框架層面通過反射和命名約定自動載入了插件,使得整個插件啟用過程不需手寫任何代碼,非常贊!另外,除了通過Mvx.Resolve()獲取一個接口的實例之外,MvvmCross也支持構造函數注入,比如,可以為ViewModel的構造函數,添加一個IMvxAccelerometer參數,當ViewModel被IoC容器初始化時,構造函數的參數都會被自動注入。

Email

Email插件,自然是用來發Email了。安裝nuget package:MvvmCross.Plugins.Email。它就包含了下面這個接口:

public interface IMvxComposeEmailTask
{
    void ComposeEmail(string to, string cc, string subject, string body, bool isHtml);
}

所以,發郵件至需要下面這樣的代碼,不能再簡單了:

Mvx.Resolve<IMvxComposeEmailTask>()
   .ComposeEmail("me@slodge.com", 
                 string.Empty, 
                 "MvvmCross Email",
                 "I <3 MvvmCross",
                 false);

File

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

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類似,就不詳述了。

Messenger

Messenger插件提供了基於內存的組件間基於消息的pub/sub功能。非常有用的一個插件。nuget package:MvvmCross.Plugins.Messenger。這是一個純PCL插件,沒有平台特定代碼。實現了IMvxMessenger接口:

public interface IMvxMessenger
{
	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

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<MvxRestResponse> successAction, Action<Exception> errorAction);
	IMvxAbortable MakeRequest(MvxRestRequest restRequest, Action<MvxStreamRestResponse> successAction, Action<Exception> errorAction);

	Task<MvxRestResponse> MakeRequestAsync(MvxRestRequest restRequest, CancellationToken cancellationToken = default(CancellationToken));
	Task<MvxStreamRestResponse> MakeStreamRequestAsync(MvxRestRequest restRequest, CancellationToken cancellationToken = default(CancellationToken));
}

public interface IMvxJsonRestClient
{
	Func<IMvxJsonConverter> JsonConverterProvider { get; set; }

	IMvxAbortable MakeRequestFor<T>(MvxRestRequest restRequest, Action<MvxDecodedRestResponse<T>> successAction, Action<Exception> errorAction);

	Task<MvxDecodedRestResponse<T>> MakeRequestForAsync<T>(MvxRestRequest restRequest, CancellationToken cancellationToken = default(CancellationToken));
}

PhoneCall

PhoneCall插件自然是用來打電話的。nuget package:MvvmCross.Plugins.PhoneCall。實現了IMvxPhoneCallTask接口:

public interface IMvxPhoneCallTask
{
    void MakePhoneCall(string name, string number);
}

PictureChooser

PictureChooser用來從相冊選照片,或者用相機拍照。nuget package:MvvmCross.Plugins.PictureChooser。實現了IMvxPictureChooserTask接口:

public interface IMvxPictureChooserTask
{
    void ChoosePictureFromLibrary(int maxPixelDimension, int percentQuality, Action<Stream> pictureAvailable, Action assumeCancelled);

    void TakePicture(int maxPixelDimension, int percentQuality, Action<Stream> pictureAvailable, Action assumeCancelled);
}

簡單的使用代碼如下:

var task = Mvx.Resolve<IMvxPictureChooserTask>();
task.ChoosePictureFromLibrary(500, 90,
       stream => {
           // use the stream
           // expect the stream to be disposed after immediately this method returns.
       },
       () => {
           // perform any cancelled operation
       });

Share

Share插件用來發表分享。nuget package:MvvmCross.Plugins.Share。實現了IMvxShareTask接口:

public interface IMvxShareTask
{
    void ShareShort(string message);
    void ShareLink(string title, string message, string link);
}

SQLite

SQLite插件是又一款必備插件,用來訪問Sqlite數據庫。nuget package:MvvmCross.Plugin.SQLitePCL。實現了IMvxSqliteConnectionFactory接口:

public interface IMvxSqliteConnectionFactory
{
    SQLiteConnection GetConnection(string databaseName, bool prefixPlatformPath = true);
    SQLiteConnection GetConnection(SqLiteConfig config, bool prefixPlatformPath = true);
    SQLiteAsyncConnection GetAsyncConnection(string databaseName, bool prefixPlatformPath = true);
    SQLiteAsyncConnection GetAsyncConnection(SqLiteConfig config, bool prefixPlatformPath = true);
}

WebBrowser

WebBrowser插件調用外部瀏覽器顯示一個頁面。nuget package:MvvmCross.Plugins.WebBrowser。實現了IMvxWebBrowserTask接口:

public interface IMvxWebBrowserTask
{
    void ShowWebPage(string url);
}

MvvmCross提供的官方插件,推薦的就這些了。貌似還有點不過癮,好像很多期望的必備功能還沒看到?下面來看看社區的貢獻。

第三方插件

Settings

Settings插件提供了一個通用方案,用來持久化保存key/value鍵值對到各平台默認的App本地配置文件。nuget package:Cheesebaron.MvxPlugins.Settings。實現了ISettings接口:

public interface ISettings
{
	/// <param name="roaming">Roam settings (only for WindowsCommon)</param>
	
	T GetValue<T>(string key, T defaultValue = default(T), bool roaming = false);
	bool AddOrUpdateValue<T>(string key, T value = default(T), bool roaming = false);
	bool DeleteValue(string key, bool roaming = false);
	bool Contains(string key, bool roaming = false);
	bool ClearAllValues(bool roaming = false);
}

DeviceInfo

DeviceInfo插件支持獲取包括設備分辨率,設備id,固件版本,內存大小等等設備信息。nuget package:Cheesebaron.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 TimeZoneOffset { 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

Connectivity插件返回當前設備是否聯網,當前使用WIFI還是移動網絡。nuget package:Cheesebaron.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

SMS插用來發送短信。nuget package:Cheesebaron.MvxPlugins.SMS。實現了ISmsTask接口:

public interface ISmsTask
{
	void SendSMS(string body, string phoneNumber);
}

SecureStorage

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

InfiniteScrollPlugin插用提供了一個能夠根據指定的數據源,一直向下滾動分頁顯示數據的能力。nuget package:Sequence.Plugins.InfiniteScroll。實現了IIncrementalCollectionFactory接口。典型的示例可以參見這里

Fingerprint

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 use dependency injection and inject IFingerprint

var result = await fpService.AuthenticateAsync("Prove you have mvx fingers!");
if (result.Authenticated)
{
    // do secret stuff :)
}
else
{
    // not allowed to do secret stuff :(
}

UserInteraction

UserInteraction插件實現了最常用的幾個UserDialogs,包括:Alert,Confirm和Input。nuget package:Chance.MvvmCross.Plugins.UserInteraction。它實現了IUserInteraction接口:

public interface IUserInteraction
{
	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<InputResponse> InputAsync(string message, string placeholder = null, string title = null, string okButton = "OK", string cancelButton = "Cancel", string initialText = null);

	void ConfirmThreeButtons(string message, Action<ConfirmThreeButtonsResponse> answer, string title = null, string positive = "Yes", string negative = "No", string neutral = "Maybe");
	Task<ConfirmThreeButtonsResponse> ConfirmThreeButtonsAsync(string message, string title = null, string positive = "Yes", string negative = "No", string neutral = "Maybe");
}

User Dialogs

User Dialogs嚴格來說並不是一個標准的MvvmCross插件。但是它提供了相比上面的UserInteraction更豐富的User Dialogs功能,所以,有需要的朋友也可以看一下。nuget package: Acr.UserDialogs。它可以和MvvmCrossy一起工作,但是沒有follow MvvmCross插件自動初始化機制,因此,需要特別配置。引用package之后,需要在App類中注冊IUserDialog到Mvx的IoC容器:

Mvx.RegisterSingleton<IUserDialogs>(() => UserDialogs.Instance);

同時,對於Droid項目(iOS項目不需要),還必須在MvxFormsApplicationActivity類的OnCreate()方法里,執行下面的初始化代碼:

UserDialogs.Init(this);

然后,就可以在任意Command里調用IUserDialogs接口的方法彈出Dialogs了。IUserDialogs接口支持下面這些Dialogs方法:

public interface IUserDialogs
{
	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? selectedDate = null, CancellationToken? cancelToken = null);

	IDisposable TimePrompt(TimePromptConfig config);
	Task<TimePromptResult> TimePromptAsync(TimePromptConfig config, CancellationToken? cancelToken = null);
	Task<TimePromptResult> TimePromptAsync(string title = null, TimeSpan? selectedTime = 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: 兌現設想,我的第一個MvvmCross跨平台插件:SimpleAudioPlayer

2016-10-23 Update: 新增一些非MvvmCross,但是Xamarin下的通用第三方組件:

  • Xamarin-Forms-Labs - 看上去是Xamarin界最大的一個第三方組件庫,包含了大量UI和Service等類型的好東西,組件列表太長了,不貼在這里,大家可以點過去看。
  • DLToolkit.Forms.Controls - DL Toolkit提供的幾個非常酷的組件,包括圖片轉換、增強的ListView和tag標簽功能等。
  • DevExpress-Grid - 老牌大廠DevExpress出的Xamarin Forms數據表格控件,功能強大,竟然完全免費?!

暫時就這些了。如果您發現有別的優秀插件,歡迎推薦,我也會持續補充進來。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM