Xamarin.Forms讀取並展示Android和iOS通訊錄 - TerminalMACS客戶端

来源:https://www.cnblogs.com/Dotnet9-com/archive/2020/03/31/12605775.html
-Advertisement-
Play Games

Xamarin.Forms讀取並展示Android和iOS通訊錄 TerminalMACS客戶端 本文同步更新地址: https://dotnet9.com/11520.html https://terminalmacs.com/861.html 閱讀導航: 一、功能說明 二、代碼實現 三、源碼獲取 ...


Xamarin.Forms讀取並展示Android和iOS通訊錄 - TerminalMACS客戶端

本文同步更新地址:

閱讀導航:

  • 一、功能說明
  • 二、代碼實現
  • 三、源碼獲取
  • 四、參考資料
  • 五、後面計劃

一、功能說明

完整思維導圖:https://github.com/dotnet9/TerminalMACS/blob/master/docs/TerminalMACS.xmind
Xamarin.Forms客戶端通訊錄功能

本文介紹圖中右側畫紅圈處的功能,即使用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文件添加通訊錄許可權使用說明

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.CurrentActivityAcr.UserDialogs 包。

7、Android工程添加許可權處理判斷類

Permission.Util

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>

三、源碼獲取

已編譯的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地址等。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言 為什麼要把反射和泛型放在一起講呢,這裡是處於個人對C 的一個很棒的觀感,因為C 的反射是可以獲取泛型里的元素的,而不像Java一個讓我比較難受的地方就是Java的泛型實際編譯的時候會擦除類型信息。 那麼問題來了,什麼是泛型,什麼又是反射呢? 泛型 請原諒我先介紹泛型,因為沒有泛型基礎直接介紹反 ...
  • 一種char分隔符 string phrase = "The quick brown fox jumps over the lazy dog."; string[] words = phrase.Split(' '); foreach (var word in words) { System.Con ...
  • 一、索引器(Indexer)允許類和結構的實例像數組一樣通過索引取值,可以看做是對[]運算符的重載,索引器實際上就是有參數的屬性,也被稱為有參屬性或索引化屬性,其聲明形式與屬性相似,不同之處在於索引器的訪問器需要傳入參數; 1.聲明索引器: class MyClass { string[] myAr ...
  • 記錄使用對象初始值設定項初始化對象。 using System; using System.Collections.Generic; namespace ConsoleApp2 { class Program { static void Main(string[] args) { // 使用構造函數 ...
  • 《深入淺出 C#》 (第3版) [作者] (美) Andrew Stellman (美) Jennifer Greene[譯者] (中) 徐陽 丁小峰 等譯[出版] 中國電力出版社[版次] 2016年08月 第1版[印次] 2018年04月 第4次 印刷[定價] 148.00元 【引子】 要學習編程 ...
  • [toc] 1.應用背景 底端設備有大量網路報文(位元組數組):心跳報文,數據採集報文,告警報文上報。需要有對應的報文結構去解析這些位元組流數據。 2.結構體解析 由此,我第一點就想到了用結構體去解析。原因有以下兩點: 2.1.結構體存在棧中 類屬於引用類型,存在堆中;結構體屬於值類型,存在棧中,在一個 ...
  • 前言 有一個東西叫做鴨子類型,所謂鴨子類型就是,只要一個東西表現得像鴨子那麼就能推出這玩意就是鴨子。 C 裡面其實也暗藏了很多類似鴨子類型的東西,但是很多開發者並不知道,因此也就沒法好好利用這些東西,那麼今天我細數一下這些藏在編譯器中的細節。 不是只有 和 才能 在 C 中編寫非同步代碼的時候,我們經 ...
  • 做下對文件複製操作相關的筆記: /// <summary> /// 文件幫助類 /// </summary> public class FileHelper { /// <summary> /// 複製一個目錄下所有文件到一個新目錄下 /// </summary> /// <param name=" ...
一周排行
    -Advertisement-
    Play Games
  • 概述:這個WPF項目通過XAML繪製汽車動態速度表盤,實現了0-300的速度刻度,包括數字、指針,並通過定時器模擬速度變化,展示了動態效果。詳細實現包括界面設計、刻度繪製、指針角度計算等,通過C#代碼與XAML文件結合完成。 新建 WPF 項目: 在 Visual Studio 中創建一個新的 WP ...
  • 概述:在WPF中使用`WpfAnimatedGif`庫展示GIF動畫,首先確保全裝了該庫。通過XAML設置Image控制項,指定GIF路徑,然後在代碼中使用庫提供的方法實現動畫控制。這簡化了在WPF應用中處理GIF圖的過程,提供了方便的介面來管理動畫播放和暫停。 當使用 WpfAnimatedGif  ...
  • 您是否曾經訪問過一個網站,它需要很長時間載入,最終你敲擊 F5 重新載入頁面。 即使用戶刷新了瀏覽器取消了原始請求,而對於伺服器來說,API也不會知道它正在計算的值將在結束時被丟棄,刷新五次,伺服器將觸發 5 個請求。 為瞭解決這個問題,ASP.NET Core 為 Web 伺服器提供了一種機制,就 ...
  • 本章將和大家分享如何通過 Elasticsearch 實現自動補全查詢功能。 一、自動補全-安裝拼音分詞器 1、自動補全需求說明 當用戶在搜索框輸入字元時,我們應該提示出與該字元有關的搜索項,如圖: 2、使用拼音分詞 要實現根據字母做補全,就必須對文檔按照拼音分詞。在 GitHub 上恰好有 Ela ...
  • using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Windows.Forms; namespace OOP { pub ...
  • 概述:以上內容詳細介紹了在C#中如何從另一個線程更新GUI,包括基礎功能和高級功能。對於WinForms,使用`Control.Invoke`;對於WPF,使用`Dispatcher.Invoke`。高級功能使用`SynchronizationContext`實現線程間通信,確保清晰、可讀性高的代碼 ...
  • Nuget包 Microsoft.Extensions.Telemetry.Abstractions 包含的新的日誌記錄source generator,它支持使用[LogProperties]將整個對象作為State與日誌一起記錄。 我將展示一種方法來控制如何使用[LogProperties]對象 ...
  • 支持.Net/.Net Core/.Net Framework,可以部署在Docker, Windows, Linux, Mac。 常見的ORM技術(比如:Entity Framework,Dapper,SqlSugar,NHibernate,等…),它們不是在做Sql語句的程式化變種,就是在做Sq ...
  • 一、引言 在現代應用程式開發中,尤其是在涉及I/O操作(如網路請求、文件讀寫等)時,非同步編程成為了提高性能和用戶體驗的關鍵技術。C#作為.NET框架下的主流開發語言,提供了強大的非同步編程支持,通過async/await關鍵字,可以讓開發者以同步的方式編寫非同步代碼,極大地簡化了非同步編程的複雜性。本文將 ...
  • 一、引言 在.NET開發中,操作Office文檔(特別是Excel和Word)是一項常見的需求。然而,在伺服器端或無Microsoft Office環境的場景下,直接使用Office Interop可能會面臨挑戰。為瞭解決這個問題,開源庫NPOI應運而生,它提供了無需安裝Office即可創建、讀取和 ...