0
点赞
收藏
分享

微信扫一扫

Xamarin.Forms读取并展示Android和iOS通讯录 - TerminalMACS客户端

年夜雪 2022-10-06 阅读 190

阅读导航:

  • 一、功能说明
  • 二、代码实现
  • 三、源码获取
  • 四、参考资料
  • 五、后面计划

一、功能说明

Xamarin.Forms读取并展示Android和iOS通讯录 - TerminalMACS客户端_xamarin

转存失败重新上传取消

完整思维导图:https://github.com/dotnet9/TerminalMACS/blob/master/docs/TerminalMACS.xmind 

本文介绍图中右侧画红圈处的功能,即使用Xamarin.Forms获取和展示Android和iOS的通讯录信息,下面是最终效果,由于使用的是真实手机,所以联系人姓名及电话号码打码显示。

Xamarin.Forms读取并展示Android和iOS通讯录 - TerminalMACS客户端_ico_02

转存失败重新上传取消

并简单的进行了搜索功能处理,之所以说简单,是因为通讯录列表是全部读取出来了,搜索是直接从此列表进行过滤的。

下图来自:https://www.xamboy.com/2019/10/10/getting-phone-contacts-in-xamarin-forms/,本功能是参考此文所写,所以直接引用文中的图片。

Xamarin.Forms读取并展示Android和iOS通讯录 - TerminalMACS客户端_xamarin_03

Xamarin.Forms读取并展示Android和iOS通讯录 - TerminalMACS客户端_android_04

转存失败重新上传取消

二、代码实现

1、共享库工程创建联系人实体类:Contacts.cs

  1. namespace TerminalMACS.Clients.App.Models
  2. {
  3. /// <summary>
  4. /// 通讯录
  5. /// </summary>
  6. public class Contact
  7. {
  8. /// <summary>
  9. /// 获取或者设置名称
  10. /// </summary>
  11. public string Name { get; set; }
  12. /// <summary>
  13. /// 获取或者设置 头像
  14. /// </summary>
  15. public string Image { get; set; }
  16. /// <summary>
  17. /// 获取或者设置 邮箱地址
  18. /// </summary>
  19. public string[] Emails { get; set; }
  20. /// <summary>
  21. /// 获取或者设置 手机号码
  22. /// </summary>
  23. public string[] PhoneNumbers { get; set; }
  24. }
  25. }

2、共享库创建通讯录服务接口:IContactsService.cs

包括:

  • 一个通讯录获取请求接口:RetrieveContactsAsync
  • 一个读取一条通讯结果通知事件:OnContactLoaded
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Threading;
  4. using System.Threading.Tasks;
  5. using TerminalMACS.Clients.App.Models;

  6. namespace TerminalMACS.Clients.App.Services
  7. {
  8. /// <summary>
  9. /// 通讯录事件参数
  10. /// </summary>
  11. public class ContactEventArgs:EventArgs
  12. {
  13. public Contact Contact { get; }
  14. public ContactEventArgs(Contact contact)
  15. {
  16. Contact = contact;
  17. }
  18. }

  19. /// <summary>
  20. /// 通讯录服务接口,android和iOS终端具体的通讯录获取服务需要继承此接口
  21. /// </summary>
  22. public interface IContactsService
  23. {
  24. /// <summary>
  25. /// 读取一条数据通知
  26. /// </summary>
  27. event EventHandler<ContactEventArgs> OnContactLoaded;
  28. /// <summary>
  29. /// 是否正在加载
  30. /// </summary>
  31. bool IsLoading { get; }
  32. /// <summary>
  33. /// 尝试获取所有通讯录
  34. /// </summary>
  35. /// <param name="token"></param>
  36. /// <returns></returns>
  37. Task<IList<Contact>> RetrieveContactsAsync(CancellationToken? token = null);
  38. }
  39. }

