阅读导航:
- 一、功能说明
- 二、代码实现
- 三、源码获取
- 四、参考资料
- 五、后面计划
一、功能说明
转存失败重新上传取消
完整思维导图: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
- namespace TerminalMACS.Clients.App.Models
- {
- /// <summary>
- /// 通讯录
- /// </summary>
- public class Contact
- {
- /// <summary>
- /// 获取或者设置名称
- /// </summary>
- public string Name { get; set; }
- /// <summary>
- /// 获取或者设置 头像
- /// </summary>
- public string Image { get; set; }
- /// <summary>
- /// 获取或者设置 邮箱地址
- /// </summary>
- public string[] Emails { get; set; }
- /// <summary>
- /// 获取或者设置 手机号码
- /// </summary>
- public string[] PhoneNumbers { get; set; }
- }
- }
2、共享库创建通讯录服务接口:IContactsService.cs
包括:
- 一个通讯录获取请求接口:RetrieveContactsAsync
- 一个读取一条通讯结果通知事件:OnContactLoaded
- using System;
- using System.Collections.Generic;
- using System.Threading;
- using System.Threading.Tasks;
- using TerminalMACS.Clients.App.Models;
- namespace TerminalMACS.Clients.App.Services
- {
- /// <summary>
- /// 通讯录事件参数
- /// </summary>
- public class ContactEventArgs:EventArgs
- {
- public Contact Contact { get; }
- public ContactEventArgs(Contact contact)
- {
- Contact = contact;
- }
- }
- /// <summary>
- /// 通讯录服务接口,android和iOS终端具体的通讯录获取服务需要继承此接口
- /// </summary>
- public interface IContactsService
- {
- /// <summary>
- /// 读取一条数据通知
- /// </summary>
- event EventHandler<ContactEventArgs> OnContactLoaded;
- /// <summary>
- /// 是否正在加载
- /// </summary>
- bool IsLoading { get; }
- /// <summary>
- /// 尝试获取所有通讯录
- /// </summary>
- /// <param name="token"></param>
- /// <returns></returns>
- Task<IList<Contact>> RetrieveContactsAsync(CancellationToken? token = null);
- }
- }
3、iOS工程中添加通讯录服务,实现IContactsService接口:
- using Contacts;
- using Foundation;
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Linq;
- using System.Threading;
- using System.Threading.Tasks;
- using TerminalMACS.Clients.App.Models;
- using TerminalMACS.Clients.App.Services;
- namespace TerminalMACS.Clients.App.iOS.Services
- {
- /// <summary>
- /// 通讯录获取服务
- /// </summary>
- public class ContactsService : NSObject, IContactsService
- {
- const string ThumbnailPrefix = "thumb";
- bool requestStop = false;
- public event EventHandler<ContactEventArgs> OnContactLoaded;
- bool _isLoading = false;
- public bool IsLoading => _isLoading;
- /// <summary>
- /// 异步请求权限
- /// </summary>
- /// <returns></returns>
- public async Task<bool> RequestPermissionAsync()
- {
- var status = CNContactStore.GetAuthorizationStatus(CNEntityType.Contacts);
- Tuple<bool, NSError> authotization = new Tuple<bool, NSError>(status == CNAuthorizationStatus.Authorized, null);
- if (status == CNAuthorizationStatus.NotDetermined)
- {
- using (var store = new CNContactStore())
- {
- authotization = await store.RequestAccessAsync(CNEntityType.Contacts);
- }
- }
- return authotization.Item1;
- }
- /// <summary>
- /// 异步请求通讯录,此方法由界面真正调用
- /// </summary>
- /// <param name="cancelToken"></param>
- /// <returns></returns>
- public async Task<IList<Contact>> RetrieveContactsAsync(CancellationToken? cancelToken = null)
- {
- requestStop = false;
- if (!cancelToken.HasValue)
- cancelToken = CancellationToken.None;
- // 我们创建了一个十进制的TaskCompletionSource
- var taskCompletionSource = new TaskCompletionSource<IList<Contact>>();
- // 在cancellationToken中注册lambda
- cancelToken.Value.Register(() =>
- {
- // 我们收到一条取消消息,取消TaskCompletionSource.Task
- requestStop = true;
- taskCompletionSource.TrySetCanceled();
- });
- _isLoading = true;
- var task = LoadContactsAsync();
- // 等待两个任务中的第一个任务完成
- var completedTask = await Task.WhenAny(task, taskCompletionSource.Task);
- _isLoading = false;
- return await completedTask;
- }
- /// <summary>
- /// 异步加载通讯录,具体的通讯录读取方法
- /// </summary>
- /// <returns></returns>
- async Task<IList<Contact>> LoadContactsAsync()
- {
- IList<Contact> contacts = new List<Contact>();
- var hasPermission = await RequestPermissionAsync();
- if (hasPermission)
- {
- NSError error = null;
- var keysToFetch = new[] { CNContactKey.PhoneNumbers, CNContactKey.GivenName, CNContactKey.FamilyName, CNContactKey.EmailAddresses, CNContactKey.ImageDataAvailable, CNContactKey.ThumbnailImageData };
- var request = new CNContactFetchRequest(keysToFetch: keysToFetch);
- request.SortOrder = CNContactSortOrder.GivenName;
- using (var store = new CNContactStore())
- {
- var result = store.EnumerateContacts(request, out error, new CNContactStoreListContactsHandler((CNContact c, ref bool stop) =>
- {
- string path = null;
- if (c.ImageDataAvailable)
- {
- path = path = Path.Combine(Path.GetTempPath(), $"{ThumbnailPrefix}-{Guid.NewGuid()}");
- if (!File.Exists(path))
- {
- var imageData = c.ThumbnailImageData;
- imageData?.Save(path, true);
- }
- }
- var contact = new Contact()
- {
- Name = string.IsNullOrEmpty(c.FamilyName) ? c.GivenName : $"{c.GivenName} {c.FamilyName}",
- Image = path,
- PhoneNumbers = c.PhoneNumbers?.Select(p => p?.Value?.StringValue).ToArray(),
- Emails = c.EmailAddresses?.Select(p => p?.Value?.ToString()).ToArray(),
- };
- if (!string.IsNullOrWhiteSpace(contact.Name))
- {
- OnContactLoaded?.Invoke(this, new ContactEventArgs(contact));
- contacts.Add(contact);
- }
- stop = requestStop;
- }));
- }
- }
- return contacts;
- }
- }
- }
4、在iOS工程中的Info.plist文件添加通讯录权限使用说明
转存失败重新上传取消
5、在Android工程中添加读取通讯录权限配置:AndroidManifest.xml
- <uses-permission android:name="android.permission.READ_CONTACTS"/>
完整权限配置如下
- <?xml version="1.0" encoding="utf-8"?>
- <manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.companyname.terminalmacs.clients.app">
- <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28" />
- <application android:label="TerminalMACS.Clients.App.Android"></application>
- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
- <uses-permission android:name="android.permission.READ_CONTACTS"/>
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
- </manifest>
6、在Android工程中添加通讯录服务,实现IContactServer接口:ContactsService.cs
- using Acr.UserDialogs;
- using Android;
- using Android.App;
- using Android.Content;
- using Android.Content.PM;
- using Android.Database;
- using Android.Provider;
- using Android.Runtime;
- using Android.Support.V4.App;
- using Plugin.CurrentActivity;
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Linq;
- using System.Threading;
- using System.Threading.Tasks;
- using TerminalMACS.Clients.App.Models;
- using TerminalMACS.Clients.App.Services;
- namespace TerminalMACS.Clients.App.Droid.Services
- {
- /// <summary>
- /// 通讯录获取服务
- /// </summary>
- public class ContactsService : IContactsService
- {
- const string ThumbnailPrefix = "thumb";
- bool stopLoad = false;
- static TaskCompletionSource<bool> contactPermissionTcs;
- public string TAG
- {
- get
- {
- return "MainActivity";
- }
- }
- bool _isLoading = false;
- public bool IsLoading => _isLoading;
- //权限请求状态码
- public const int RequestContacts = 1239;
- /// <summary>
- /// 获取通讯录需要的请求权限
- /// </summary>
- static string[] PermissionsContact = {
- Manifest.Permission.ReadContacts
- };
- public event EventHandler<ContactEventArgs> OnContactLoaded;
- /// <summary>
- /// 异步请求通讯录权限
- /// </summary>
- async void RequestContactsPermissions()
- {
- //检查是否可以弹出申请读、写通讯录权限
- if (ActivityCompat.ShouldShowRequestPermissionRationale(CrossCurrentActivity.Current.Activity, Manifest.Permission.ReadContacts)
- || ActivityCompat.ShouldShowRequestPermissionRationale(CrossCurrentActivity.Current.Activity, Manifest.Permission.WriteContacts))
- {
- // 如果未授予许可,请向用户提供其他理由用户将从使用权限的附加上下文中受益。
- // 例如,如果请求先前被拒绝。
- await UserDialogs.Instance.AlertAsync("通讯录权限", "此操作需要“通讯录”权限", "确定");
- }
- else
- {
- // 尚未授予通讯录权限。直接请求这些权限。
- ActivityCompat.RequestPermissions(CrossCurrentActivity.Current.Activity, PermissionsContact, RequestContacts);
- }
- }
- /// <summary>
- /// 收到用户响应请求权限操作后的结果
- /// </summary>
- /// <param name="requestCode"></param>
- /// <param name="permissions"></param>
- /// <param name="grantResults"></param>
- public static void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
- {
- if (requestCode == RequestContacts)
- {
- // 我们请求了多个通讯录权限,因此需要检查相关的所有权限
- if (PermissionUtil.VerifyPermissions(grantResults))
- {
- // 已授予所有必需的权限,显示联系人片段。
- contactPermissionTcs.TrySetResult(true);
- }
- else
- {
- contactPermissionTcs.TrySetResult(false);
- }
- }
- }
- /// <summary>
- /// 异步请求权限
- /// </summary>
- /// <returns></returns>
- public async Task<bool> RequestPermissionAsync()
- {
- contactPermissionTcs = new TaskCompletionSource<bool>();
- // 验证是否已授予所有必需的通讯录权限。
- if (Android.Support.V4.Content.ContextCompat.CheckSelfPermission(CrossCurrentActivity.Current.Activity, Manifest.Permission.ReadContacts) != (int)Permission.Granted
- || Android.Support.V4.Content.ContextCompat.CheckSelfPermission(CrossCurrentActivity.Current.Activity, Manifest.Permission.WriteContacts) != (int)Permission.Granted)
- {
- // 尚未授予通讯录权限。
- RequestContactsPermissions();
- }
- else
- {
- // 已授予通讯录权限。
- contactPermissionTcs.TrySetResult(true);
- }
- return await contactPermissionTcs.Task;
- }
- /// <summary>
- /// 异步请求通讯录,此方法由界面真正调用
- /// </summary>
- /// <param name="cancelToken"></param>
- /// <returns></returns>
- public async Task<IList<Contact>> RetrieveContactsAsync(CancellationToken? cancelToken = null)
- {
- stopLoad = false;
- if (!cancelToken.HasValue)
- cancelToken = CancellationToken.None;
- // 我们创建了一个十进制的TaskCompletionSource
- var taskCompletionSource = new TaskCompletionSource<IList<Contact>>();
- // 在cancellationToken中注册lambda
- cancelToken.Value.Register(() =>
- {
- // 我们收到一条取消消息,取消TaskCompletionSource.Task
- stopLoad = true;
- taskCompletionSource.TrySetCanceled();
- });
- _isLoading = true;
- var task = LoadContactsAsync();
- // 等待两个任务中的第一个任务完成
- var completedTask = await Task.WhenAny(task, taskCompletionSource.Task);
- _isLoading = false;
- return await completedTask;
- }
- /// <summary>
- /// 异步加载通讯录,具体的通讯录读取方法
- /// </summary>
- /// <returns></returns>
- async Task<IList<Contact>> LoadContactsAsync()
- {
- IList<Contact> contacts = new List<Contact>();
- var hasPermission = await RequestPermissionAsync();
- if (!hasPermission)
- {
- return contacts;
- }
- var uri = ContactsContract.Contacts.ContentUri;
- var ctx = Application.Context;
- await Task.Run(() =>
- {
- // 暂时只请求通讯录Id、DisplayName、PhotoThumbnailUri,可以扩展
- var cursor = ctx.ApplicationContext.ContentResolver.Query(uri, new string[]
- {
- ContactsContract.Contacts.InterfaceConsts.Id,
- ContactsContract.Contacts.InterfaceConsts.DisplayName,
- ContactsContract.Contacts.InterfaceConsts.PhotoThumbnailUri
- }, null, null, $"{ContactsContract.Contacts.InterfaceConsts.DisplayName} ASC");
- if (cursor.Count > 0)
- {
- while (cursor.MoveToNext())
- {
- var contact = CreateContact(cursor, ctx);
- if (!string.IsNullOrWhiteSpace(contact.Name))
- {
- // 读取出一条,即通知界面展示
- OnContactLoaded?.Invoke(this, new ContactEventArgs(contact));
- contacts.Add(contact);
- }
- if (stopLoad)
- break;
- }
- }
- });
- return contacts;
- }
- /// <summary>
- /// 读取一条通讯录数据
- /// </summary>
- /// <param name="cursor"></param>
- /// <param name="ctx"></param>
- /// <returns></returns>
- Contact CreateContact(ICursor cursor, Context ctx)
- {
- var contactId = GetString(cursor, ContactsContract.Contacts.InterfaceConsts.Id);
- var numbers = GetNumbers(ctx, contactId);
- var emails = GetEmails(ctx, contactId);
- var uri = GetString(cursor, ContactsContract.Contacts.InterfaceConsts.PhotoThumbnailUri);
- string path = null;
- if (!string.IsNullOrEmpty(uri))
- {
- try
- {
- using (var stream = Android.App.Application.Context.ContentResolver.OpenInputStream(Android.Net.Uri.Parse(uri)))
- {
- path = Path.Combine(Path.GetTempPath(), $"{ThumbnailPrefix}-{Guid.NewGuid()}");
- using (var fstream = new FileStream(path, FileMode.Create))
- {
- stream.CopyTo(fstream);
- fstream.Close();
- }
- stream.Close();
- }
- }
- catch (Exception ex)
- {
- System.Diagnostics.Debug.WriteLine(ex);
- }
- }
- var contact = new Contact
- {
- Name = GetString(cursor, ContactsContract.Contacts.InterfaceConsts.DisplayName),
- Emails = emails,
- Image = path,
- PhoneNumbers = numbers,
- };
- return contact;
- }
- /// <summary>
- /// 读取联系人电话号码
- /// </summary>
- /// <param name="ctx"></param>
- /// <param name="contactId"></param>
- /// <returns></returns>
- string[] GetNumbers(Context ctx, string contactId)
- {
- var key = ContactsContract.CommonDataKinds.Phone.Number;
- var cursor = ctx.ApplicationContext.ContentResolver.Query(
- ContactsContract.CommonDataKinds.Phone.ContentUri,
- null,
- ContactsContract.CommonDataKinds.Phone.InterfaceConsts.ContactId + " = ?",
- new[] { contactId },
- null
- );
- return ReadCursorItems(cursor, key)?.ToArray();
- }
- /// <summary>
- /// 读取联系人邮箱地址
- /// </summary>
- /// <param name="ctx"></param>
- /// <param name="contactId"></param>
- /// <returns></returns>
- string[] GetEmails(Context ctx, string contactId)
- {
- var key = ContactsContract.CommonDataKinds.Email.InterfaceConsts.Data;
- var cursor = ctx.ApplicationContext.ContentResolver.Query(
- ContactsContract.CommonDataKinds.Email.ContentUri,
- null,
- ContactsContract.CommonDataKinds.Email.InterfaceConsts.ContactId + " = ?",
- new[] { contactId },
- null);
- return ReadCursorItems(cursor, key)?.ToArray();
- }
- IEnumerable<string> ReadCursorItems(ICursor cursor, string key)
- {
- while (cursor.MoveToNext())
- {
- var value = GetString(cursor, key);
- yield return value;
- }
- cursor.Close();
- }
- string GetString(ICursor cursor, string key)
- {
- return cursor.GetString(cursor.GetColumnIndex(key));
- }
- }
- }
需要添加 Plugin.CurrentActivity 和 Acr.UserDialogs 包。
7、Android工程添加权限处理判断类
Permission.Util.cs
- using Android.Content.PM;
- namespace TerminalMACS.Clients.App.Droid
- {
- public static class PermissionUtil
- {
- /**
- * 通过验证给定数组中的每个条目的值是否为Permission.Granted,检查是否已授予所有给定权限。
- *
- * See Activity#onRequestPermissionsResult (int, String[], int[])
- */
- public static bool VerifyPermissions(Permission[] grantResults)
- {
- // 必须至少检查一个结果.
- if (grantResults.Length < 1)
- return false;
- // 验证是否已授予每个必需的权限,否则返回false.
- foreach (Permission result in grantResults)
- {
- if (result != Permission.Granted)
- {
- return false;
- }
- }
- return true;
- }
- }
- }
MainActivity.OnRequestPermissionResult是权限申请结果处理函数,在此函数中调用ContactsService.OnRequestPermissionsResult通知通讯录服务权限处理结果。
MainActivity.cs
- using Acr.UserDialogs;
- using Android.App;
- using Android.Content.PM;
- using Android.OS;
- using Android.Runtime;
- using TerminalMACS.Clients.App.Droid.Services;
- using TerminalMACS.Clients.App.Services;
- namespace TerminalMACS.Clients.App.Droid
- {
- [Activity(Label = "TerminalMACS.Clients.App", Icon = "@mipmap/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
- public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
- {
- IContactsService contactsService = new ContactsService();
- protected override void OnCreate(Bundle savedInstanceState)
- {
- TabLayoutResource = Resource.Layout.Tabbar;
- ToolbarResource = Resource.Layout.Toolbar;
- base.OnCreate(savedInstanceState);
- Xamarin.Essentials.Platform.Init(this, savedInstanceState);
- global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
- UserDialogs.Init(() => this);
- // 将通讯录服务实例传递给共享库,由共享库使用读取通讯录接口
- LoadApplication(new App(contactsService));
- }
- public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
- {
- Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
- // 通讯录服务处理权限请求结果
- ContactsService.OnRequestPermissionsResult(requestCode, permissions, grantResults);
- base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
- }
- }
- }
8、创建通讯录ViewModel,并使用通讯录服务
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using System.Collections.ObjectModel;
- using System.Linq;
- using System.Threading.Tasks;
- using System.Windows.Input;
- using TerminalMACS.Clients.App.Models;
- using TerminalMACS.Clients.App.Services;
- using Xamarin.Forms;
- namespace TerminalMACS.Clients.App.ViewModels
- {
- /// <summary>
- /// 通讯录ViewModel
- /// </summary>
- public class ContactViewModel : BaseViewModel
- {
- /// <summary>
- /// 通讯录服务接口
- /// </summary>
- IContactsService _contactService;
- /// <summary>
- /// 标题
- /// </summary>
- public new string Title => "通讯录";
- private string _SearchText;
- /// <summary>
- /// 搜索关键字
- /// </summary>
- public string SearchText
- {
- get { return _SearchText; }
- set
- {
- SetProperty(ref _SearchText, value);
- }
- }
- /// <summary>
- /// 通讯录搜索命令
- /// </summary>
- public ICommand RaiseSearchCommand { get; }
- /// <summary>
- /// 通讯录列表
- /// </summary>
- public ObservableCollection<Contact> Contacts { get; set; }
- private List<Contact> _FilteredContacts;
- /// <summary>
- /// 通讯录过滤列表
- /// </summary>
- public List<Contact> FilteredContacts
- {
- get { return _FilteredContacts; }
- set
- {
- SetProperty(ref _FilteredContacts, value);
- }
- }
- public ContactViewModel(IContactsService contactService)
- {
- _contactService = contactService;
- Contacts = new ObservableCollection<Contact>();
- Xamarin.Forms.BindingBase.EnableCollectionSynchronization(Contacts, null, ObservableCollectionCallback);
- _contactService.OnContactLoaded += OnContactLoaded;
- LoadContacts();
- RaiseSearchCommand = new Command(RaiseSearchHandle);
- }
- /// <summary>
- /// 过滤通讯录
- /// </summary>
- void RaiseSearchHandle()
- {
- if (string.IsNullOrEmpty(SearchText))
- {
- FilteredContacts = Contacts.ToList();
- return;
- }
- Func<Contact, bool> checkContact = (s) =>
- {
- if (!string.IsNullOrWhiteSpace(s.Name) && s.Name.ToLower().Contains(SearchText.ToLower()))
- {
- return true;
- }
- else if (s.PhoneNumbers.Length > 0 && s.PhoneNumbers.ToList().Exists(cu => cu.ToString().Contains(SearchText)))
- {
- return true;
- }
- return false;
- };
- FilteredContacts = Contacts.ToList().Where(checkContact).ToList();
- }
- /// <summary>
- /// BindingBase.EnableCollectionSynchronization 为集合启用跨线程更新
- /// </summary>
- /// <param name="collection"></param>
- /// <param name="context"></param>
- /// <param name="accessMethod"></param>
- /// <param name="writeAccess"></param>
- void ObservableCollectionCallback(IEnumerable collection, object context, Action accessMethod, bool writeAccess)
- {
- // `lock` ensures that only one thread access the collection at a time
- lock (collection)
- {
- accessMethod?.Invoke();
- }
- }
- /// <summary>
- /// 收到事件通知,读取一条通讯录信息
- /// </summary>
- /// <param name="sender"></param>
- /// <param name="e"></param>
- private void OnContactLoaded(object sender, ContactEventArgs e)
- {
- Contacts.Add(e.Contact);
- RaiseSearchHandle();
- }
- /// <summary>
- /// 异步读取终端通讯录
- /// </summary>
- /// <returns></returns>
- async Task LoadContacts()
- {
- try
- {
- await _contactService.RetrieveContactsAsync();
- }
- catch (TaskCanceledException)
- {
- Console.WriteLine("任务已经取消");
- }
- }
- }
- }
9、添加通讯录页面展示通讯录数据
- <?xml version="1.0" encoding="utf-8" ?>
- <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
- xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
- xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- xmlns:d="http://xamarin.com/schemas/2014/forms/design"
- xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
- mc:Ignorable="d"
- Title="{Binding Title}"
- x:Class="TerminalMACS.Clients.App.Views.ContactPage"
- ios:Page.UseSafeArea="true">
- <ContentPage.Content>
- <StackLayout>
- <SearchBar x:Name="filterText"
- HeightRequest="40"
- Text="{Binding SearchText}"
- SearchCommand="{Binding RaiseSearchCommand}"/>
- <ListView ItemsSource="{Binding FilteredContacts}"
- HasUnevenRows="True">
- <ListView.ItemTemplate>
- <DataTemplate>
- <ViewCell>
- <StackLayout Padding="10"
- Orientation="Horizontal">
- <Image Source="{Binding Image}"
- VerticalOptions="Center"
- x:Name="image"
- Aspect="AspectFit"
- HeightRequest="60"/>
- <StackLayout VerticalOptions="Center">
- <Label Text="{Binding Name}"
- FontAttributes="Bold"/>
- <Label Text="{Binding PhoneNumbers[0]}"/>
- <Label Text="{Binding Emails[0]}"/>
- </StackLayout>
- </StackLayout>
- </ViewCell>
- </DataTemplate>
- </ListView.ItemTemplate>
- </ListView>
- </StackLayout>
- </ContentPage.Content>
- </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地址等。