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. 这么香的Chrome插件,你都安装了吗?

    工欲善其事必先利其器,今天长话短说,介绍13个敏捷.高效的Chrome插件 根据使用方式,本人将其划分为三大类: 开发者工具 日常效率工具类 浏览器管理类 开发者工具 1. Web Developer ...

  2. volatile和synchronized到底啥区别?多图文讲解告诉你

    你有一个思想,我有一个思想,我们交换后,一个人就有两个思想 If you can NOT explain it simply, you do NOT understand it well enough ...

  3. JZOJ 1349. 最大公约数 (Standard IO)

    1349. 最大公约数 (Standard IO) Time Limits: 1000 ms Memory Limits: 65536 KB Description 小菜的妹妹小诗就要读小学了!正所谓 ...

  4. ES6的编程风格

    1,建议使用let替代var 2,全局常量使用const,多使用const有利于提高程序的运行效率. const有两个好处:一是阅读代码的人立刻会意识到不应该修改这个值,二是防止无意间修改变量值导致错 ...

  5. c++作业22题

    一.单选题(共22题,100.0分) 1 已知int i=5,下列do-while循环语句的循环次数是 do{ cout<<i - -<<endl; i - -; }while ...

  6. .net webapi 接收保存图片到服务器,并居中剪裁压缩图片

    原文链接:https:////www.cnblogs.com/Jackyye/p/12510943.html 每天解决一些c#小问题,在写微信小程序,或者一些手机软件接口,我们经常要用到上传图片到服务 ...

  7. turtle学习笔记续集

    turtle(海龟)是Python重要的标准库之一,它能够进行基本的图形绘制.turtle图形绘制的概念诞生于1969年,成功应用于LOGO编程语言. turtle库绘制图形有一个基本框架:一个小海龟 ...

  8. TCP建立连接(三次握手)

    TCP的三次握手 TCP作为可靠稳定的传输方式要建立会话,会话的发起者是请求者(客户机),下文中A为客户机,B为服务器. 1.客户机(A)从closed状态转化为主动打开,服务器(B)此时从close ...

  9. 记一次修改mysql密码

    因为马大哈的原因,没有记录自己服务器mysql的密码,试来试去试不出来只好选择重置密码. 直接上命令: 首先 vim /etc/my.cnf 在my.cnf文件中,在[mysqld]的段中加上 ski ...

  10. Java注解 看这一篇就够了

    注解 1.概念 注解:说明程序的.给计算机看的 注释:用文字描述程序的.给程序员看的 注解的定义:注解(Annotation),也叫元数据.一种代码级别的说明.它是JDK1.5及以后版本引入的一个特性 ...