3、iOS工程中添加通讯录服务,实现IContactsService接口:

  1. using Contacts;
  2. using Foundation;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.IO;
  6. using System.Linq;
  7. using System.Threading;
  8. using System.Threading.Tasks;
  9. using TerminalMACS.Clients.App.Models;
  10. using TerminalMACS.Clients.App.Services;

  11. namespace TerminalMACS.Clients.App.iOS.Services
  12. {
  13. /// <summary>
  14. /// 通讯录获取服务
  15. /// </summary>
  16. public class ContactsService : NSObject, IContactsService
  17. {
  18. const string ThumbnailPrefix = "thumb";

  19. bool requestStop = false;

  20. public event EventHandler<ContactEventArgs> OnContactLoaded;

  21. bool _isLoading = false;
  22. public bool IsLoading => _isLoading;

  23. /// <summary>
  24. /// 异步请求权限
  25. /// </summary>
  26. /// <returns></returns>
  27. public async Task<bool> RequestPermissionAsync()
  28. {
  29. var status = CNContactStore.GetAuthorizationStatus(CNEntityType.Contacts);

  30. Tuple<bool, NSError> authotization = new Tuple<bool, NSError>(status == CNAuthorizationStatus.Authorized, null);

  31. if (status == CNAuthorizationStatus.NotDetermined)
  32. {
  33. using (var store = new CNContactStore())
  34. {
  35. authotization = await store.RequestAccessAsync(CNEntityType.Contacts);
  36. }
  37. }
  38. return authotization.Item1;

  39. }

  40. /// <summary>
  41. /// 异步请求通讯录,此方法由界面真正调用
  42. /// </summary>
  43. /// <param name="cancelToken"></param>
  44. /// <returns></returns>
  45. public async Task<IList<Contact>> RetrieveContactsAsync(CancellationToken? cancelToken = null)
  46. {
  47. requestStop = false;

  48. if (!cancelToken.HasValue)
  49. cancelToken = CancellationToken.None;

  50. // 我们创建了一个十进制的TaskCompletionSource
  51. var taskCompletionSource = new TaskCompletionSource<IList<Contact>>();

  52. // 在cancellationToken中注册lambda
  53. cancelToken.Value.Register(() =>
  54. {
  55. // 我们收到一条取消消息,取消TaskCompletionSource.Task
  56. requestStop = true;
  57. taskCompletionSource.TrySetCanceled();
  58. });

  59. _isLoading = true;

  60. var task = LoadContactsAsync();

  61. // 等待两个任务中的第一个任务完成
  62. var completedTask = await Task.WhenAny(task, taskCompletionSource.Task);
  63. _isLoading = false;

  64. return await completedTask;

  65. }

  66. /// <summary>
  67. /// 异步加载通讯录,具体的通讯录读取方法
  68. /// </summary>
  69. /// <returns></returns>
  70. async Task<IList<Contact>> LoadContactsAsync()
  71. {
  72. IList<Contact> contacts = new List<Contact>();
  73. var hasPermission = await RequestPermissionAsync();
  74. if (hasPermission)
  75. {

  76. NSError error = null;
  77. var keysToFetch = new[] { CNContactKey.PhoneNumbers, CNContactKey.GivenName, CNContactKey.FamilyName, CNContactKey.EmailAddresses, CNContactKey.ImageDataAvailable, CNContactKey.ThumbnailImageData };

  78. var request = new CNContactFetchRequest(keysToFetch: keysToFetch);
  79. request.SortOrder = CNContactSortOrder.GivenName;

  80. using (var store = new CNContactStore())
  81. {
  82. var result = store.EnumerateContacts(request, out error, new CNContactStoreListContactsHandler((CNContact c, ref bool stop) =>
  83. {

  84. string path = null;
  85. if (c.ImageDataAvailable)
  86. {
  87. path = path = Path.Combine(Path.GetTempPath(), $"{ThumbnailPrefix}-{Guid.NewGuid()}");

  88. if (!File.Exists(path))
  89. {
  90. var imageData = c.ThumbnailImageData;
  91. imageData?.Save(path, true);


  92. }
  93. }

  94. var contact = new Contact()
  95. {
  96. Name = string.IsNullOrEmpty(c.FamilyName) ? c.GivenName : $"{c.GivenName} {c.FamilyName}",
  97. Image = path,
  98. PhoneNumbers = c.PhoneNumbers?.Select(p => p?.Value?.StringValue).ToArray(),
  99. Emails = c.EmailAddresses?.Select(p => p?.Value?.ToString()).ToArray(),

  100. };

  101. if (!string.IsNullOrWhiteSpace(contact.Name))
  102. {
  103. OnContactLoaded?.Invoke(this, new ContactEventArgs(contact));

  104. contacts.Add(contact);
  105. }

  106. stop = requestStop;

  107. }));
  108. }
  109. }

  110. return contacts;
  111. }


  112. }
  113. }

