Xamarin.Forms客户端第一版

作为TerminalMACS的一个子进程模块,目前完成第一版:读取展示手机基本信息、联系人信息、应用程序本地化。

  1. 功能简介
  2. 详细功能说明
  3. 关于TerminalMACS

1. 功能简介

1.1. 读取手机基本信息

主要使用Xamarin.Essentials库获取设备基本信息,Xam.Plugin.DeviceInfo插件获取App Id,其实该插件也能获取设备基本信息。

1.2. 读取手机联系人信息

Android和iOS工程具体实现联系人读取服务,使用到DependencyService获取服务功能。

1.3. 应用本地化

使用资源文件实现本地化,目前只做了中、英文。

2. 详细功能说明

2.1. 读取手机基本信息

Xamarin.Essentials库用于获取手机基本信息,比如手机厂商、型号、名称、类型、版本等;Xam.Plugin.DeviceInfo插件获取App Id,用于唯一标识不同手机,获取信息见下图:

代码结构如下图:

ClientInfoViewModel.cs

using Plugin.DeviceInfo;
using System;
using Xamarin.Essentials; namespace TerminalMACS.Clients.App.ViewModels
{
/// <summary>
/// Client base information page ViewModel
/// </summary>
public class ClientInfoViewModel : BaseViewModel
{
/// <summary>
/// Gets or sets the id of the application.
/// </summary>
public string AppId { get; set; } = CrossDeviceInfo.Current.GenerateAppId();
/// <summary>
/// Gets or sets the model of the device.
/// </summary>
public string Model { get; private set; } = DeviceInfo.Model;
/// <summary>
/// Gets or sets the manufacturer of the device.
/// </summary>
public string Manufacturer { get; private set; } = DeviceInfo.Manufacturer;
/// <summary>
/// Gets or sets the name of the device.
/// </summary>
public string Name { get; private set; } = DeviceInfo.Name;
/// <summary>
/// Gets or sets the version of the operating system.
/// </summary>
public string VersionString { get; private set; } = DeviceInfo.VersionString;
/// <summary>
/// Gets or sets the version of the operating system.
/// </summary>
public Version Version { get; private set; } = DeviceInfo.Version;
/// <summary>
/// Gets or sets the platform or operating system of the device.
/// </summary>
public DevicePlatform Platform { get; private set; } = DeviceInfo.Platform;
/// <summary>
/// Gets or sets the idiom of the device.
/// </summary>
public DeviceIdiom Idiom { get; private set; } = DeviceInfo.Idiom;
/// <summary>
/// Gets or sets the type of device the application is running on.
/// </summary>
public DeviceType DeviceType { get; private set; } = DeviceInfo.DeviceType;
}
}

ClientInfoPage.xaml

<?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:resources="clr-namespace:TerminalMACS.Clients.App.Resx"
xmlns:vm="clr-namespace:TerminalMACS.Clients.App.ViewModels"
mc:Ignorable="d"
x:Class="TerminalMACS.Clients.App.Views.ClientInfoPage"
Title="{x:Static resources:AppResource.Title_ClientInfoPage}">
<ContentPage.BindingContext>
<vm:ClientInfoViewModel/>
</ContentPage.BindingContext>
<ContentPage.Content>
<StackLayout>
<Label Text="{x:Static resources:AppResource.AppId}"/>
<Label Text="{Binding AppId}" FontAttributes="Bold" Margin="10,0,0,10"/> <Label Text="{x:Static resources:AppResource.DeviceModel}"/>
<Label Text="{Binding Model}" FontAttributes="Bold" Margin="10,0,0,10"/> <Label Text="{x:Static resources:AppResource.DeviceManufacturer}"/>
<Label Text="{Binding Manufacturer}" FontAttributes="Bold" Margin="10,0,0,10"/> <Label Text="{x:Static resources:AppResource.DeviceName}"/>
<Label Text="{Binding Name}" FontAttributes="Bold" Margin="10,0,0,10"/> <Label Text="{x:Static resources:AppResource.DeviceVersionString}"/>
<Label Text="{Binding VersionString}" FontAttributes="Bold" Margin="10,0,0,10"/> <Label Text="{x:Static resources:AppResource.DevicePlatform}"/>
<Label Text="{Binding Platform}" FontAttributes="Bold" Margin="10,0,0,10"/> <Label Text="{x:Static resources:AppResource.DeviceIdiom}"/>
<Label Text="{Binding Idiom}" FontAttributes="Bold" Margin="10,0,0,10"/> <Label Text="{x:Static resources:AppResource.DeviceType}"/>
<Label Text="{Binding DeviceType}" FontAttributes="Bold" Margin="10,0,0,10"/>
</StackLayout>
</ContentPage.Content>
</ContentPage>

