Xamarin.Forms.Plugins
Xamarin.Forms.Plugins copied to clipboard
Change the source of the image
I need to change the SvgImage, so I tried
- binding in xaml:
SvgPath="{Binding CurrentWeather.IconSource, StringFormat='Umbrella.Images.{0:F0}.svg'}" - or to change it directly from C#:
icon.SvgPath = "Umbrella.Images." + currentWeather.IconSource + ".svg";
The SvgPath attribute does change, but the image doesn't rerender.
Is there any workaround?
I had the same problem. I ended up re-creating entirely the SvgImage in the codebehind when the source changes, and assigning it in a container (defined in XAML).
Well, I ended up doing the same, it's the easiest workaround, but the binding would be much cleaner.
Another workaround is to implement the missing logic in a custom renderer, as described here:
iOS Example
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == "SvgPath")
{
var svgStream = _formsControl.SvgAssembly.GetManifestResourceStream(_formsControl.SvgPath);
var reader = new SvgReader(new StreamReader(svgStream), new StylesParser(new ValuesParser()), new ValuesParser());
var graphics = reader.Graphic;
var canvas = new ApplePlatform().CreateImageCanvas(graphics.Size);
graphics.Draw(canvas);
var image = canvas.GetImage();
var uiImage = image.GetUIImage();
Control.Image = uiImage;
}
}
Yes, finally I dropped this library because it embeds an old version of NGraphics (the SVG parser and renderer) that couldn't parse some basic SVGs. Basically copy-pasted the renderers of this lib and fixed them...
A little update for those who are interested in a better SVG renderer, I improved the original Android renderer (coming from this library) and now I'm handling :
- SvgImage auto-sizing without
WidthRequestorHeightRequest(but only if you havewidth="x"&height="x"SVG attributes set). - Changing the SvgPath re-renders the bitmap (and re-calculates the element size)
- Handles Xamarin Form's
Image.Aspectto control the aspect ratio (choose between Fill, AspectFill, AspectFit and FitCenter which is the default).
Do not forget to add NGraphics as a NuGet dependency.
Portable Element (in the portable project):
using System.Reflection;
using Xamarin.Forms;
namespace MYAPP.Controls
{
public class SvgImage : Image
{
public static readonly Assembly SvgAssembly = typeof(App).GetTypeInfo().Assembly;
public static readonly BindableProperty SvgPathProperty = BindableProperty.Create(nameof(SvgPath), typeof(string), typeof(SvgImage), default(string));
public string SvgPath
{
get { return (string)GetValue(SvgPathProperty); }
set { SetValue(SvgPathProperty, value); }
}
}
}
Android renderer (in the Android project):
using Android.Graphics;
using Android.Widget;
using MYAPP.Controls;
using MYAPP.Droid.CustomRenderers;
using NGraphics;
using System;
using System.ComponentModel;
using System.IO;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(SvgImage), typeof(SvgImageRenderer))]
namespace MYAPP.Droid.CustomRenderers
{
public class SvgImageRenderer : ViewRenderer<SvgImage, ImageView>
{
protected override void OnElementChanged(ElementChangedEventArgs<SvgImage> e)
{
base.OnElementChanged(e);
if (e.OldElement == null) {
SetNativeControl(new ImageView(Context));
}
UpdateBitmap();
UpdateAspect();
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
switch (e.PropertyName) {
case nameof(SvgImage.SvgPath):
UpdateBitmap();
break;
case nameof(SvgImage.Aspect):
UpdateAspect();
break;
}
}
private static ImageView.ScaleType ToNativeScaleType(Aspect aspect)
{
switch (aspect) {
case Aspect.Fill:
return ImageView.ScaleType.FitXy;
case Aspect.AspectFill:
return ImageView.ScaleType.CenterCrop;
case Aspect.AspectFit:
default:
return ImageView.ScaleType.FitCenter;
}
}
private void UpdateAspect()
{
Control.SetScaleType(ToNativeScaleType(Element.Aspect));
}
private async void UpdateBitmap()
{
var bitmap = await Task.Run(() => SvgToBitmap(Element.SvgPath, Element.WidthRequest, Element.HeightRequest));
Control.SetImageBitmap(bitmap);
bitmap.Dispose();
((IVisualElementController)Element).NativeSizeChanged();
}
private int DpToPx(double dp)
{
return (int)(dp * Resources.DisplayMetrics.Density);
}
private Bitmap SvgToBitmap(string svgPath, double widthRequest, double heightRequest)
{
var svgStream = SvgImage.SvgAssembly.GetManifestResourceStream(svgPath);
if (svgStream == null) {
throw new Exception($"Error retrieving {svgPath} ; make sure the Build Action is set to Embedded Resource");
}
var reader = new SvgReader(new StreamReader(svgStream));
var graphics = reader.Graphic;
var width = widthRequest > -1 ? DpToPx(widthRequest) : graphics.Size.Width;
var height = heightRequest > -1 ? DpToPx(heightRequest) : graphics.Size.Height;
var scale = height >= width ? (height / graphics.Size.Height) : (width / graphics.Size.Width);
var canvas = new AndroidPlatform().CreateImageCanvas(graphics.Size, scale);
graphics.Draw(canvas);
return ((BitmapImage)canvas.GetImage()).Bitmap;
}
}
}
Will probably post an updated iOS renderer later (still using the old one).