4、在iOS工程中的Info.plist文件添加通讯录权限使用说明

Xamarin.Forms读取并展示Android和iOS通讯录 - TerminalMACS客户端_ico_05

转存失败重新上传取消

5、在Android工程中添加读取通讯录权限配置:AndroidManifest.xml

  1. <uses-permission android:name="android.permission.READ_CONTACTS"/>

完整权限配置如下

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.companyname.terminalmacs.clients.app">
  3. <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28" />
  4. <application android:label="TerminalMACS.Clients.App.Android"></application>
  5. <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
  6. <uses-permission android:name="android.permission.READ_CONTACTS"/>
  7. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  8. </manifest>

6、在Android工程中添加通讯录服务,实现IContactServer接口:ContactsService.cs

  1. using Acr.UserDialogs;
  2. using Android;
  3. using Android.App;
  4. using Android.Content;
  5. using Android.Content.PM;
  6. using Android.Database;
  7. using Android.Provider;
  8. using Android.Runtime;
  9. using Android.Support.V4.App;
  10. using Plugin.CurrentActivity;
  11. using System;
  12. using System.Collections.Generic;
  13. using System.IO;
  14. using System.Linq;
  15. using System.Threading;
  16. using System.Threading.Tasks;
  17. using TerminalMACS.Clients.App.Models;
  18. using TerminalMACS.Clients.App.Services;

  19. namespace TerminalMACS.Clients.App.Droid.Services
  20. {
  21. /// <summary>
  22. /// 通讯录获取服务
  23. /// </summary>
  24. public class ContactsService : IContactsService
  25. {
  26. const string ThumbnailPrefix = "thumb";
  27. bool stopLoad = false;
  28. static TaskCompletionSource<bool> contactPermissionTcs;
  29. public string TAG
  30. {
  31. get
  32. {
  33. return "MainActivity";
  34. }
  35. }
  36. bool _isLoading = false;
  37. public bool IsLoading => _isLoading;
  38. //权限请求状态码
  39. public const int RequestContacts = 1239;
  40. /// <summary>
  41. /// 获取通讯录需要的请求权限
  42. /// </summary>
  43. static string[] PermissionsContact = {
  44. Manifest.Permission.ReadContacts
  45. };

  46. public event EventHandler<ContactEventArgs> OnContactLoaded;
  47. /// <summary>
  48. /// 异步请求通讯录权限
  49. /// </summary>
  50. async void RequestContactsPermissions()
  51. {
  52. //检查是否可以弹出申请读、写通讯录权限
  53. if (ActivityCompat.ShouldShowRequestPermissionRationale(CrossCurrentActivity.Current.Activity, Manifest.Permission.ReadContacts)
  54. || ActivityCompat.ShouldShowRequestPermissionRationale(CrossCurrentActivity.Current.Activity, Manifest.Permission.WriteContacts))
  55. {
  56. // 如果未授予许可,请向用户提供其他理由用户将从使用权限的附加上下文中受益。
  57. // 例如,如果请求先前被拒绝。
  58. await UserDialogs.Instance.AlertAsync("通讯录权限", "此操作需要“通讯录”权限", "确定");
  59. }
  60. else
  61. {
  62. // 尚未授予通讯录权限。直接请求这些权限。
  63. ActivityCompat.RequestPermissions(CrossCurrentActivity.Current.Activity, PermissionsContact, RequestContacts);
  64. }
  65. }

  66. /// <summary>
  67. /// 收到用户响应请求权限操作后的结果
  68. /// </summary>
  69. /// <param name="requestCode"></param>
  70. /// <param name="permissions"></param>
  71. /// <param name="grantResults"></param>
  72. public static void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
  73. {
  74. if (requestCode == RequestContacts)
  75. {
  76. // 我们请求了多个通讯录权限,因此需要检查相关的所有权限
  77. if (PermissionUtil.VerifyPermissions(grantResults))
  78. {
  79. // 已授予所有必需的权限,显示联系人片段。
  80. contactPermissionTcs.TrySetResult(true);
  81. }
  82. else
  83. {
  84. contactPermissionTcs.TrySetResult(false);
  85. }

  86. }
  87. }

  88. /// <summary>
  89. /// 异步请求权限
  90. /// </summary>
  91. /// <returns></returns>
  92. public async Task<bool> RequestPermissionAsync()
  93. {
  94. contactPermissionTcs = new TaskCompletionSource<bool>();

  95. // 验证是否已授予所有必需的通讯录权限。
  96. if (Android.Support.V4.Content.ContextCompat.CheckSelfPermission(CrossCurrentActivity.Current.Activity, Manifest.Permission.ReadContacts) != (int)Permission.Granted
  97. || Android.Support.V4.Content.ContextCompat.CheckSelfPermission(CrossCurrentActivity.Current.Activity, Manifest.Permission.WriteContacts) != (int)Permission.Granted)
  98. {
  99. // 尚未授予通讯录权限。
  100. RequestContactsPermissions();
  101. }
  102. else
  103. {
  104. // 已授予通讯录权限。
  105. contactPermissionTcs.TrySetResult(true);
  106. }

  107. return await contactPermissionTcs.Task;
  108. }

  109. /// <summary>
  110. /// 异步请求通讯录,此方法由界面真正调用
  111. /// </summary>
  112. /// <param name="cancelToken"></param>
  113. /// <returns></returns>
  114. public async Task<IList<Contact>> RetrieveContactsAsync(CancellationToken? cancelToken = null)
  115. {
  116. stopLoad = false;

  117. if (!cancelToken.HasValue)
  118. cancelToken = CancellationToken.None;

  119. // 我们创建了一个十进制的TaskCompletionSource
  120. var taskCompletionSource = new TaskCompletionSource<IList<Contact>>();

  121. // 在cancellationToken中注册lambda
  122. cancelToken.Value.Register(() =>
  123. {
  124. // 我们收到一条取消消息,取消TaskCompletionSource.Task
  125. stopLoad = true;
  126. taskCompletionSource.TrySetCanceled();
  127. });

  128. _isLoading = true;

  129. var task = LoadContactsAsync();

  130. // 等待两个任务中的第一个任务完成
  131. var completedTask = await Task.WhenAny(task, taskCompletionSource.Task);
  132. _isLoading = false;

  133. return await completedTask;
  134. }

  135. /// <summary>
  136. /// 异步加载通讯录,具体的通讯录读取方法
  137. /// </summary>
  138. /// <returns></returns>
  139. async Task<IList<Contact>> LoadContactsAsync()
  140. {
  141. IList<Contact> contacts = new List<Contact>();
  142. var hasPermission = await RequestPermissionAsync();
  143. if (!hasPermission)
  144. {
  145. return contacts;
  146. }

  147. var uri = ContactsContract.Contacts.ContentUri;
  148. var ctx = Application.Context;
  149. await Task.Run(() =>
  150. {
  151. // 暂时只请求通讯录Id、DisplayName、PhotoThumbnailUri,可以扩展
  152. var cursor = ctx.ApplicationContext.ContentResolver.Query(uri, new string[]
  153. {
  154. ContactsContract.Contacts.InterfaceConsts.Id,
  155. ContactsContract.Contacts.InterfaceConsts.DisplayName,
  156. ContactsContract.Contacts.InterfaceConsts.PhotoThumbnailUri
  157. }, null, null, $"{ContactsContract.Contacts.InterfaceConsts.DisplayName} ASC");
  158. if (cursor.Count > 0)
  159. {
  160. while (cursor.MoveToNext())
  161. {
  162. var contact = CreateContact(cursor, ctx);

  163. if (!string.IsNullOrWhiteSpace(contact.Name))
  164. {
  165. // 读取出一条,即通知界面展示
  166. OnContactLoaded?.Invoke(this, new ContactEventArgs(contact));
  167. contacts.Add(contact);
  168. }

  169. if (stopLoad)
  170. break;
  171. }
  172. }
  173. });

  174. return contacts;

  175. }

  176. /// <summary>
  177. /// 读取一条通讯录数据
  178. /// </summary>
  179. /// <param name="cursor"></param>
  180. /// <param name="ctx"></param>
  181. /// <returns></returns>
  182. Contact CreateContact(ICursor cursor, Context ctx)
  183. {
  184. var contactId = GetString(cursor, ContactsContract.Contacts.InterfaceConsts.Id);

  185. var numbers = GetNumbers(ctx, contactId);
  186. var emails = GetEmails(ctx, contactId);

  187. var uri = GetString(cursor, ContactsContract.Contacts.InterfaceConsts.PhotoThumbnailUri);
  188. string path = null;
  189. if (!string.IsNullOrEmpty(uri))
  190. {
  191. try
  192. {
  193. using (var stream = Android.App.Application.Context.ContentResolver.OpenInputStream(Android.Net.Uri.Parse(uri)))
  194. {
  195. path = Path.Combine(Path.GetTempPath(), $"{ThumbnailPrefix}-{Guid.NewGuid()}");
  196. using (var fstream = new FileStream(path, FileMode.Create))
  197. {
  198. stream.CopyTo(fstream);
  199. fstream.Close();
  200. }

  201. stream.Close();
  202. }


  203. }
  204. catch (Exception ex)
  205. {
  206. System.Diagnostics.Debug.WriteLine(ex);
  207. }

  208. }
  209. var contact = new Contact
  210. {
  211. Name = GetString(cursor, ContactsContract.Contacts.InterfaceConsts.DisplayName),
  212. Emails = emails,
  213. Image = path,
  214. PhoneNumbers = numbers,
  215. };

  216. return contact;
  217. }

  218. /// <summary>
  219. /// 读取联系人电话号码
  220. /// </summary>
  221. /// <param name="ctx"></param>
  222. /// <param name="contactId"></param>
  223. /// <returns></returns>
  224. string[] GetNumbers(Context ctx, string contactId)
  225. {
  226. var key = ContactsContract.CommonDataKinds.Phone.Number;

  227. var cursor = ctx.ApplicationContext.ContentResolver.Query(
  228. ContactsContract.CommonDataKinds.Phone.ContentUri,
  229. null,
  230. ContactsContract.CommonDataKinds.Phone.InterfaceConsts.ContactId + " = ?",
  231. new[] { contactId },
  232. null
  233. );

  234. return ReadCursorItems(cursor, key)?.ToArray();
  235. }

  236. /// <summary>
  237. /// 读取联系人邮箱地址
  238. /// </summary>
  239. /// <param name="ctx"></param>
  240. /// <param name="contactId"></param>
  241. /// <returns></returns>
  242. string[] GetEmails(Context ctx, string contactId)
  243. {
  244. var key = ContactsContract.CommonDataKinds.Email.InterfaceConsts.Data;

  245. var cursor = ctx.ApplicationContext.ContentResolver.Query(
  246. ContactsContract.CommonDataKinds.Email.ContentUri,
  247. null,
  248. ContactsContract.CommonDataKinds.Email.InterfaceConsts.ContactId + " = ?",
  249. new[] { contactId },
  250. null);

  251. return ReadCursorItems(cursor, key)?.ToArray();
  252. }

  253. IEnumerable<string> ReadCursorItems(ICursor cursor, string key)
  254. {
  255. while (cursor.MoveToNext())
  256. {
  257. var value = GetString(cursor, key);
  258. yield return value;
  259. }

  260. cursor.Close();
  261. }

  262. string GetString(ICursor cursor, string key)
  263. {
  264. return cursor.GetString(cursor.GetColumnIndex(key));
  265. }

  266. }
  267. }

需要添加 Plugin.CurrentActivity 和 Acr.UserDialogs 包。

7、Android工程添加权限处理判断类

Permission.Util.cs

  1. using Android.Content.PM;

  2. namespace TerminalMACS.Clients.App.Droid
  3. {
  4. public static class PermissionUtil
  5. {
  6. /**
  7. * 通过验证给定数组中的每个条目的值是否为Permission.Granted,检查是否已授予所有给定权限。
  8. *
  9. * See Activity#onRequestPermissionsResult (int, String[], int[])
  10. */
  11. public static bool VerifyPermissions(Permission[] grantResults)
  12. {
  13. // 必须至少检查一个结果.
  14. if (grantResults.Length < 1)
  15. return false;

  16. // 验证是否已授予每个必需的权限,否则返回false.
  17. foreach (Permission result in grantResults)
  18. {
  19. if (result != Permission.Granted)
  20. {
  21. return false;
  22. }
  23. }
  24. return true;
  25. }
  26. }
  27. }

MainActivity.OnRequestPermissionResult是权限申请结果处理函数,在此函数中调用ContactsService.OnRequestPermissionsResult通知通讯录服务权限处理结果。

MainActivity.cs

  1. using Acr.UserDialogs;
  2. using Android.App;
  3. using Android.Content.PM;
  4. using Android.OS;
  5. using Android.Runtime;
  6. using TerminalMACS.Clients.App.Droid.Services;
  7. using TerminalMACS.Clients.App.Services;

  8. namespace TerminalMACS.Clients.App.Droid
  9. {
  10. [Activity(Label = "TerminalMACS.Clients.App", Icon = "@mipmap/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
  11. public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
  12. {
  13. IContactsService contactsService = new ContactsService();
  14. protected override void OnCreate(Bundle savedInstanceState)
  15. {
  16. TabLayoutResource = Resource.Layout.Tabbar;
  17. ToolbarResource = Resource.Layout.Toolbar;

  18. base.OnCreate(savedInstanceState);

  19. Xamarin.Essentials.Platform.Init(this, savedInstanceState);
  20. global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
  21. UserDialogs.Init(() => this);

  22. // 将通讯录服务实例传递给共享库,由共享库使用读取通讯录接口
  23. LoadApplication(new App(contactsService));
  24. }
  25. public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
  26. {
  27. Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);

  28. // 通讯录服务处理权限请求结果
  29. ContactsService.OnRequestPermissionsResult(requestCode, permissions, grantResults);

  30. base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
  31. }
  32. }
  33. }

8、创建通讯录ViewModel,并使用通讯录服务

  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Collections.ObjectModel;
  5. using System.Linq;
  6. using System.Threading.Tasks;
  7. using System.Windows.Input;
  8. using TerminalMACS.Clients.App.Models;
  9. using TerminalMACS.Clients.App.Services;
  10. using Xamarin.Forms;

  11. namespace TerminalMACS.Clients.App.ViewModels
  12. {
  13. /// <summary>
  14. /// 通讯录ViewModel
  15. /// </summary>
  16. public class ContactViewModel : BaseViewModel
  17. {
  18. /// <summary>
  19. /// 通讯录服务接口
  20. /// </summary>
  21. IContactsService _contactService;
  22. /// <summary>
  23. /// 标题
  24. /// </summary>
  25. public new string Title => "通讯录";
  26. private string _SearchText;
  27. /// <summary>
  28. /// 搜索关键字
  29. /// </summary>
  30. public string SearchText
  31. {
  32. get { return _SearchText; }
  33. set
  34. {
  35. SetProperty(ref _SearchText, value);
  36. }
  37. }
  38. /// <summary>
  39. /// 通讯录搜索命令
  40. /// </summary>
  41. public ICommand RaiseSearchCommand { get; }
  42. /// <summary>
  43. /// 通讯录列表
  44. /// </summary>
  45. public ObservableCollection<Contact> Contacts { get; set; }
  46. private List<Contact> _FilteredContacts;
  47. /// <summary>
  48. /// 通讯录过滤列表
  49. /// </summary>
  50. public List<Contact> FilteredContacts

  51. {
  52. get { return _FilteredContacts; }
  53. set
  54. {
  55. SetProperty(ref _FilteredContacts, value);
  56. }
  57. }
  58. public ContactViewModel(IContactsService contactService)
  59. {
  60. _contactService = contactService;
  61. Contacts = new ObservableCollection<Contact>();
  62. Xamarin.Forms.BindingBase.EnableCollectionSynchronization(Contacts, null, ObservableCollectionCallback);
  63. _contactService.OnContactLoaded += OnContactLoaded;
  64. LoadContacts();
  65. RaiseSearchCommand = new Command(RaiseSearchHandle);
  66. }

  67. /// <summary>
  68. /// 过滤通讯录
  69. /// </summary>
  70. void RaiseSearchHandle()
  71. {
  72. if (string.IsNullOrEmpty(SearchText))
  73. {
  74. FilteredContacts = Contacts.ToList();
  75. return;
  76. }

  77. Func<Contact, bool> checkContact = (s) =>
  78. {
  79. if (!string.IsNullOrWhiteSpace(s.Name) && s.Name.ToLower().Contains(SearchText.ToLower()))
  80. {
  81. return true;
  82. }
  83. else if (s.PhoneNumbers.Length > 0 && s.PhoneNumbers.ToList().Exists(cu => cu.ToString().Contains(SearchText)))
  84. {
  85. return true;
  86. }
  87. return false;
  88. };
  89. FilteredContacts = Contacts.ToList().Where(checkContact).ToList();
  90. }

  91. /// <summary>
  92. /// BindingBase.EnableCollectionSynchronization 为集合启用跨线程更新
  93. /// </summary>
  94. /// <param name="collection"></param>
  95. /// <param name="context"></param>
  96. /// <param name="accessMethod"></param>
  97. /// <param name="writeAccess"></param>
  98. void ObservableCollectionCallback(IEnumerable collection, object context, Action accessMethod, bool writeAccess)
  99. {
  100. // `lock` ensures that only one thread access the collection at a time
  101. lock (collection)
  102. {
  103. accessMethod?.Invoke();
  104. }
  105. }

  106. /// <summary>
  107. /// 收到事件通知,读取一条通讯录信息
  108. /// </summary>
  109. /// <param name="sender"></param>
  110. /// <param name="e"></param>
  111. private void OnContactLoaded(object sender, ContactEventArgs e)
  112. {
  113. Contacts.Add(e.Contact);
  114. RaiseSearchHandle();
  115. }

  116. /// <summary>
  117. /// 异步读取终端通讯录
  118. /// </summary>
  119. /// <returns></returns>
  120. async Task LoadContacts()
  121. {
  122. try
  123. {
  124. await _contactService.RetrieveContactsAsync();
  125. }
  126. catch (TaskCanceledException)
  127. {
  128. Console.WriteLine("任务已经取消");
  129. }
  130. }
  131. }
  132. }

9、添加通讯录页面展示通讯录数据

  1. <?xml version="1.0" encoding="utf-8" ?>
  2. <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
  4. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  5. xmlns:d="http://xamarin.com/schemas/2014/forms/design"
  6. xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
  7. mc:Ignorable="d"
  8. Title="{Binding Title}"
  9. x:Class="TerminalMACS.Clients.App.Views.ContactPage"
  10. ios:Page.UseSafeArea="true">
  11. <ContentPage.Content>
  12. <StackLayout>
  13. <SearchBar x:Name="filterText"
  14. HeightRequest="40"
  15. Text="{Binding SearchText}"
  16. SearchCommand="{Binding RaiseSearchCommand}"/>
  17. <ListView   ItemsSource="{Binding FilteredContacts}"
  18. HasUnevenRows="True">
  19. <ListView.ItemTemplate>
  20. <DataTemplate>
  21. <ViewCell>
  22. <StackLayout Padding="10"
  23. Orientation="Horizontal">
  24. <Image  Source="{Binding Image}"
  25. VerticalOptions="Center"
  26. x:Name="image"
  27. Aspect="AspectFit"
  28. HeightRequest="60"/>
  29. <StackLayout VerticalOptions="Center">
  30. <Label Text="{Binding Name}"
  31. FontAttributes="Bold"/>
  32. <Label Text="{Binding PhoneNumbers[0]}"/>
  33. <Label Text="{Binding Emails[0]}"/>
  34. </StackLayout>
  35. </StackLayout>
  36. </ViewCell>
  37. </DataTemplate>
  38. </ListView.ItemTemplate>
  39. </ListView>
  40. </StackLayout>
  41. </ContentPage.Content>
  42. </ContentPage>

三、源码获取

  • 1.完整源码:https://github.com/dotnet9/TerminalMACS
  • 2.Android客户端可成功取得通讯录数据,并可查询;

已编译的Android客户端:https://terminalmacs.com/terminalmacs-clients-app-android

  • 3.iOS读取通讯录功能代码也已添加,但由于本人没有iOS测试环境,所以未验证,有条件的朋友可以测试下iOS的通讯录读取功能,如果代码不起作用,可参考本文参考的文章检查iOS代码。

四、参考资料

Getting phone contacts in Xamarin Forms:https://www.xamboy.com/2019/10/10/getting-phone-contacts-in-xamarin-forms/

参考文章末尾有源代码链接。

五、后面计划

Xamarin.Forms客户端基本信息获取,比如IMEI、IMSI、本机号码、Mac地址等。

举报

相关推荐

0 条评论