2.2. 读取手机联系人信息

Android和iOS工程具体实现联系人读取服务,使用到DependencyService获取服务功能,功能截图如下:

2.2.1. TerminalMACS.Clients.App

代码结构如下图:

2.2.1.1. 联系人实体类:Contacts.cs

目前只获取联系人名称、图片、电子邮件(可能多个)、电话号码(可能多个),更多可以扩展。

namespace TerminalMACS.Clients.App.Models
{
/// <summary>
/// Contact information entity.
/// </summary>
public class Contact
{
/// <summary>
/// Gets or sets the name
/// </summary>
public string Name { get; set; }
/// <summary>
/// Gets or sets the image
/// </summary>
public string Image { get; set; }
/// <summary>
/// Gets or sets the emails
/// </summary>
public string[] Emails { get; set; }
/// <summary>
/// Gets or sets the phone numbers
/// </summary>
public string[] PhoneNumbers { get; set; }
}
}
2.2.1.2. 联系人服务接口:IContactsService.cs

包括:

  • 一个联系人获取请求接口:RetrieveContactsAsync
  • 一个读取一条联系人结果通知事件:OnContactLoaded

该接口由具体平台(Android和iOS)实现。

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using TerminalMACS.Clients.App.Models; namespace TerminalMACS.Clients.App.Services
{
/// <summary>
/// Read a contact record notification event parameter.
/// </summary>
public class ContactEventArgs:EventArgs
{
public Contact Contact { get; }
public ContactEventArgs(Contact contact)
{
Contact = contact;
}
} /// <summary>
/// Contact service interface, which is required for Android and iOS terminal specific
/// contact acquisition service needs to implement this interface.
/// </summary>
public interface IContactsService
{
/// <summary>
/// Read a contact record and notify the shared library through this event.
/// </summary>
event EventHandler<ContactEventArgs> OnContactLoaded;
/// <summary>
/// Loading or not
/// </summary>
bool IsLoading { get; }
/// <summary>
/// Try to get all contact information
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
Task<IList<Contact>> RetrieveContactsAsync(CancellationToken? token = null);
}
}
2.2.1.3. 联系人VM:ContactViewModel.cs

VM提供下面两个功能:

  1. 全部联系人加载。
  2. 联系人关键字查询。
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.Resx;
using TerminalMACS.Clients.App.Services;
using Xamarin.Forms; namespace TerminalMACS.Clients.App.ViewModels
{
/// <summary>
/// Contact page ViewModel
/// </summary>
public class ContactViewModel : BaseViewModel
{
/// <summary>
/// Contact service interface
/// </summary>
IContactsService _contactService;
private string _SearchText;
/// <summary>
/// Gets or sets the search text of the contact list.
/// </summary>
public string SearchText
{
get { return _SearchText; }
set
{
SetProperty(ref _SearchText, value);
}
}
/// <summary>
/// The search contact command.
/// </summary>
public ICommand RaiseSearchCommand { get; }
/// <summary>
/// The contact list.
/// </summary>
public ObservableCollection<Contact> Contacts { get; set; }
private List<Contact> _FilteredContacts;
/// <summary>
/// Contact filter list.
/// </summary>
public List<Contact> FilteredContacts {
get { return _FilteredContacts; }
set
{
SetProperty(ref _FilteredContacts, value);
}
}
public ContactViewModel()
{
_contactService = DependencyService.Get<IContactsService>();
Contacts = new ObservableCollection<Contact>();
Xamarin.Forms.BindingBase.EnableCollectionSynchronization(Contacts, null, ObservableCollectionCallback);
_contactService.OnContactLoaded += OnContactLoaded;
LoadContacts();
RaiseSearchCommand = new Command(RaiseSearchHandle);
} /// <summary>
/// Filter contact list
/// </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
/// Enable cross thread updates for collections
/// </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>
/// Received a event notification that a contact information was successfully read.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnContactLoaded(object sender, ContactEventArgs e)
{
Contacts.Add(e.Contact);
RaiseSearchHandle();
} /// <summary>
/// Read contact information asynchronously
/// </summary>
/// <returns></returns>
async Task LoadContacts()
{
try
{
await _contactService.RetrieveContactsAsync();
}
catch (TaskCanceledException)
{
Console.WriteLine(AppResource.TaskCancelled);
}
}
}
}
2.2.1.4. 联系人展示页面:ContactPage.xaml

