本文同步更新地址:

  • 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. ///

  4. /// 通讯录

  5. ///

  6. public class Contact

  7. {

  8. ///

  9. /// 获取或者设置名称

  10. ///

  11. public string Name { get; set; }

  12. ///

  13. /// 获取或者设置 头像

  14. ///

  15. public string Image { get; set; }

  16. ///

  17. /// 获取或者设置 邮箱地址

  18. ///

  19. public string[] Emails { get; set; }

  20. ///

  21. /// 获取或者设置 手机号码

  22. ///

  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. ///

  9. /// 通讯录事件参数

  10. ///

  11. public class ContactEventArgs:EventArgs

  12. {

  13. public Contact Contact { get; }

  14. public ContactEventArgs(Contact contact)

  15. {

  16. Contact = contact;

  17. }

  18. }

  19. ///

  20. /// 通讯录服务接口,android和iOS终端具体的通讯录获取服务需要继承此接口

  21. ///

  22. public interface IContactsService

  23. {

  24. ///

  25. /// 读取一条数据通知

  26. ///

  27. event EventHandler OnContactLoaded;

  28. ///

  29. /// 是否正在加载

  30. ///

  31. bool IsLoading { get; }

  32. ///

  33. /// 尝试获取所有通讯录

  34. ///

  35. ///

  36. ///

  37. Task> 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. ///

  14. /// 通讯录获取服务

  15. ///

  16. public class ContactsService : NSObject, IContactsService

  17. {

  18. const string ThumbnailPrefix = "thumb";

  19. bool requestStop = false;

  20. public event EventHandler OnContactLoaded;

  21. bool _isLoading = false;

  22. public bool IsLoading => _isLoading;

  23. ///

  24. /// 异步请求权限

  25. ///

  26. ///

  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. ///

  41. /// 异步请求通讯录,此方法由界面真正调用

  42. ///

  43. ///

  44. ///

  45. public async Task> RetrieveContactsAsync(CancellationToken? cancelToken = null)

  46. {

  47. requestStop = false;

  48. if (!cancelToken.HasValue)

  49. cancelToken = CancellationToken.None;

  50. // 我们创建了一个十进制的TaskCompletionSource

  51. var taskCompletionSource = new TaskCompletionSource>();

  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. ///

  67. /// 异步加载通讯录,具体的通讯录读取方法

  68. ///

  69. ///

  70. async Task> LoadContactsAsync()

  71. {

  72. IList contacts = new List();

  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. android:name="android.permission.READ_CONTACTS"/>

完整权限配置如下

  1. xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.companyname.terminalmacs.clients.app">

  2. android:minSdkVersion="21" android:targetSdkVersion="28" />

  3. android:label="TerminalMACS.Clients.App.Android">

  4. android:name="android.permission.ACCESS_NETWORK_STATE" />

  5. android:name="android.permission.READ_CONTACTS"/>

  6. android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

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. ///

  22. /// 通讯录获取服务

  23. ///

  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. ///

  41. /// 获取通讯录需要的请求权限

  42. ///

  43. static string[] PermissionsContact = {

  44. Manifest.Permission.ReadContacts

  45. };

  46. public event EventHandler OnContactLoaded;

  47. ///

  48. /// 异步请求通讯录权限

  49. ///

  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. ///

  67. /// 收到用户响应请求权限操作后的结果

  68. ///

  69. ///

  70. ///

  71. ///

  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. ///

  89. /// 异步请求权限

  90. ///

  91. ///

  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. ///

  110. /// 异步请求通讯录,此方法由界面真正调用

  111. ///

  112. ///

  113. ///

  114. public async Task> RetrieveContactsAsync(CancellationToken? cancelToken = null)

  115. {

  116. stopLoad = false;

  117. if (!cancelToken.HasValue)

  118. cancelToken = CancellationToken.None;

  119. // 我们创建了一个十进制的TaskCompletionSource

  120. var taskCompletionSource = new TaskCompletionSource>();

  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. ///

  136. /// 异步加载通讯录,具体的通讯录读取方法

  137. ///

  138. ///

  139. async Task> LoadContactsAsync()

  140. {

  141. IList contacts = new List();

  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. ///

  177. /// 读取一条通讯录数据

  178. ///

  179. ///

  180. ///

  181. ///

  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. ///

  219. /// 读取联系人电话号码

  220. ///

  221. ///

  222. ///

  223. ///

  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. ///

  237. /// 读取联系人邮箱地址

  238. ///

  239. ///

  240. ///

  241. ///

  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. ///

  14. /// 通讯录ViewModel

  15. ///

  16. public class ContactViewModel : BaseViewModel

  17. {

  18. ///

  19. /// 通讯录服务接口

  20. ///

  21. IContactsService _contactService;

  22. ///

  23. /// 标题

  24. ///

  25. public new string Title => "通讯录";

  26. private string _SearchText;

  27. ///

  28. /// 搜索关键字

  29. ///

  30. public string SearchText

  31. {

  32. get { return _SearchText; }

  33. set

  34. {

  35. SetProperty(ref _SearchText, value);

  36. }

  37. }

  38. ///

  39. /// 通讯录搜索命令

  40. ///

  41. public ICommand RaiseSearchCommand { get; }

  42. ///

  43. /// 通讯录列表

  44. ///

  45. public ObservableCollection Contacts { get; set; }

  46. private List _FilteredContacts;

  47. ///

  48. /// 通讯录过滤列表

  49. ///

  50. public List 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();

  62. Xamarin.Forms.BindingBase.EnableCollectionSynchronization(Contacts, null, ObservableCollectionCallback);

  63. _contactService.OnContactLoaded += OnContactLoaded;

  64. LoadContacts();

  65. RaiseSearchCommand = new Command(RaiseSearchHandle);

  66. }

  67. ///

  68. /// 过滤通讯录

  69. ///

  70. void RaiseSearchHandle()

  71. {

  72. if (string.IsNullOrEmpty(SearchText))

  73. {

  74. FilteredContacts = Contacts.ToList();

  75. return;

  76. }

  77. Funcbool> 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. ///

  92. /// BindingBase.EnableCollectionSynchronization 为集合启用跨线程更新

  93. ///

  94. ///

  95. ///

  96. ///

  97. ///

  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. ///

  107. /// 收到事件通知,读取一条通讯录信息

  108. ///

  109. ///

  110. ///

  111. private void OnContactLoaded(object sender, ContactEventArgs e)

  112. {

  113. Contacts.Add(e.Contact);

  114. RaiseSearchHandle();

  115. }

  116. ///

  117. /// 异步读取终端通讯录

  118. ///

  119. ///

  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. "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. "filterText"

  12. HeightRequest="40"

  13. Text="{Binding SearchText}"

  14. SearchCommand="{Binding RaiseSearchCommand}"/>

  15. "{Binding FilteredContacts}"

  16. HasUnevenRows="True">

  17. "10"

  18. Orientation="Horizontal">

  19. "{Binding Image}"

  20. VerticalOptions="Center"

  21. x:Name="image"

  22. Aspect="AspectFit"

  23. HeightRequest="60"/>

  24. "Center">

  25. "{Binding Name}"

  26. FontAttributes="Bold"/>

  27. "{Binding PhoneNumbers[0]}"/>

  28. "{Binding Emails[0]}"/>

三、源码获取

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

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

  1. android 获取通讯录全选反选_Android Recyclerview实现多选,单选,全选,反选,批量删除的功能...

    效果图如下: Recyclerview 实现多选,单选,全选,反选,批量删除的步骤 1.在Recyclerview布局中添加上底部的全选和反选按钮,删除按钮,和计算数量等控件 2.这里选中的控件没有用 ...

  2. Android Dialog实现全选反选

    Android的AlertDialog中可以通过builder.setMultiChoiceItems(....)来添加一个多选项,但是并不能实现对选项的全选/反选功能,所以需要自定义一个控件.原理是 ...

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

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

  4. android 批量查找view,Android Recyclerview实现多选,单选,全选,反选,批量删除的功能

    效果图如下: recyclerview 实现多选,单选,全选,反选,批量删除的步骤 1.在recyclerview布局中添加上底部的全选和反选按钮,删除按钮,和计算数量等控件 2.这里选中的控件没有用 ...

  5. android 全选功能,Android Recyclerview实现多选,单选,全选,反选,批量删除的功能

    效果图如下: Recyclerview 实现多选,单选,全选,反选,批量删除的步骤 1.在Recyclerview布局中添加上底部的全选和反选按钮,删除按钮,和计算数量等控件 2.这里选中的控件没有用 ...

  6. js、jquery操作复选框checkbox总结(单个/多个获取选中值、初始化设置默认选中值、全选反选)

    一.单个复选框 使用label标签可以点击文字就能选中复选框或者是取消选择复选框 label标签的两种使用方法:   https://blog.csdn.net/qq_40015157/article ...

  7. JSjQuery全选反选父项子项联动多选框

    全选反选父项子项联动多选框 js代码:<!DOCTYPE html><html lang="en"><head> <meta charse ...

  8. jQuery全选反选实例

    1. $('#tb:checkbox').each(function(){ 每次都会执行 全选-取消操作,注意$('#tb :checkbox').prop('checked',true); tb后面 ...

  9. Jquery实现 全选反选

    Jquery实现 全选反选 <!DOCTYPE html> <html lang="en"><head><meta charset=&qu ...

最新文章

  1. oracle 函数参数类型,ORACLE 11g中的表值函数? (参数化视图)
  2. Linux环境变量设置中配置文件分析(/etc/profile,~/.bashrc等)(转)
  3. eclipse怎样在线安装hibernate tools插件并使用
  4. xcode 写代码没有补全 提示
  5. JAVA通过反射获取方法真实参数名
  6. 解决将Ubuntu下导出的requirements.txt到Centos服务器上面出现pkg-resource的版本为0.0.0...
  7. 【渝粤题库】陕西师范大学202091公共管理学原理 作业(高起本、专升本)
  8. hackintool怎么重建缓存_mysql表数据装满了怎么办?自增id引发的bug。
  9. CVPR2018论文阅读-Faster MPN-COV:迭代计算矩阵平方根以快速训练全局协方差池化
  10. vfp邮件.html格式,VFP 实现自动发邮件代码
  11. python 函数 思维导图
  12. 道德经 全文 简体字 繁体字_电脑的各种程序由 0 1 组成,就如同道德经中所说的(道生一。一生二。二生三。三生萬物)
  13. 共享个人整理的Python问题,有源码,分析过程,解决方案,还有时间戳做间隔
  14. C++类和C结构体类型的异同
  15. 22南工计算机学院新生培训最终章---ACM竞赛机制
  16. oracle 判断节气,FullCalendar应用——整合农历节气和节日
  17. C#开发金蝶K3插件
  18. jupyter notebook第七章seaborn库的一些案例分析加相关函数的解析
  19. form表单中id与name的区别
  20. 2345浏览器还算厚道

热门文章

  1. 并发编程实践之公平有界阻塞队列实现
  2. 攀钢告诉你:钢铁是怎样用AI炼成的?
  3. Node.js 应用故障排查手册 —— Node.js 性能平台使用指南
  4. 如何交付机器学习项目:一份机器学习工程开发流程指南
  5. 作为工程师,你真的了解无服务器?
  6. 征战云时代,如何捍卫企业数据安全?
  7. 一家典型的云原生企业,如何在创业早期数次“弯道超车”?
  8. 想要AI优先?数据优先才行
  9. es6 Promise,生成器函数,async
  10. java queue源码_Java高并发系列之ArrayBlockingQueue源码解析