Reputation: 2150
I have Xamarin Forms solution and in Xamarin Droid project I have custom renderer for class CustomCameraScanView that extends ContentView. This CustomCameraScanView should use Camera (old API) on Androids before 5.0 (Lollipop) and Camera2 (new API) from 5.0 and afterwards. How can I create two renderers: CustomCameraScanViewRenderer_Droid4 and CustomCameraScanViewRenderer_Droid5 that will be used on different versions of OS?
My CustomCameraScanViewRenderer_Droid4 looks like this:
[assembly: ExportRenderer(typeof(CustomCameraScanView), typeof(CustomCameraScanViewRenderer_Droid4))]
namespace Genea.Droid.Renderers
{
public class CustomCameraScanViewRenderer_Droid4 : ViewRenderer,
ISurfaceTextureListener,
ISurfaceHolderCallback,
Android.Hardware.Camera.IPreviewCallback,
Android.Hardware.Camera.IPictureCallback,
Android.Hardware.Camera.IShutterCallback
{
}
}
Upvotes: 2
Views: 1340
Reputation: 71
You can put the following code in your main activity right after Forms.Init() (if you are using forms)
if (Android.OS.Build.VERSION.SdkInt >= BuildVersionCodes.O)
{
Xamarin.Forms.Internals.Registrar.Registered.Register(typeof(Xamarin.Forms.Picker), typeof(PickerRendererEx));
}
else
{
Xamarin.Forms.Internals.Registrar.Registered.Register(typeof(Xamarin.Forms.Picker), typeof(PickerRendererExOld));
}
Upvotes: 0
Reputation: 13601
Unfortunately, registering a custom-renderer at runtime is not possible at this point in Xamarin-Forms. More details can be found on this forum link. But what you can do is resolve the controls at run-time using OnPlatform and build-version number.
I would of course recommend the conditional-compilation approach first as suggested by @luccas-clezar; but if that is not an option, then you can try resolving your control (and hence the renderer) at run-time using following steps.
Steps:
Create an simple contract to resolve current API value - in forms project
public interface IBuildVersion { int Number { get; } }
Implement it for Android platform
public class AndroidBuildVersion : IBuildVersion
{
public int Number { get { return ((int)Android.OS.Build.VERSION.SdkInt); } }
}
Register on DependencyService
[assembly: Xamarin.Forms.Dependency (typeof (AndroidBuildVersion))]
Subclass your CustomCameraScanView
into two derived types - in forms project
public class CustomCameraScanView_Droid4 : CustomCameraScanView { }
public class CustomCameraScanView_Droid5 : CustomCameraScanView { }
Setup your renderers in Android project
[assembly: ExportRenderer(typeof(CustomCameraScanView_Droid4), typeof(CustomCameraScanViewRenderer_Droid4))]
and,
[assembly: ExportRenderer(typeof(CustomCameraScanView_Droid5), typeof(CustomCameraScanViewRenderer_Droid5))]
Create custom-control to resolve at run-time - using OnPlatform
- in forms project
public class CameraScanContentView : Xamarin.Forms.ContentView
{
public CameraScanContentView()
{
this.Content = Device.OnPlatform(
iOS: new CustomCameraScanView(),
Android: DependencyService.Get<IBuildVersion>().Number < 21
? ((CustomCameraScanView)new CustomCameraScanView_Droid4())
: ((CustomCameraScanView)new CustomCameraScanView_Droid5()),
WinPhone: new CustomCameraScanView()
);
}
}
And, XAML usage will look like this
<local:CameraScanContentView />
I don't believe Xamarin-Forms supports 'Property Value' inheritance as WPF does - so it is kind of tricky to propagate the values from parent to child control.
One option is to cascade these values back to CustomCameraScanView
from CameraScanContentView
by defining bindable-properties (recommended):
public class CameraScanContentView : Xamarin.Forms.ContentView
{
public CameraScanContentView()
{
CustomCameraScanView scannerCtrl = null;
switch (Device.RuntimePlatform)
{
case Device.Android:
scannerCtrl = DependencyService.Get<IBuildVersion>().Number < 21
? ((CustomCameraScanView)new CustomCameraScanView_Droid4())
: ((CustomCameraScanView)new CustomCameraScanView_Droid5());
break;
default:
scannerCtrl = new CustomCameraScanView();
break;
}
scannerCtrl.SetBinding(CustomCameraScanView.IsScanningProperty, new Binding(nameof(IsScanning), source: this));
scannerCtrl.SetBinding(CustomCameraScanView.IsTakingImageProperty, new Binding(nameof(IsTakingImage), source: this));
scannerCtrl.SetBinding(CustomCameraScanView.IsFlashlightOnProperty, new Binding(nameof(IsFlashlightOn), source: this));
Content = scannerCtrl;
}
public static readonly BindableProperty IsScanningProperty = BindableProperty.Create("IsScanning", typeof(bool), typeof(CameraScanContentView), false);
public static readonly BindableProperty IsTakingImageProperty = BindableProperty.Create("IsTakingImage", typeof(bool), typeof(CameraScanContentView), false);
public static readonly BindableProperty IsFlashlightOnProperty = BindableProperty.Create("IsFlashlightOn", typeof(bool), typeof(CameraScanContentView), false);
public bool IsScanning
{
get { return (bool)GetValue(IsScanningProperty); }
set { SetValue(IsScanningProperty, value); }
}
public bool IsTakingImage
{
get { return (bool)GetValue(IsTakingImageProperty); }
set { SetValue(IsTakingImageProperty, value); }
}
public bool IsFlashlightOn
{
get { return (bool)GetValue(IsFlashlightOnProperty); }
set { SetValue(IsFlashlightOnProperty, value); }
}
}
XAML usage will then look like this
<local:CameraScanContentView IsScanning="{Binding IsScanning}" IsFlashlightOn="{Binding IsFlashlightOn}" IsTakingImage="{Binding IsTakingImage}" />
Or, you can manually define your binding-expressions as below (not recommended as it makes the assumption that the property-names on view-model will not change):
public class CameraScanContentView : Xamarin.Forms.ContentView
{
public CameraScanContentView()
{
CustomCameraScanView scannerCtrl = null;
switch (Device.RuntimePlatform)
{
case Device.Android:
scannerCtrl = DependencyService.Get<IBuildVersion>().Number < 21
? ((CustomCameraScanView)new CustomCameraScanView_Droid4())
: ((CustomCameraScanView)new CustomCameraScanView_Droid5());
break;
default:
scannerCtrl = new CustomCameraScanView();
break;
}
scannerCtrl.SetBinding(CustomCameraScanView.IsScanningProperty, new Binding("IsScanning", source: this.BindingContext));
scannerCtrl.SetBinding(CustomCameraScanView.IsTakingImageProperty, new Binding("IsTakingImage", source: this.BindingContext));
scannerCtrl.SetBinding(CustomCameraScanView.IsFlashlightOnProperty, new Binding("IsFlashlightOn", source: this.BindingContext));
Content = scannerCtrl;
}
}
Note: My first instinct was to use an implicit style targeting CustomCameraScanView
to bind the values - but somehow couldn't get it to work.
Upvotes: 3
Reputation: 1074
There's no need to have two renderers I think. Something like this should work:
if (Build.VERSION.SdkInt < BuildVersionCodes.Lollipop)
{
// Access old API
}
else
{
// Access new API
}
Another method of doing the same thing is by adding compiler directives. Like so:
#if __ANDROID_21__
// New API (>=21)
#else
// Old API (<21)
#endif
This will compile only the specific code of that Android API. I'm not sure if this works with PCL projects and the only thing that I could find that said something about it is this line from Dealing with Multiple Platforms guide: "Conditional compilation works best with Shared Asset Projects, where the same source file is being referenced in multiple projects that have different symbols defined.". Anyway, I think you could give it a try.
Hope it helps! :)
Upvotes: 1
Reputation: 2981
You will probably have to put some code like the code below inside the renderers so their methods don't get called unless a certain platform requirement is met:
if (Build.VERSION.SdkInt >= BuildVersionCodes.Lollipop)
{
// Lollipop specific stuff
}
There is no mechanism in place to actually point to a different renderer on different OS versions.
Upvotes: 0