Xamarin中如何根据目标平台为自定义编辑器控件使用不同基类以复用绑定属性代码?
跨平台编辑器控件的代码复用优化方案
我最近在做Xamarin.Forms项目时遇到了个小麻烦:iOS平台用自定义的CustomIOSEditor(继承自常规Editor),UWP平台用CustomUWPEditor(继承自SfRichTextEditor),这俩控件的核心绑定属性StringResultCommand和StringResultCommandParameter完全一样,但之前用ContentView嵌套OnPlatform的方式切换控件,问题一堆——多余的嵌套拖慢性能、代码重复不说,还时不时出现奇怪的绑定问题(虽然没完全定位,但总觉得不对劲)。
先给大家看看我之前的实现代码,感受下那种难受的嵌套:
<ContentView> <OnPlatform x:TypeArguments="View"> <OnPlatform.Platforms> <On Platform="iOS"> <controls:CustomIOSEditor/> </On> <On Platform="UWP"> <controls:CustomUWPEditor/> </On> </OnPlatform.Platforms> </OnPlatform> </ContentView>
还有两个平台控件的重复代码:
iOS自定义控件:
public class CustomIOSEditor : Editor // 使用常规Xamarin Editor { public static readonly BindableProperty StringResultCommandProperty = BindableProperty.Create( nameof(StringResultCommand), typeof(ICommand), typeof(CustomIOSEditor), default(ICommand)); public object StringResultCommandParameter { get => GetValue(StringResultCommandParameterProperty); set => SetValue(StringResultCommandParameterProperty, value); } }
UWP自定义控件:
public class CustomUWPEditor : SfRichTextEditor // 此处使用SfRichTextEditor { public static readonly BindableProperty StringResultCommandProperty = BindableProperty.Create( nameof(StringResultCommand), typeof(ICommand), typeof(CustomUWPEditor), default(ICommand)); public object StringResultCommandParameter { get => GetValue(StringResultCommandParameterProperty); set => SetValue(StringResultCommandParameterProperty, value); } }
优化思路:共享属性+统一控件封装
核心就是把重复的绑定属性抽出来共享,再封装一个跨平台控件来处理平台切换逻辑,彻底干掉嵌套和重复代码。
1. 提取共享绑定属性
先创建一个静态类,把两个控件共有的绑定属性定义成附加属性,这样两个平台控件都能复用:
public static class EditorSharedProperties { public static readonly BindableProperty StringResultCommandProperty = BindableProperty.CreateAttached( "StringResultCommand", typeof(ICommand), typeof(EditorSharedProperties), default(ICommand)); public static readonly BindableProperty StringResultCommandParameterProperty = BindableProperty.CreateAttached( "StringResultCommandParameter", typeof(object), typeof(EditorSharedProperties), null); public static void SetStringResultCommand(BindableObject bindable, ICommand value) { bindable.SetValue(StringResultCommandProperty, value); } public static ICommand GetStringResultCommand(BindableObject bindable) { return (ICommand)bindable.GetValue(StringResultCommandProperty); } public static void SetStringResultCommandParameter(BindableObject bindable, object value) { bindable.SetValue(StringResultCommandParameterProperty, value); } public static object GetStringResultCommandParameter(BindableObject bindable) { return bindable.GetValue(StringResultCommandParameterProperty); } }
2. 修改平台控件复用属性
现在把两个平台控件里的重复属性替换成对共享附加属性的包装,这样就不用重复定义BindableProperty了:
iOS控件修改后:
public class CustomIOSEditor : Editor { public ICommand StringResultCommand { get => EditorSharedProperties.GetStringResultCommand(this); set => EditorSharedProperties.SetStringResultCommand(this, value); } public object StringResultCommandParameter { get => EditorSharedProperties.GetStringResultCommandParameter(this); set => EditorSharedProperties.SetStringResultCommandParameter(this, value); } }
UWP控件修改后:
public class CustomUWPEditor : SfRichTextEditor { public ICommand StringResultCommand { get => EditorSharedProperties.GetStringResultCommand(this); set => EditorSharedProperties.SetStringResultCommand(this, value); } public object StringResultCommandParameter { get => EditorSharedProperties.GetStringResultCommandParameter(this); set => EditorSharedProperties.SetStringResultCommandParameter(this, value); } }
3. 封装跨平台统一控件
接下来创建一个自定义ContentView,在代码里根据当前平台加载对应的编辑器控件,同时把自身的属性绑定到平台控件上:
public class CrossPlatformRichTextEditor : ContentView { public CrossPlatformRichTextEditor() { LoadPlatformSpecificEditor(); } private void LoadPlatformSpecificEditor() { View platformEditor; switch (Device.RuntimePlatform) { case Device.iOS: platformEditor = new CustomIOSEditor(); break; case Device.UWP: platformEditor = new CustomUWPEditor(); break; default: // 这里可以根据需求处理其他平台,比如默认用普通Editor platformEditor = new Editor(); break; } // 将当前控件的属性绑定到平台编辑器上 platformEditor.SetBinding(EditorSharedProperties.StringResultCommandProperty, new Binding(nameof(StringResultCommand), source: this)); platformEditor.SetBinding(EditorSharedProperties.StringResultCommandParameterProperty, new Binding(nameof(StringResultCommandParameter), source: this)); Content = platformEditor; } // 对外暴露的属性,直接复用共享附加属性 public ICommand StringResultCommand { get => EditorSharedProperties.GetStringResultCommand(this); set => EditorSharedProperties.SetStringResultCommand(this, value); } public object StringResultCommandParameter { get => EditorSharedProperties.GetStringResultCommandParameter(this); set => EditorSharedProperties.SetStringResultCommandParameter(this, value); } }
4. 在XAML中简洁使用
现在终于可以摆脱繁琐的嵌套了,直接在XAML里用这个封装好的跨平台控件就行:
<controls:CrossPlatformRichTextEditor StringResultCommand="{Binding YourCommand}" StringResultCommandParameter="{Binding YourParameter}" />
优化后的好处
- 彻底消除了不必要的UI嵌套层级,性能更优
- 绑定属性只定义一次,完全避免了代码重复
- 平台切换逻辑集中在一个类里,后续维护更方便
- 绑定逻辑更清晰,减少了莫名的绑定异常概率
内容的提问来源于stack exchange,提问作者DiddanDo