简单的布局,一个StackLayout布局容器竖直排列,一个SearchBar提供关键字搜索功能。

<?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:resources="clr-namespace:TerminalMACS.Clients.App.Resx"
xmlns:vm="clr-namespace:TerminalMACS.Clients.App.ViewModels"
xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
mc:Ignorable="d"
Title="{x:Static resources:AppResource.Title_ContactPage}"
x:Class="TerminalMACS.Clients.App.Views.ContactPage"
ios:Page.UseSafeArea="true">
<ContentPage.BindingContext>
<vm:ContactViewModel/>
</ContentPage.BindingContext>
<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>

2.2.2. Android

代码结构如下图:

  • AndroidManifest.xml:写入读、写联系人权限请求。
  • ContactsService.cs:具体的联系人权限请求、数据读取操作。
  • MainActivity.cs:接收权限请求结果
  • MainApplicaion.cs:此类未添加任务关键代码,但必不可少,否则无法正确弹出权限请求窗口。
  • PermissionUtil.cs:权限请求结果判断

2.2.2.1. AndroidManifest.xml添加权限

只添加下面这一行即可:

<uses-permission android:name="android.permission.READ_CONTACTS" />

2.2.2.2. ContactsService.cs

Android联系人获取实现服务,实现IContactsService。注意命名空间上的特性代码,必须添加上这个特性后,在前面的联系人VM中才能使用DependencyService.Get()获取此服务实例,默认服务是单例的:

[assembly: Xamarin.Forms.Dependency(typeof(TerminalMACS.Clients.App.iOS.Services.ContactsService))]
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; [assembly: Xamarin.Forms.Dependency(typeof(TerminalMACS.Clients.App.iOS.Services.ContactsService))]
namespace TerminalMACS.Clients.App.iOS.Services
{
/// <summary>
/// Contact service.
/// </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>
/// Asynchronous request permission
/// </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>
/// Request contact asynchronously. This method is called by the interface.
/// </summary>
/// <param name="cancelToken"></param>
/// <returns></returns>
public async Task<IList<Contact>> RetrieveContactsAsync(CancellationToken? cancelToken = null)
{
requestStop = false; if (!cancelToken.HasValue)
cancelToken = CancellationToken.None; // We create a TaskCompletionSource of decimal
var taskCompletionSource = new TaskCompletionSource<IList<Contact>>(); // Registering a lambda into the cancellationToken
cancelToken.Value.Register(() =>
{
// We received a cancellation message, cancel the TaskCompletionSource.Task
requestStop = true;
taskCompletionSource.TrySetCanceled();
}); _isLoading = true; var task = LoadContactsAsync(); // Wait for the first task to finish among the two
var completedTask = await Task.WhenAny(task, taskCompletionSource.Task);
_isLoading = false; return await completedTask; } /// <summary>
/// Load contacts asynchronously, fact reading method of address book.
/// </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;
} }
}

