我目前正在努力将我过去所做的自定义渲染器重构为自定义图标选择器的 MAUI 处理程序。 目前我根本无法让它工作,模拟器上呈现的选择器与默认的选择器完全相同。
我的自定义控件如下:
public partial class IconPicker : Picker
{
[AutoBindable]
private readonly ImageSource _source = null!;
[AutoBindable(DefaultValue = "1.0")]
private readonly double _strokeThickness;
[AutoBindable]
private readonly Color? _stroke;
[AutoBindable]
private readonly float _cornerRadius;
}
我开发的渲染器基本上添加了一个边框(圆角或不圆角)和一个图标到基本选择器,删除了 android 的下划线:
[Obsolete("This renderer is obsolete, it will be replaced in future release by corresponding handler/mapper")]
public class IconPickerRenderer(Context context) : PickerRenderer(context)
{
IconPicker? element;
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged(e);
element = (IconPicker)this.Element;
if (Control != null && this.Element != null && element.Source != null)
Control.Background = SetPickerUi(element);
}
private LayerDrawable SetPickerUi(IconPicker element)
{
GradientDrawable border = new GradientDrawable();
border.SetShape(ShapeType.Rectangle);
border.SetPadding(10, 10, 10, 10);
border.SetCornerRadius(element.CornerRadius);
border.SetColor(ColorStateList.ValueOf(element.BackgroundColor?.ToAndroid() ?? Android.Graphics.Color.Transparent));
border.SetStroke(Convert.ToInt32(element.StrokeThickness), element.Stroke?.ToAndroid() ?? Android.Graphics.Color.Black);
LayerDrawable layerDrawable = new([border, GetDrawable(element.Source).Result]);
layerDrawable.SetLayerInset(0, 20, 0, 0, 0);
return layerDrawable;
}
private async Task<Drawable> GetDrawable(ImageSource source)
{
Drawable drawable;
if (source is FontImageSource font)
drawable = new FontDrawable(Context!, font.Glyph, font.Color.ToAndroid(), Convert.ToInt32(font.Size), "MaterialIconsRound-Regular.otf"); // TODO: add font family and recover filename from font family
else
{
var renderer = new StreamImagesourceHandler();
Bitmap bitmapImg = await renderer.LoadImageAsync(source, Context);
drawable = new BitmapDrawable(Resources, Bitmap.CreateScaledBitmap(bitmapImg, 70, 70, true))
{
Gravity = GravityFlags.Right
};
}
return drawable;
}
}
根据可绑定属性设置,结果如下:
此渲染的 xaml 如下:
<ContentPage.Resources>
<x:Array Type="{x:Type x:String}" x:Key="array">
<x:String>Baboon</x:String>
<x:String>Capuchin Monkey</x:String>
<x:String>Blue Monkey</x:String>
<x:String>Squirrel Monkey</x:String>
<x:String>Golden Lion Tamarin</x:String>
<x:String>Howler Monkey</x:String>
<x:String>Japanese Macaque</x:String>
</x:Array>
</ContentPage.Resources>
<VerticalStackLayout Padding="20, 32" Spacing="20">
<Label Text="Basic Icon Picker:" HorizontalOptions="Center"/>
<yourNamespace:IconPicker MaximumWidthRequest="200"
Source="{FontImage FontFamily='MaterialIconsRound', Glyph={StaticResource IconMD_Expand_more}, Color={StaticResource DarkGray}}"
TextColor="{StaticResource DarkGray}" TitleColor="{StaticResource DarkGray}"
HorizontalTextAlignment="Center" Title="Select a monkey"
StrokeThickness="2" Stroke="{StaticResource DarkGray}"
ItemsSource="{StaticResource array}">
</yourNamespace:IconPicker>
<Label Text="Picker with rounded corners:" HorizontalOptions="Center"/>
<yourNamespace:IconPicker MaximumWidthRequest="200"
Source="{FontImage FontFamily='MaterialIconsRound', Glyph={StaticResource IconMD_Expand_more}, Color={StaticResource DarkGray}}"
TextColor="{StaticResource DarkGray}" TitleColor="{StaticResource DarkGray}"
HorizontalTextAlignment="Center" Title="Select a monkey"
CornerRadius="50" StrokeThickness="5" Stroke="{StaticResource DarkGray}"
ItemsSource="{StaticResource array}">
</yourNamespace:IconPicker>
</VerticalStackLayout>
我重构到 MAUI 处理程序的当前状态是:
public partial class IconPickerHandler : PickerHandler
{
//public static readonly new PropertyMapper<IconPicker, IconPickerHandler> Mapper = new(PickerHandler.Mapper)
//{
// [nameof(IconPicker.Source)] = MapSourceChanged,
// [nameof(IconPicker.StrokeThickness)] = MapStrokeThicknessChanged,
// [nameof(IconPicker.Stroke)] = MapStrokeChanged,
// [nameof(IconPicker.CornerRadius)] = MapCornerRadiusChanged,
//};
public IconPickerHandler() : base(Mapper) { }
}
// The android one
public partial class IconPickerHandler
{
public static void MapSourceChanged(IconPickerHandler handler, IconPicker view) { Debug.WriteLine(nameof(MapSourceChanged)); }
public static void MapStrokeThicknessChanged(IconPickerHandler handler, IconPicker view)
{
Debug.WriteLine(nameof(MapStrokeThicknessChanged));
if (handler.PlatformView.Background is LayerDrawable backgroundDrawable)
{
GradientDrawable? borderDrawable = (GradientDrawable?)backgroundDrawable.GetDrawable(0);
if (borderDrawable != null)
{
borderDrawable.Thickness = Convert.ToInt32(view.StrokeThickness);
}
}
}
public static void MapStrokeChanged(IconPickerHandler handler, IconPicker view)
{
if (handler.PlatformView.Background is LayerDrawable backgroundDrawable)
{
GradientDrawable? borderDrawable = (GradientDrawable?)backgroundDrawable.GetDrawable(0);
if (borderDrawable != null)
{
borderDrawable.SetStroke(borderDrawable.Thickness, view.Stroke?.ToPlatform() ?? Android.Graphics.Color.Black);
}
}
Debug.WriteLine(nameof(MapStrokeChanged));
}
public static void MapCornerRadiusChanged(IconPickerHandler handler, IconPicker view)
{
if (handler.PlatformView.Background is LayerDrawable backgroundDrawable)
{
GradientDrawable? borderDrawable = (GradientDrawable?)backgroundDrawable.GetDrawable(0);
if (borderDrawable != null)
{
borderDrawable.SetCornerRadius(view.CornerRadius);
}
}
Debug.WriteLine(nameof(MapCornerRadiusChanged));
}
protected override void ConnectHandler(MauiPicker platformView)
{
base.ConnectHandler(platformView);
IconPicker element = (IconPicker)VirtualView;
GradientDrawable borderDrawable = new GradientDrawable();
borderDrawable.SetShape(ShapeType.Rectangle);
borderDrawable.SetPadding(10, 10, 10, 10);
borderDrawable.SetCornerRadius(element.CornerRadius);
borderDrawable.SetColor(ColorStateList.ValueOf(element.BackgroundColor?.ToPlatform() ?? Android.Graphics.Color.Transparent));
borderDrawable.SetStroke(Convert.ToInt32(element.StrokeThickness), element.Stroke?.ToPlatform() ?? Android.Graphics.Color.Black);
LayerDrawable layerDrawable = new([borderDrawable, GetImageSourceAsDrawable(element.Source).Result]);
layerDrawable.SetLayerInset(0, 20, 0, 0, 0);
platformView.Background = layerDrawable;
}
private async Task<Drawable> GetImageSourceAsDrawable(ImageSource source)
{
Drawable drawable;
if (source is FontImageSource fontSource)
drawable = new FontDrawable(Context, fontSource.Glyph, fontSource.Color.ToPlatform(), Convert.ToInt32(fontSource.Size), "MaterialIconsRound-Regular.otf"); // TODO: add font family and recover filename from font family
else
{
var renderer = new StreamImagesourceHandler();
Bitmap bitmapImg = await renderer.LoadImageAsync(source, Context);
drawable = new BitmapDrawable(Context.Resources, Bitmap.CreateScaledBitmap(bitmapImg, 70, 70, true))
{
Gravity = GravityFlags.Right
};
}
return drawable;
}
}
编辑:用于测试目的的 fontDrawable 源代码,它所做的就是从资源/字体中设置的字体文件中获取字体并绘制字形文本:
internal class FontDrawable : Drawable
{
private int alpha = 255;
private int size;
private string text;
private readonly TextPaint paint = new TextPaint();
public override int IntrinsicWidth => this.size;
public override int IntrinsicHeight => this.size;
public override bool IsStateful => true;
public override int Opacity => this.alpha;
public FontDrawable(Context context, string text, Android.Graphics.Color iconColor, int iconSizeDP, string font = "ionicons.ttf")
{
this.text = text;
this.paint.SetTypeface(Typeface.CreateFromAsset(context.Assets, font));
this.paint.SetStyle(Android.Graphics.Paint.Style.Fill);
this.paint.TextAlign = Android.Graphics.Paint.Align.Center;
this.paint.Color = iconColor;
this.paint.AntiAlias = true;
this.size = GetPX(context, iconSizeDP);
this.SetBounds(0, 0, this.size, this.size);
}
public override void Draw(Canvas canvas)
{
this.paint.TextSize = this.Bounds.Height();
var textBounds = new Android.Graphics.Rect();
this.paint.GetTextBounds(this.text, 0, 1, textBounds);
var textHeight = textBounds.Height();
var textBottom = this.Bounds.Top + (this.paint.TextSize - textHeight) / 2f + textHeight - textBounds.Bottom;
canvas.DrawText(this.text, this.Bounds.Right - this.Bounds.Right / 10, textBottom, this.paint);
}
public override bool SetState(int[] stateSet)
{
var oldValue = paint.Alpha;
var newValue = stateSet.Any(s => s == global::Android.Resource.Attribute.StateEnabled) ? this.alpha : this.alpha / 2;
paint.Alpha = newValue;
return oldValue != newValue;
}
private static int GetPX(Context context, int sizeDP)
{
return (int)TypedValue.ApplyDimension(ComplexUnitType.Dip, sizeDP, context.Resources.DisplayMetrics);
}
private static bool IsEnabled(int[] stateSet)
{
return stateSet.Any(s => s == global::Android.Resource.Attribute.StateEnabled);
}
public override void SetAlpha(int alpha)
{
this.alpha = alpha;
this.paint.Alpha = alpha;
}
public override void SetColorFilter(ColorFilter colorFilter)
{
this.paint.SetColorFilter(colorFilter);
}
public override void ClearColorFilter()
{
this.paint.SetColorFilter(null);
}
}
我尝试在属性更改事件的 Map 方法上和 ConnectHandler 中设置平台视图属性,但似乎没有任何效果。 在调试中,ConnectHandler 平台代码正确执行。
带有处理程序的选择器结果不考虑任何更改并显示基本的 Android 选择器:
谢谢。
在您的共享代码中,创建一个部分处理程序
public partial class IconPickerHandler : PickerHandler
{
}
在您的 Android 平台中,在同一命名空间中创建一个部分处理程序
public partial class IconPickerHandler : PickerHandler
{
IconPicker Picker => VirtualView as IconPicker;
protected override void ConnectHandler(MauiPicker platformView)
{
base.ConnectHandler(platformView);
Picker.Loaded += OnLoaded;
}
protected override void DisconnectHandler(MauiPicker platformView)
{
Picker.Loaded -= OnLoaded;
base.DisconnectHandler(platformView);
}
private void OnLoaded(object sender, EventArgs e)
{
PlatformView.Background = SetPickerUi(Picker);
}
private LayerDrawable SetPickerUi(IconPicker element)
{
GradientDrawable border = new GradientDrawable();
border.SetShape(ShapeType.Rectangle);
border.SetPadding(10, 10, 10, 10);
border.SetCornerRadius(element.CornerRadius);
border.SetColor(ColorStateList.ValueOf(element.BackgroundColor?.ToAndroid() ?? Android.Graphics.Color.Transparent));
border.SetStroke(Convert.ToInt32(element.StrokeThickness), element.Stroke?.ToAndroid() ?? Android.Graphics.Color.Black);
LayerDrawable layerDrawable = new([border, GetDrawable(element.Source).Result]);
layerDrawable.SetLayerInset(0, 20, 0, 0, 0);
return layerDrawable;
}
private async Task<Drawable> GetDrawable(ImageSource source)
{
Drawable drawable;
if (source is FontImageSource font)
drawable = new FontDrawable(Context!, font.Glyph, font.Color.ToAndroid(), Convert.ToInt32(font.Size), "MaterialIconsRound-Regular.otf"); // TODO: add font family and recover filename from font family
else
{
var renderer = new StreamImagesourceHandler();
Bitmap bitmapImg = await renderer.LoadImageAsync(source, Context);
drawable = new BitmapDrawable(Context.Resources, Bitmap.CreateScaledBitmap(bitmapImg, 70, 70, true))
{
Gravity = GravityFlags.Right
};
}
return drawable;
}
}
在 iOS 平台中创建类似的部分处理程序。
将处理程序定义添加到您的 MauiProgram.cs
builder
.UseMauiApp<App>()
.ConfigureMauiHandlers(handlers =>
{
handlers.AddHandler<IconPicker, IconPickerHandler>();
})