前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Xamarin.Forms读取并展示Android和iOS通讯录 - TerminalMACS客户端

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

作者头像
DDGarfield
发布2022-06-23 17:16:21
4K0
发布2022-06-23 17:16:21
举报
文章被收录于专栏:加菲的博客加菲的博客

本文同步更新地址:

  • 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

代码语言:javascript
复制
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
代码语言:javascript
复制
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

代码语言:javascript
复制
<uses-permission android:name="android.permission.READ_CONTACTS"/>

完整权限配置如下

代码语言:javascript
复制
<?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

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
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,并使用通讯录服务

代码语言:txt
复制
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、添加通讯录页面展示通讯录数据

代码语言:javascript
复制
<?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地址等。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-12-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 加菲的博客 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、功能说明
  • 二、代码实现
    • 1、共享库工程创建联系人实体类:Contacts.cs
      • 2、共享库创建通讯录服务接口:IContactsService.cs
        • 4、在iOS工程中的Info.plist文件添加通讯录权限使用说明
          • 5、在Android工程中添加读取通讯录权限配置:AndroidManifest.xml
            • 6、在Android工程中添加通讯录服务,实现IContactServer接口:ContactsService.cs
              • 7、Android工程添加权限处理判断类
                • 8、创建通讯录ViewModel,并使用通讯录服务
                  • 9、添加通讯录页面展示通讯录数据
                  • 三、源码获取
                  • 四、参考资料
                  • 五、后面计划
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档