本文同步更新地址:

  • https://dotnet9.com/11520.html

  • https://terminalmacs.com/861.html

阅读导航:

  • 一、功能说明

  • 二、代码实现

  • 三、源码获取

  • 四、参考资料

  • 五、后面计划

一、功能说明

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

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

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

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

二、代码实现

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文件添加通讯录权限使用说明

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地址等。

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

  1. android 获取通讯录全选反选_Xamarin.Forms读取并展示Android和iOS通讯录 TerminalMACS客户端...

    本文同步更新地址: https://dotnet9.com/11520.html https://terminalmacs.com/861.html 阅读导航: 一.功能说明 二.代码实现 三.源码获 ...

  2. 张高兴的 Xamarin.Forms 开发笔记:Android 快捷方式 Shortcut 应用

    一.Shortcut 简介 Shortcut 是 Android 7.1 (API Level 25) 的新特性,类似于苹果的 3D Touch ,但并不是压力感应,只是一种长按菜单.Shortcut ...

  3. xamarin.forms_重构:从Xamarin Native到Xamarin.Forms

    xamarin.forms 介绍 (Introduction) Before you actually start developing a mobile app, you have to make ...

  4. 张高兴的 Xamarin.Forms 开发笔记:为 Android 与 iOS 引入 UWP 风格的汉堡菜单 ( MasterDetailPage )...

    所谓 UWP 样式的汉堡菜单,我曾在"张高兴的 UWP 开发笔记:汉堡菜单进阶"里说过,也就是使用 Segoe MDL2 Assets 字体作为左侧 Icon,并且左侧使用填充颜色 ...

  5. xamarin android密码,Xamarin.Forms学习历程(七)——用户偏好设置存储

    经常会遇到要存储一下用户账号密码之类的,让用户下次登录时不需要重新输入账号密码,直接进入主界面.Xamarin.Forms里没有自己的解决方案,还是得调用iOS和Android原生的API才可以实现. ...

  6. Xamarin.Forms教程下载安装Xamarin.iOS

    Xamarin.Forms教程下载安装Xamarin.iOS 下载安装Xamarin.iOS Xamarin.iOS可以为Mac上iOS应用程序在Windows计算机上编写和测试网络提供构建和部署服务 ...

  7. Xamarin.Forms教程Android SDK工具下载安装

    Xamarin.Form的Android SDK工具下载安装 本节将讲解如何下载Xamarin.Form的Android SDK工具,并使用其中的工具管理Android SDK,如何创建模拟器等内容. ...

  8. 从零开始学Xamarin.Forms(四) Android 准备步骤(添加第三方Xamarin.Forms.Labs库)

    从零开始学Xamarin.Forms(四) Android 准备步骤(添加第三方Xamarin.Forms.Labs库) 原文:从零开始学Xamarin.Forms(四) Android 准备步骤(添 ...

  9. Xamarin.Forms 中iOS通过URL Scheme判断应用是否安装

    Xamarin.Forms 中iOS通过URL Scheme判断应用是否安装 在移动应用开发中,经常需要判断一个app是否安装,iOS中有什么方式可以判断app是否安装呢? 这里介绍通过Url Sch ...

最新文章

  1. centos 7安装 navicat
  2. 常见排序算法效率比较
  3. Error in configuration process解决方法
  4. 破解前端面试系列(3):如何搞定纸上代码环节?
  5. 【DVWA(五)】XXS存储型跨站攻击
  6. Android中贝塞尔曲线的绘制方法
  7. 小程序开发(13)-location定位
  8. 利用 assistant_如何使用Dialogflow对Google Assistant操作实施本地履行
  9. 大部分人其实根本就不上进,他们只是表现的很努力
  10. PHP获取当前页面的完整URL
  11. unix下source的使用
  12. 处理vue项目中使用es6模板字符串中\n换行问题
  13. mybatis中sql写法技巧小总结
  14. 早间简评:黄金亚盘快速下跌   1300关口岌岌可危?
  15. JavaScript:实现PigeonHoleSort鸽巢排序算法(附完整源码)
  16. Android闹钟制作过程图,小学闹钟手工制作步骤详解(配图)
  17. 2021图机器学习有哪些新突破?麦吉尔大学博士后一文梳理展望领域趋势
  18. linux中shell的循环
  19. 【转发】微信小程序详细图文教程
  20. 央视网商城app_传播中国文化 央视网商城重磅打造中国好物产

热门文章

  1. 对于 APM 用户的一次真实调查分析(下)
  2. javascript 中的暗物质 - 闭包
  3. ubuntu 安装 php
  4. LNK2005 连接错误解决办法
  5. 北京一公交车发生爆炸 疑为乘客携带药品起反应
  6. 计算机考研分数2019,2019考研分数线和国家线有什么关系
  7. 如何在 Apple Silicon (M1) 上开发 Teams App
  8. java 输入流关闭顺序_Java IO流中先关闭输出流还是先关闭输入流?为什么?
  9. Centos7作为VNCserver,本地使用VNCViewer连接
  10. python读取windows系统的文件后print乱码问题的解决