2.2.2.3. MainActivity.cs

代码简单,只在OnRequestPermissionsResult方法中接收权限请求结果:

// The contact service processes the result of the permission request.
ContactsService.OnRequestPermissionsResult(requestCode, permissions, grantResults);

2.2.3. iOS

代码结构如下图:

  • ContactsService.cs:具体的联系人权限请求、数据读取操作。
  • Info.plist:权限请求时描述文件

2.2.3.1. ContactsService.cs

iOS具体的联系人读取服务,实现IContactsService接口,原理同Android联系人服务类似,本人无调试环境,iOS此功能未测试。

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; [assembly: Xamarin.Forms.Dependency(typeof(TerminalMACS.Clients.App.iOS.Services.ContactsService))]
namespace TerminalMACS.Clients.App.iOS.Services
{
/// <summary>
/// Contact service.
/// </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>
/// Asynchronous request permission
/// </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>
/// Request contact asynchronously. This method is called by the interface.
/// </summary>
/// <param name="cancelToken"></param>
/// <returns></returns>
public async Task<IList<Contact>> RetrieveContactsAsync(CancellationToken? cancelToken = null)
{
requestStop = false; if (!cancelToken.HasValue)
cancelToken = CancellationToken.None; // We create a TaskCompletionSource of decimal
var taskCompletionSource = new TaskCompletionSource<IList<Contact>>(); // Registering a lambda into the cancellationToken
cancelToken.Value.Register(() =>
{
// We received a cancellation message, cancel the TaskCompletionSource.Task
requestStop = true;
taskCompletionSource.TrySetCanceled();
}); _isLoading = true; var task = LoadContactsAsync(); // Wait for the first task to finish among the two
var completedTask = await Task.WhenAny(task, taskCompletionSource.Task);
_isLoading = false; return await completedTask; } /// <summary>
/// Load contacts asynchronously, fact reading method of address book.
/// </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;
}
}
}

2.2.3.2. Info.plist

联系人权限请求说明

2.3. 应用本地化

使用资源文件实现本地化,目前只做了中、英文。

资源文件如下:

指定默认区域性

