平台特性 #
平台特性概述 #
为什么需要平台特性? #
text
┌─────────────────────────────────────────────────────────────┐
│ 平台特性需求 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Xamarin.Forms 提供了跨平台的抽象,但有时需要: │
│ │
│ ├── 访问平台特定 API │
│ │ - 相机、相册 │
│ │ - 蓝牙、NFC │
│ │ - 推送通知 │
│ │ - 生物识别 │
│ │ │
│ ├── 自定义控件外观 │
│ │ - 原生控件样式定制 │
│ │ - 特殊视觉效果 │
│ │ │
│ └── 使用原生库 │
│ - 第三方 SDK │
│ - 原生性能优化 │
│ │
└─────────────────────────────────────────────────────────────┘
平台特性实现方式 #
text
┌─────────────────────────────────────────────────────────────┐
│ 实现方式 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. DependencyService │
│ - 服务接口 + 平台实现 │
│ - 适合调用平台 API │
│ │
│ 2. 自定义渲染器 (Custom Renderer) │
│ - 完全控制控件渲染 │
│ - 适合深度定制控件 │
│ │
│ 3. Effects │
│ - 轻量级样式修改 │
│ - 适合简单外观调整 │
│ │
│ 4. 平台特定 XAML │
│ - OnPlatform / OnIdiom │
│ - 适合简单平台差异 │
│ │
└─────────────────────────────────────────────────────────────┘
平台特定 XAML #
OnPlatform #
xml
<Label>
<Label.FontSize>
<OnPlatform x:TypeArguments="x:Double">
<On Platform="iOS" Value="16" />
<On Platform="Android" Value="14" />
<On Platform="UWP" Value="18" />
</OnPlatform>
</Label.FontSize>
</Label>
<Label FontSize="{OnPlatform iOS=16, Android=14, UWP=18}" />
xml
<Label Text="Hello">
<Label.FontFamily>
<OnPlatform x:TypeArguments="x:String">
<On Platform="iOS" Value="SF Pro Display" />
<On Platform="Android" Value="Roboto" />
</OnPlatform>
</Label.FontFamily>
</Label>
OnIdiom #
xml
<Label>
<Label.FontSize>
<OnIdiom x:TypeArguments="x:Double">
<On Idiom="Phone" Value="14" />
<On Idiom="Tablet" Value="18" />
<On Idiom="Desktop" Value="16" />
</OnIdiom>
</Label.FontSize>
</Label>
<Label FontSize="{OnIdiom Phone=14, Tablet=18, Desktop=16}" />
代码中使用 #
csharp
if (Device.RuntimePlatform == Device.iOS)
{
Padding = new Thickness(0, 20, 0, 0);
}
else if (Device.RuntimePlatform == Device.Android)
{
Padding = new Thickness(0, 24, 0, 0);
}
csharp
var fontSize = Device.Idiom == TargetIdiom.Phone ? 14 : 18;
DependencyService #
DependencyService 概述 #
text
┌─────────────────────────────────────────────────────────────┐
│ DependencyService │
├─────────────────────────────────────────────────────────────┤
│ │
│ 工作流程: │
│ │
│ ┌─────────────────┐ │
│ │ 共享代码 │ │
│ │ (接口定义) │ │
│ └────────┬────────┘ │
│ │ │
│ │ DependencyService.Get<ITextToSpeech>() │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ DependencyService │ │
│ │ 根据当前平台返回对应实现 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ┌─────┴─────┐ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────┐ ┌──────┐ │
│ │ iOS │ │Android│ │
│ │ 实现 │ │ 实现 │ │
│ └──────┘ └──────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
定义接口 #
csharp
public interface ITextToSpeech
{
void Speak(string text);
}
public interface ICamera
{
Task<PhotoResult> TakePhotoAsync();
Task<PhotoResult> PickPhotoAsync();
}
public interface ILocalNotification
{
void Show(string title, string message, int id = 0);
void Cancel(int id);
}
public class PhotoResult
{
public Stream Stream { get; set; }
public string Path { get; set; }
}
iOS 实现 #
csharp
using AVFoundation;
[assembly: Dependency(typeof(TextToSpeech_iOS))]
namespace MyApp.iOS.Services
{
public class TextToSpeech_iOS : ITextToSpeech
{
public void Speak(string text)
{
var synthesizer = new AVSpeechSynthesizer();
var utterance = new AVSpeechUtterance(text)
{
Rate = AVSpeechUtterance.DefaultSpeechRate,
Voice = AVSpeechSynthesisVoice.FromLanguage("zh-CN"),
Volume = 1.0f,
PitchMultiplier = 1.0f
};
synthesizer.SpeakUtterance(utterance);
}
}
}
Android 实现 #
csharp
using Android.Speech.Tts;
[assembly: Dependency(typeof(TextToSpeech_Android))]
namespace MyApp.Droid.Services
{
public class TextToSpeech_Android : Java.Lang.Object, ITextToSpeech, TextToSpeech.IOnInitListener
{
private TextToSpeech _tts;
private string _textToSpeak;
public void Speak(string text)
{
_textToSpeak = text;
if (_tts == null)
{
_tts = new TextToSpeech(Android.App.Application.Context, this);
}
else
{
SpeakNow();
}
}
public void OnInit(OperationResult status)
{
if (status == OperationResult.Success)
{
SpeakNow();
}
}
private void SpeakNow()
{
if (_tts != null && !string.IsNullOrEmpty(_textToSpeak))
{
_tts.Speak(_textToSpeak, QueueMode.Flush, null, null);
}
}
}
}
使用 DependencyService #
csharp
public class MainViewModel
{
private readonly ITextToSpeech _textToSpeech;
public MainViewModel()
{
_textToSpeech = DependencyService.Get<ITextToSpeech>();
}
public ICommand SpeakCommand => new Command<string>(text =>
{
_textToSpeech?.Speak(text);
});
}
xml
<Entry Text="{Binding TextToSpeak}" />
<Button Text="朗读"
Command="{Binding SpeakCommand}"
CommandParameter="{Binding TextToSpeak}" />
自定义渲染器 #
自定义渲染器概述 #
text
┌─────────────────────────────────────────────────────────────┐
│ 自定义渲染器 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Xamarin.Forms 控件 → 原生控件映射: │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Xamarin.Forms │ │ iOS │ │
│ │ Button │ ───► │ UIButton │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Xamarin.Forms │ │ Android │ │
│ │ Button │ ───► │ AppCompatButton│ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ 自定义渲染器允许: │
│ - 修改原生控件属性 │
│ - 添加原生控件功能 │
│ - 完全替换控件渲染 │
│ │
└─────────────────────────────────────────────────────────────┘
创建自定义控件 #
csharp
public class CustomButton : Button
{
public static readonly BindableProperty CornerRadiusProperty =
BindableProperty.Create(nameof(CornerRadius), typeof(int), typeof(CustomButton), 0);
public int CornerRadius
{
get => (int)GetValue(CornerRadiusProperty);
set => SetValue(CornerRadiusProperty, value);
}
public static readonly BindableProperty BorderColorProperty =
BindableProperty.Create(nameof(BorderColor), typeof(Color), typeof(CustomButton), Color.Default);
public Color BorderColor
{
get => (Color)GetValue(BorderColorProperty);
set => SetValue(BorderColorProperty, value);
}
}
iOS 渲染器 #
csharp
[assembly: ExportRenderer(typeof(CustomButton), typeof(CustomButtonRenderer_iOS))]
namespace MyApp.iOS.Renderers
{
public class CustomButtonRenderer_iOS : ButtonRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
if (e.NewElement != null && Control != null)
{
var button = (CustomButton)e.NewElement;
UpdateCornerRadius(button);
UpdateBorderColor(button);
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == CustomButton.CornerRadiusProperty.PropertyName)
{
UpdateCornerRadius((CustomButton)Element);
}
else if (e.PropertyName == CustomButton.BorderColorProperty.PropertyName)
{
UpdateBorderColor((CustomButton)Element);
}
}
private void UpdateCornerRadius(CustomButton button)
{
Control.Layer.CornerRadius = button.CornerRadius;
Control.Layer.MasksToBounds = true;
}
private void UpdateBorderColor(CustomButton button)
{
Control.Layer.BorderColor = button.BorderColor.ToCGColor();
Control.Layer.BorderWidth = 1;
}
}
}
Android 渲染器 #
csharp
[assembly: ExportRenderer(typeof(CustomButton), typeof(CustomButtonRenderer_Android))]
namespace MyApp.Droid.Renderers
{
public class CustomButtonRenderer_Android : ButtonRenderer
{
public CustomButtonRenderer_Android(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
if (e.NewElement != null && Control != null)
{
var button = (CustomButton)e.NewElement;
UpdateCornerRadius(button);
UpdateBorderColor(button);
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == CustomButton.CornerRadiusProperty.PropertyName)
{
UpdateCornerRadius((CustomButton)Element);
}
else if (e.PropertyName == CustomButton.BorderColorProperty.PropertyName)
{
UpdateBorderColor((CustomButton)Element);
}
}
private void UpdateCornerRadius(CustomButton button)
{
var gradientDrawable = new GradientDrawable();
gradientDrawable.SetCornerRadius(button.CornerRadius * Context.Resources.DisplayMetrics.Density);
gradientDrawable.SetColor(button.BackgroundColor.ToAndroid());
gradientDrawable.SetStroke(2, button.BorderColor.ToAndroid());
Control.SetBackground(gradientDrawable);
}
private void UpdateBorderColor(CustomButton button)
{
UpdateCornerRadius(button);
}
}
}
使用自定义控件 #
xml
<local:CustomButton Text="自定义按钮"
CornerRadius="20"
BorderColor="Blue"
BackgroundColor="LightBlue"
TextColor="Blue"
HorizontalOptions="Center"
Padding="20,10" />
Effects #
Effects 概述 #
text
┌─────────────────────────────────────────────────────────────┐
│ Effects vs 渲染器 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Effects: │
│ ├── 轻量级 │
│ ├── 可复用 │
│ ├── 不需要创建新控件 │
│ ├── 适合简单样式修改 │
│ └── 可叠加多个 Effects │
│ │
│ 自定义渲染器: │
│ ├── 完全控制 │
│ ├── 需要创建新控件 │
│ ├── 适合深度定制 │
│ └── 一个控件一个渲染器 │
│ │
└─────────────────────────────────────────────────────────────┘
创建 Effect #
csharp
public class FocusEffect : RoutingEffect
{
public FocusEffect() : base("MyApp.FocusEffect")
{
}
}
iOS Effect 实现 #
csharp
[assembly: ResolutionGroupName("MyApp")]
[assembly: ExportEffect(typeof(FocusEffect_iOS), "FocusEffect")]
namespace MyApp.iOS.Effects
{
public class FocusEffect_iOS : PlatformEffect
{
UIColor _originalColor;
UIColor _focusColor = UIColor.FromRGB(33, 150, 243);
protected override void OnAttached()
{
if (Control != null)
{
_originalColor = Control.BackgroundColor;
}
}
protected override void OnDetached()
{
if (Control != null)
{
Control.BackgroundColor = _originalColor;
}
}
protected override void OnElementPropertyChanged(PropertyChangedEventArgs args)
{
base.OnElementPropertyChanged(args);
if (args.PropertyName == VisualElement.IsFocusedProperty.PropertyName)
{
if (Control != null)
{
if (((Entry)Element).IsFocused)
{
Control.BackgroundColor = _focusColor;
}
else
{
Control.BackgroundColor = _originalColor;
}
}
}
}
}
}
Android Effect 实现 #
csharp
[assembly: ResolutionGroupName("MyApp")]
[assembly: ExportEffect(typeof(FocusEffect_Android), "FocusEffect")]
namespace MyApp.Droid.Effects
{
public class FocusEffect_Android : PlatformEffect
{
Android.Graphics.Color _originalColor;
Android.Graphics.Color _focusColor = Android.Graphics.Color.Rgb(33, 150, 243);
protected override void OnAttached()
{
if (Control != null)
{
_originalColor = Control.BackgroundTintList?.GetDefaultColor() ?? Android.Graphics.Color.Transparent;
}
}
protected override void OnDetached()
{
if (Control != null)
{
Control.BackgroundTintList = ColorStateList.ValueOf(_originalColor);
}
}
protected override void OnElementPropertyChanged(PropertyChangedEventArgs args)
{
base.OnElementPropertyChanged(args);
if (args.PropertyName == VisualElement.IsFocusedProperty.PropertyName)
{
if (Control != null)
{
if (((Entry)Element).IsFocused)
{
Control.BackgroundTintList = ColorStateList.ValueOf(_focusColor);
}
else
{
Control.BackgroundTintList = ColorStateList.ValueOf(_originalColor);
}
}
}
}
}
}
使用 Effect #
xml
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:effects="clr-namespace:MyApp.Effects"
x:Class="MyApp.MainPage">
<StackLayout>
<Entry Placeholder="用户名">
<Entry.Effects>
<effects:FocusEffect />
</Entry.Effects>
</Entry>
<Entry Placeholder="密码" IsPassword="True">
<Entry.Effects>
<effects:FocusEffect />
</Entry.Effects>
</Entry>
</StackLayout>
</ContentPage>
csharp
var entry = new Entry { Placeholder = "用户名" };
entry.Effects.Add(new FocusEffect());
平台特定功能示例 #
相机拍照 #
csharp
public interface ICameraService
{
Task<string> TakePhotoAsync();
Task<string> PickPhotoAsync();
}
csharp
[assembly: Dependency(typeof(CameraService_iOS))]
namespace MyApp.iOS.Services
{
public class CameraService_iOS : ICameraService
{
private UIImagePickerController _imagePicker;
private TaskCompletionSource<string> _tcs;
public async Task<string> TakePhotoAsync()
{
_tcs = new TaskCompletionSource<string>();
_imagePicker = new UIImagePickerController
{
SourceType = UIImagePickerControllerSourceType.Camera,
MediaTypes = UIImagePickerController.AvailableMediaTypes(UIImagePickerControllerSourceType.Camera),
AllowsEditing = true
};
_imagePicker.FinishedPickingMedia += OnFinishedPickingMedia;
_imagePicker.Canceled += OnCanceled;
var viewController = UIApplication.SharedApplication.KeyWindow.RootViewController;
viewController.PresentViewController(_imagePicker, true, null);
return await _tcs.Task;
}
private void OnFinishedPickingMedia(object sender, UIImagePickerMediaPickedEventArgs e)
{
var image = e.EditedImage ?? e.OriginalImage;
var documents = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
var filename = Path.Combine(documents, $"photo_{DateTime.Now.Ticks}.jpg");
image.AsJPEG().Save(filename, false);
_imagePicker.DismissViewController(true, null);
_tcs.TrySetResult(filename);
}
private void OnCanceled(object sender, EventArgs e)
{
_imagePicker.DismissViewController(true, null);
_tcs.TrySetResult(null);
}
}
}
本地通知 #
csharp
public interface INotificationService
{
void Show(string title, string message, int id = 0);
void Cancel(int id);
}
csharp
[assembly: Dependency(typeof(NotificationService_Android))]
namespace MyApp.Droid.Services
{
public class NotificationService_Android : INotificationService
{
public void Show(string title, string message, int id = 0)
{
var context = Android.App.Application.Context;
var intent = context.PackageManager.GetLaunchIntentForPackage(context.PackageName);
var pendingIntent = PendingIntent.GetActivity(context, id, intent, PendingIntentFlags.Immutable);
var builder = new NotificationCompat.Builder(context, "default_channel")
.SetContentTitle(title)
.SetContentText(message)
.SetSmallIcon(Resource.Drawable.notification_icon)
.SetAutoCancel(true)
.SetContentIntent(pendingIntent);
var notificationManager = NotificationManagerCompat.From(context);
notificationManager.Notify(id, builder.Build());
}
public void Cancel(int id)
{
var context = Android.App.Application.Context;
var notificationManager = NotificationManagerCompat.From(context);
notificationManager.Cancel(id);
}
}
}
设备信息 #
csharp
public interface IDeviceInfoService
{
string GetDeviceModel();
string GetPlatform();
string GetVersion();
}
public class DeviceInfoService_iOS : IDeviceInfoService
{
public string GetDeviceModel()
{
return UIDevice.CurrentDevice.Model;
}
public string GetPlatform()
{
return "iOS";
}
public string GetVersion()
{
return UIDevice.CurrentDevice.SystemVersion;
}
}
public class DeviceInfoService_Android : IDeviceInfoService
{
public string GetDeviceModel()
{
return Android.OS.Build.Model;
}
public string GetPlatform()
{
return "Android";
}
public string GetVersion()
{
return Android.OS.Build.VERSION.Release;
}
}
平台特性最佳实践 #
1. 使用接口抽象 #
csharp
public interface IPlatformService
{
Task<string> DoSomethingAsync();
}
public class MainViewModel
{
private readonly IPlatformService _platformService;
public MainViewModel(IPlatformService platformService)
{
_platformService = platformService;
}
}
2. 处理平台差异 #
csharp
public class CameraService : ICameraService
{
public async Task<string> TakePhotoAsync()
{
if (Device.RuntimePlatform == Device.iOS)
{
}
else if (Device.RuntimePlatform == Device.Android)
{
}
return await DependencyService.Get<ICameraService>().TakePhotoAsync();
}
}
3. 错误处理 #
csharp
public async Task<string> TakePhotoAsync()
{
try
{
var cameraService = DependencyService.Get<ICameraService>();
if (cameraService == null)
{
throw new InvalidOperationException("Camera service not available");
}
return await cameraService.TakePhotoAsync();
}
catch (Exception ex)
{
Console.WriteLine($"Camera error: {ex.Message}");
return null;
}
}
下一步 #
现在你已经掌握了平台特性,接下来学习 高级主题,了解性能优化、部署发布等进阶内容!
最后更新:2026-03-29