平台特性 #

平台特性概述 #

为什么需要平台特性? #

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