要使资源文件可正常使用,应用程序必须指定 NeutralResourcesLanguage。 在共享项目中,应自定义 AssemblyInfo.cs 文件以指定默认区域性 。 以下代码演示如何在 AssemblyInfo.cs 文件中将 NeutralResourcesLanguage 设置为 zh-CN (摘自官方文档:https://docs.microsoft.com/zh-cn/samples/xamarin/xamarin-forms-samples/usingresxlocalization/,后经测试,注释下面这段代码也能正常本地化):

[assembly: NeutralResourcesLanguage("zh-CN")]

XAML中使用

引入资源文件命名空间

xmlns:resources="clr-namespace:TerminalMACS.Clients.App.Resx"

具体使用如

<Label Text="{x:Static resources:AppResource.ClientName_AboutPage}" FontAttributes="Bold"/>

3. 关于TerminalMACS及本客户端

3.1. TermainMACS

多终端资源管理与检测系统,包含多个子进程模块,目前只开发了Xamarin.Forms客户端,下一步开发服务端,使用 .NET 5 Web API开发,基于Abp vNext搭建。

3.2. Xamarin.Forms客户端

作为TerminalMACS系统的一个子进程模块,目前只开发了手机基本信息获取、联系人信息获取、本地化功能,后续开发服务端时,会配合添加通信功能,比如连接服务端验证、主动推送已获取资源等。

3.3. 关于项目开源

Xamarin.Forms客户端第一版的更多相关文章

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

    Xamarin.Forms读取并展示Android和iOS通讯录 - TerminalMACS客户端 本文同步更新地址: https://dotnet9.com/11520.html https:// ...

  2. Xamarin.Forms 简介

    An Introduction to Xamarin.Forms 来源:http://developer.xamarin.com/guides/cross-platform/xamarin-forms ...

  3. Xamarin.Forms WebView

    目前本地或网络的网页内容和文件加载 WebView是在您的应用程序显示Web和HTML内容的视图.不像OpenUri,这需要用户在Web浏览器的设备上,WebView中显示您的应用程序内的HTML内容 ...

  4. xamarin.forms之使用CarouselView插件模仿网易新闻导航

    在APP中基本都能见到类似网易.今日头条等上边横向导航条,下边是左右滑动的页面,之前做iOS的时候模仿实现过,https://github.com/ywcui/ViewPagerndicator,在做 ...

  5. Xamarin Forms:小马过河,王者归来

    因为我媳妇的原因,去年下半年从零开始学习Android原生开发,做了一个答题库app.整体给我的感觉是入门难度不大,前期折腾一番,大部分时间都是花在开发上面,其实任何一门语言都是如此. 今年我又有另一 ...

  6. Xamarin.Forms学习之XAML命名空间

    大家好,我又悄咪咪的来了,在上一篇的Xamarin文章中简单介绍了Xamarin的安装过程,妈蛋没想到很多小朋友很感激我,让他们成功的安装了Xamarin,然后......成功的显示了经典的两个单词( ...

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

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

  8. Xamarin.Forms 免费电子书

    Xamarin Evolve 正在举行,现在已经放出2本免费的Xamarin.Forms 免费电子书,据现场的同学说这两天还有Xamarin.Forms 重磅消息发布: Creating Mobile ...

  9. 老司机学新平台 - Xamarin Forms开发框架之MvvmCross插件精选

    在前两篇老司机学Xamarin系列中,简单介绍了Xamarin开发环境的搭建以及Prism和MvvmCross这两个开发框架.不同的框架,往往不仅仅使用不同的架构风格,同时社区活跃度不同,各种功能模块 ...

随机推荐

  1. MySQL集群MGR架构for单主模式

    本文转载自: https://www.93bok.com MGR简介 MySQL Group Replication(简称MGR)是MySQL官方于2016年12月推出的一个全新的高可用与高扩展的解决 ...

  2. Windows下安装虚拟机

    一.准备工作 1.下载centos7操作系统 阿里巴巴站点: http://mirrors.aliyun.com/centos/7/isos/x86_64/ 2.下载VMware虚假机 可以直接通过3 ...

  3. NLP自然语言处理入门-- 文本预处理Pre-processing

    引言 自然语言处理NLP(nature language processing),顾名思义,就是使用计算机对语言文字进行处理的相关技术以及应用.在对文本做数据分析时,我们一大半的时间都会花在文本预处理 ...

  4. 06 yarn是什么

    yarn集群中有两个角色: 主节点:Resource Manager  1台 从节点:Node Manager   N台 Resource Manager一般安装在一台专门的机器上 Node Mana ...

  5. vue-element-admin 模板 登录页面 post请求通过django的csrf认证,处理304错误

    经过一天的研究,终于把 vue-admin-template 模板的 post 请求 和django的api 弄通了 没有了那该死的304报错了 直接贴代码: 在main.js中 我直接给设置了一个 ...

  6. python虚拟环境安装使用

    # 安装 pip install virtualenv pip install virtualenvwrapper-win # win环境下 liunx下不需要-win #创建一个文件夹并cd进去mk ...

  7. opt目录

    在linux环境测试时,会部署到/opt目录下,这是为何呢? 下面来详解Linux的/opt目录: /opt:用户级的程序目录 这里主要存放那些可选的程序. 比如,你想部署firefox测试版,那就装 ...

  8. C语言程序设计(十二) 结构体和共用体

    第十二章 结构体和共用体 当需要表示复杂对象时,仅使用几个基本数据类型显然是不够的 根本的解决方法是允许用户自定义数据类型 构造数据类型(复合数据类型)允许用户根据实际需要利用已有的基本数据类型来构造 ...

  9. requests.exceptions.SSLError报错

    requests.exceptions.SSLError: HTTPSConnectionPool(host='www.baidu.com', port=443): Max retries excee ...

  10. C++ 判断两个圆是否有交集

    #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include <math.h> #include <easyx.h ...