起因

做了个播放器控制界面,发现进度滑动条Slider这个控件,如果全用默认不加修饰,那个滑块在iOS上会特别大。


要定制滑块,只要修改这个属性:

1
public Xamarin.Forms.ImageSource ThumbImageSource { get; set; }

最简单的做法当然是直接加载一张图片。
但是由于我想做一个方便复用的控件,最好是只包含代码就够,如果还带有资源,就不那么省心了。
所以想想不如自己画这个小圆块,反正xamarin里也支持动态绘图,且能将绘好的图直接做为图片使用。

于是祭起SkiaSharp这个大杀器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var slider = this.FindByName<Slider>("PrgSlider");
var size = (int)slider.Height;
// size = 20;
SKBitmap bitmap = new SKBitmap(size, size);
using (SKCanvas bitmapCanvas = new SKCanvas(bitmap))
{
bitmapCanvas.Clear();
SKPaint paint = new SKPaint
{
Style = SKPaintStyle.Fill,
Color = Color.Pink.ToSKColor(),
};
bitmapCanvas.DrawCircle(size / 2, size / 2, size / 2, paint);
}
this.FindByName<Slider>("PrgSlider").ThumbImageSource = (SKBitmapImageSource)bitmap;

但是没想到用了自绘的图像后,居然还是一大一小,iOS上仍然倍大。
这时我想到了之前改过的一个Xamarin.iOSbug,(提过pr):

动态加载的图片,没有根据屏幕缩放系数(retina scale)做相应处理!

很快找到它的代码一看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
[assembly: ExportImageSourceHandler(typeof(SkiaSharp.Views.Forms.SKImageImageSource), typeof(SkiaSharp.Views.Forms.SKImageSourceHandler))]
[assembly: ExportImageSourceHandler(typeof(SkiaSharp.Views.Forms.SKBitmapImageSource), typeof(SkiaSharp.Views.Forms.SKImageSourceHandler))]
[assembly: ExportImageSourceHandler(typeof(SkiaSharp.Views.Forms.SKPixmapImageSource), typeof(SkiaSharp.Views.Forms.SKImageSourceHandler))]
[assembly: ExportImageSourceHandler(typeof(SkiaSharp.Views.Forms.SKPictureImageSource), typeof(SkiaSharp.Views.Forms.SKImageSourceHandler))]

public sealed class SKImageSourceHandler : IImageSourceHandler
{
public Task<UIImage> LoadImageAsync(ImageSource imagesource, CancellationToken cancelationToken = default(CancellationToken), float scale = 1f)
{
UIImage image = null;

var imageImageSource = imagesource as SKImageImageSource;
if (imageImageSource != null)
{
image = imageImageSource.Image?.ToUIImage();
}

var bitmapImageSource = imagesource as SKBitmapImageSource;
if (bitmapImageSource != null)
{
image = bitmapImageSource.Bitmap?.ToUIImage();
}

var pixmapImageSource = imagesource as SKPixmapImageSource;
if (pixmapImageSource != null)
{
image = pixmapImageSource.Pixmap?.ToUIImage();
}

var pictureImageSource = imagesource as SKPictureImageSource;
if (pictureImageSource != null)
{
image = pictureImageSource.Picture?.ToUIImage(pictureImageSource.Dimensions);
}

return Task.FromResult(image);
}
}

果不其然!完全没有用到最后一个参数scale,而且从标注了默认值1f来看,他们可能都不认为这个值会变 😓
修正很简单,只要加上对scale的处理即可:

1
if (scale > 1) image = new UIImage(image.CGImage, scale, UIImageOrientation.Up);

但问题是如何在不自己编译SkiaSharp的情况下修正它呢?毕竟维护一个xamarin库太费事了 😂

好在问题是出在ImageSourceHandler上,xamarin上所有图像处理器都是通过属性自注册,集中处理,即:

1
2
3
4
5
6
7
8
namespace Xamarin.Forms
{
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
public sealed class ExportImageSourceHandlerAttribute : HandlerAttribute
{
public ExportImageSourceHandlerAttribute(Type handler, Type target);
}
}

这个ExportImageSourceHandler为每一种类型的ImageSource指定一个处理类,只要自己写个修正好的类,注册给对应的SkImageImageSource即可。

但是,和它源码中已经注册的处理类会不会冲突呢?
幸好,该属性父类中有一个Priority成员,就是用来解决绑定到同一目标的多个处理类之间优先级的 😆
所以,只要在注册自己的类时随便指定一个比默认值大的优先级,就顺利接管了SkImageImageSource的处理了:

[assembly: ExportImageSourceHandler(typeof(SkiaSharp.Views.Forms.SKBitmapImageSource), typeof(mb.SKImageSourceHandler), Priority = mb.SKImageSourceHandler.Priority)]

修正后: