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

本文同步更新地址:

阅读导航:

  • 一、功能说明
  • 二、代码实现
  • 三、源码获取
  • 四、参考资料
  • 五、后面计划

一、功能说明

完整思维导图: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.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地址等。

Xamarin.Forms读取并展示Android和iOS通讯录 - TerminalMACS客户端的更多相关文章

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

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

  2. Xamarin.Forms学习系列之Android集成极光推送

    一般App都会有消息推送的功能,如果是原生安卓或者IOS集成消息推送很容易,各大推送平台都有相关的Sample,但是关于Xamarin.Forms的消息推送集成的资料非常少,下面就说下Xamarin. ...

  3. xamarin.forms新建项目android编译错误

    vs2015 update3 新建的xamarin.forms项目中的android项目编译错误.提示缺少android_m2repository_r22.zip,96659D653BDE0FAEDB ...

  4. Xamarin.Forms介绍

    On May 28, 2014, Xamarin introduced Xamarin.Forms, which allows you to write user-interface code tha ...

  5. 自定义xamarin.forms Entry 背景色以及边框

    创建   一个Xamarin.Forms自定义控件.     自定义Entry控件可以通过继承来创建Entry控制,显示在下面的代码示例: public class MyEntry : Entry { ...

  6. xamarin forms中的Button文本默认大写

    问题来源 使用xamarin forms创建的android项目中,Button.Toolbar的右侧菜单按钮上的如果是字母的话,在android5.0以上,默认的文本都是大写,这种情况iOS项目不存 ...

  7. Xamarin.Forms 开发资源集合(复制)

    复制:https://www.cnblogs.com/mschen/p/10199997.html 收集整理了下 Xamarin.Forms 的学习参考资料,分享给大家,稍后会不断补充: UI样式 S ...

  8. xamarin.forms之page

    最近在使用xamarin.forms做APP开发,之前做过ios的应用,虽然没做过安卓,但之前也有一点了解,什么四大组件五大布局啥的,微软的xamarin.forms的文档也挺详细的,基本都是复制粘贴 ...

  9. Xamarin.Forms 开发资源集合

    收集整理了下 Xamarin.Forms 的学习参考资料,分享给大家,稍后会不断补充: UI样式 Snppts: Xamarin Forms UI Snippets. Prebuilt Templat ...

随机推荐

  1. 代工黑马,纬创如何强吞iPhone?

    ​ 现在,智能手机市场非常得意兴阑珊,以苹果为首的最强大脑似乎再也想不出什么好的创意,iPhone7也只不过是旧机种的翻新款式,看上去跟一块板砖.一块镜子差不多:软体方面则出现了大批的"过度 ...

  2. C++扬帆远航——9(小学生算数程序)

    /* * Copyright (c) 2016,烟台大学计算机与控制工程学院 * All rights reserved. * 文件名:studentjishu.cpp * 作者:常轩 * 微信公众号 ...

  3. 在idea下遇到的问题汇总(间接性更新)

    在idea下遇到的问题汇总(间接性更新) tomcat下的jsp代码问题: 在idea的环境下,遇到jsp代码.符号失效,首先需要考虑到jar包没有引入,情况如图: 这种情况是因为jar包没有导入进去 ...

  4. pc端适配移动端

    pc端和移动端共用一套代码 1. 允许网页宽度自动调整 在网页代码的头部,加入一行viewport元标签 <meta name="viewport" content=&quo ...

  5. echarts优化数据视图dataView中的样式

    在使用echart过程中,toolbox里有个dataView视图模式,里面的数据没有对整,影响展示效果,情形如下:改问题解决方案为,在optionTocontent回调函数中处理,具体代码如下: t ...

  6. A. Reorder the Array

    You are given an array of integers. Vasya can permute (change order) its integers. He wants to do it ...

  7. ant tree 展开key的集合

    这次有个功能 ant的tree 展开 点击子节点 新增节点之后 数据能够照常展开 有几种方法 我能想到的 因为ant 有个expanded 只要设置为true就能展开了,但是这边有个陷阱,就是仅仅设置 ...

  8. ajax的post提交 序列化json参数

    再一次项目中,很常见的就是我的前端需要异步进行和后端交互 ,然而需要携带一些参数过去,并且参数类型是json 怎么办呢? 这个时候我们就需要 进行参数序列化 很简单就两句话 如下图 我们看 JSON, ...

  9. 三维GIS引擎地图可视化渲染方案设计

    1.GIS地图可视化流程 GIS地图可视化就是将空间数据转化为地图数据再进行交互处理的方法,下图一展示了地图可视化的可编程渲染的典型管道,原始空间数据必须处理为图形API支持基础图元用以地图渲染.下图 ...

  10. Aircrack-ng无线审计工具破解无线密码

    Aircrack-ng工具 Aircrack-ng是一个与802.11标准的无线网络分析的安全软件,主要功能有网络探测.数据包嗅探捕获.WEP和WPA/WPA2-PSK破解.Aircrack可以工作在 ...