I need to have Image control in Xamarin.Forms with rounded corners. But I did not find any property that could make it. How to have circular Image ?
I use the FFImageLoading libraries CachedImage control with a circle transformation for circle images:
<ffimageloading:CachedImage
DownsampleToViewSize="true"
Aspect="AspectFill"
Source = "{Binding Image}"
LoadingPlaceholder = "{Binding DefaultImage}"
ErrorPlaceholder = "{Binding DefaultImage}">
<ffimageloading:CachedImage.Transformations>
<fftransformations:CircleTransformation />
</ffimageloading:CachedImage.Transformations>
</ffimageloading:CachedImage>
You can use Image Circle Control Plugin
<controls:CircleImage Source="{Binding Image}" Aspect="AspectFill">
<controls:CircleImage.WidthRequest>
<OnPlatform x:TypeArguments="x:Double"
iOS="55"
Android="55"
WinPhone="75"/>
</controls:CircleImage.WidthRequest>
<controls:CircleImage.HeightRequest>
<OnPlatform x:TypeArguments="x:Double"
iOS="55"
Android="55"
WinPhone="75"/>
</controls:CircleImage.HeightRequest>
</controls:CircleImage>
Read more at Project github readme
You can also use CircleImage from Xamarin-Forms-Labs project.
If you need a solution using Custom Renderers (to tweak the control the way you want), here is my implementation for that.
public class ImageCircle:Image
{
public ImageCircle ()
{
}
}
[assembly: ExportRenderer(typeof(ImageCircle), typeof(ImageCircleRenderer))]
namespace myNamespace.Droid
{
public class ImageCircleRenderer:ImageRenderer
{
public ImageCircleRenderer ()
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
{
base.OnElementChanged(e);
if (e.OldElement == null)
{
if ((int)Android.OS.Build.VERSION.SdkInt < 20)
SetLayerType(Android.Views.LayerType.Software, null);
}
}
protected override bool DrawChild(Canvas canvas, global::Android.Views.View child, long drawingTime)
{
try
{
var radius = Math.Min(Width, Height) / 2;
var strokeWidth = 10;
radius -= strokeWidth / 2;
//Create path to clip
var path = new Path();
path.AddCircle(Width / 2, Height / 2, radius, Path.Direction.Ccw);
canvas.Save();
canvas.ClipPath(path);
var result = base.DrawChild(canvas, child, drawingTime);
canvas.Restore();
// Create path for circle border
path = new Path();
path.AddCircle(Width / 2, Height / 2, radius, Path.Direction.Ccw);
var paint = new Paint();
paint.AntiAlias = true;
paint.StrokeWidth = 5;
paint.SetStyle(Paint.Style.Stroke);
paint.Color = global::Android.Graphics.Color.White;
canvas.DrawPath(path, paint);
//Properly dispose
paint.Dispose();
path.Dispose();
return result;
}
catch (Exception ex)
{
Console.WriteLine("Unable to create circle image: " + ex);
}
return base.DrawChild(canvas, child, drawingTime);
}
}
}
[assembly: ExportRenderer(typeof(ImageCircle), typeof(ImageCircleRenderer))]
namespace LifesTopTen.iOS
{
public class ImageCircleRenderer:ImageRenderer
{
public ImageCircleRenderer ()
{
}
private void CreateCircle()
{
try
{
double min = Math.Min(Element.Width, Element.Height);
Control.Layer.CornerRadius = (float)(min / 2.0);
Control.Layer.MasksToBounds = false;
Control.Layer.BorderColor = Color.White.ToCGColor();
Control.Layer.BorderWidth = 3;
Control.ClipsToBounds = true;
}
catch(Exception ex)
{
Console.WriteLine ("Unable to create circle image: " + ex);
}
}
protected override void OnElementChanged (ElementChangedEventArgs<Image> e)
{
base.OnElementChanged (e);
if (e.OldElement != null || Element == null)
return;
CreateCircle();
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == VisualElement.HeightProperty.PropertyName ||
e.PropertyName == VisualElement.WidthProperty.PropertyName)
{
CreateCircle();
}
}
}
}
Related
How can I implement a video recorder inside an application using xamarin forms?
You can use Xamarin.Plugin.Media. If it is not flexible enough for you, then you need to implement the video recorder using the native APIs. In which case you probably shouldn't be doing Xamarin.Forms at all, but if you insist that is the way.
Do you want to achieve the result like following GIF(Over 2M, SO cannot allow upload it )?
https://imgur.com/a/kpMl2ed
I achieve it in android by custom rendere. Here is code.
First of all, we should MediaRecorder to record the video.
public void startRecord(SurfaceView surfaceView)
{
Device.BeginInvokeOnMainThread(() => {
string path = Android.OS.Environment.ExternalStorageDirectory.AbsolutePath + "/test.mp4";
recorder = new MediaRecorder();
//If you want to rotate the video screen, you can use following code
//Camera camera = Camera.Open();
//Camera.Parameters parameters = camera.GetParameters();
//parameters.SetPreviewSize(640, 480);
//parameters.SetPictureSize(640, 480);
//camera.SetParameters(parameters);
//camera.SetDisplayOrientation(90);
//camera.Unlock();
//recorder.SetCamera(camera);
recorder.SetVideoSource(VideoSource.Camera);
recorder.SetAudioSource(AudioSource.Mic);
recorder.SetOutputFormat(OutputFormat.Default);
recorder.SetVideoEncoder(VideoEncoder.Default);
recorder.SetAudioEncoder(AudioEncoder.Default);
recorder.SetOutputFile(path);
recorder.SetPreviewDisplay(surfaceView.Holder.Surface);
recorder.Prepare();
recorder.Start();
});
}
If you want to achieve the record the video inside the application, you should use custom renderer to view. Use SurfaceView to see the camera view.First of all. please create a CameraPreview
public sealed class CameraPreview : ViewGroup, ISurfaceHolderCallback
{
public SurfaceView surfaceView;
ISurfaceHolder holder;
Camera.Size previewSize;
IList<Camera.Size> supportedPreviewSizes;
Camera camera;
IWindowManager windowManager;
MediaRecorder recorder;
public bool IsPreviewing { get; set; }
public Camera Preview {
get { return camera; }
set {
camera = value;
if (camera != null) {
supportedPreviewSizes = Preview.GetParameters().SupportedPreviewSizes;
RequestLayout();
}
}
}
public CameraPreview (Context context)
: base (context)
{
surfaceView = new SurfaceView (context);
AddView (surfaceView);
windowManager = Context.GetSystemService (Context.WindowService).JavaCast<IWindowManager>();
IsPreviewing = false;
holder = surfaceView.Holder;
holder.AddCallback (this);
MessagingCenter.Subscribe<string>("111", "Hi", (expense) =>
{
startRecord(surfaceView);
});
MessagingCenter.Subscribe<string>("1112", "Hi2", (expense) =>
{
stopRecord(surfaceView);
});
}
private void stopRecord(SurfaceView surfaceView)
{
recorder.Stop();
recorder.Release();
}
public void startRecord(SurfaceView surfaceView)
{
Device.BeginInvokeOnMainThread(() => {
string path = Android.OS.Environment.ExternalStorageDirectory.AbsolutePath + "/test.mp4";
recorder = new MediaRecorder();
//If you want to rotate the video screen, you can use following code
//Camera camera = Camera.Open();
//Camera.Parameters parameters = camera.GetParameters();
//parameters.SetPreviewSize(640, 480);
//parameters.SetPictureSize(640, 480);
//camera.SetParameters(parameters);
//camera.SetDisplayOrientation(90);
//camera.Unlock();
//recorder.SetCamera(camera);
recorder.SetVideoSource(VideoSource.Camera);
recorder.SetAudioSource(AudioSource.Mic);
recorder.SetOutputFormat(OutputFormat.Default);
recorder.SetVideoEncoder(VideoEncoder.Default);
recorder.SetAudioEncoder(AudioEncoder.Default);
recorder.SetOutputFile(path);
recorder.SetPreviewDisplay(surfaceView.Holder.Surface);
recorder.Prepare();
recorder.Start();
});
}
protected override void OnMeasure (int widthMeasureSpec, int heightMeasureSpec)
{
int width = ResolveSize (SuggestedMinimumWidth, widthMeasureSpec);
int height = ResolveSize (SuggestedMinimumHeight, heightMeasureSpec);
SetMeasuredDimension (width, height);
if (supportedPreviewSizes != null) {
previewSize = GetOptimalPreviewSize (supportedPreviewSizes, width, height);
}
}
protected override void OnLayout (bool changed, int l, int t, int r, int b)
{
var msw = MeasureSpec.MakeMeasureSpec (r - l, MeasureSpecMode.Exactly);
var msh = MeasureSpec.MakeMeasureSpec (b - t, MeasureSpecMode.Exactly);
surfaceView.Measure (msw, msh);
surfaceView.Layout (0, 0, r - l, b - t);
}
public void SurfaceCreated (ISurfaceHolder holder)
{
try {
if (Preview != null) {
Preview.SetPreviewDisplay (holder);
}
} catch (Exception ex) {
System.Diagnostics.Debug.WriteLine (#" ERROR: ", ex.Message);
}
}
public void SurfaceDestroyed (ISurfaceHolder holder)
{
if (Preview != null) {
Preview.StopPreview ();
}
}
public void SurfaceChanged (ISurfaceHolder holder, Android.Graphics.Format format, int width, int height)
{
var parameters = Preview.GetParameters ();
parameters.SetPreviewSize (previewSize.Width, previewSize.Height);
RequestLayout ();
//If you want to rotate the video screen, you can use following code
//switch (windowManager.DefaultDisplay.Rotation) {
//case SurfaceOrientation.Rotation0:
// camera.SetDisplayOrientation (90);
// break;
//case SurfaceOrientation.Rotation90:
// camera.SetDisplayOrientation (0);
// break;
//case SurfaceOrientation.Rotation270:
// camera.SetDisplayOrientation (180);
// break;
//}
Preview.SetParameters (parameters);
Preview.StartPreview ();
IsPreviewing = true;
}
Camera.Size GetOptimalPreviewSize (IList<Camera.Size> sizes, int w, int h)
{
const double AspectTolerance = 0.1;
double targetRatio = (double)w / h;
if (sizes == null) {
return null;
}
Camera.Size optimalSize = null;
double minDiff = double.MaxValue;
int targetHeight = h;
foreach (Camera.Size size in sizes) {
double ratio = (double)size.Width / size.Height;
if (Math.Abs (ratio - targetRatio) > AspectTolerance)
continue;
if (Math.Abs (size.Height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.Abs (size.Height - targetHeight);
}
}
if (optimalSize == null) {
minDiff = double.MaxValue;
foreach (Camera.Size size in sizes) {
if (Math.Abs (size.Height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.Abs (size.Height - targetHeight);
}
}
}
return optimalSize;
}
}
Then, Here is custom renderer.
[assembly: ExportRenderer(typeof(CustomRenderer.CameraPreview), typeof(CameraPreviewRenderer))]
namespace CustomRenderer.Droid
{
public class CameraPreviewRenderer : ViewRenderer
{
CameraPreview cameraPreview;
public CameraPreviewRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<CustomRenderer.CameraPreview> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
// Unsubscribe
cameraPreview.Click -= OnCameraPreviewClicked;
}
if (e.NewElement != null)
{
if (Control == null)
{
cameraPreview = new CameraPreview(Context);
SetNativeControl(cameraPreview);
}
Control.Preview = Camera.Open((int)e.NewElement.Camera);
// Subscribe
cameraPreview.Click += OnCameraPreviewClicked;
}
}
void OnCameraPreviewClicked(object sender, EventArgs e)
{
if (cameraPreview.IsPreviewing)
{
cameraPreview.Preview.StopPreview();
cameraPreview.IsPreviewing = false;
}
else
{
cameraPreview.Preview.StartPreview();
cameraPreview.IsPreviewing = true;
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
Control.Preview.Release();
}
base.Dispose(disposing);
}
}
}
If you want to know how to achieve Camera Preview in the IOS or UWP, you can refer to this link.
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/custom-renderer/view
Here is code in github.
https://github.com/851265601/FormsRecordVideoInside
Above way to achieve it, it a bit complexed. I suggest you to use the Xam.Plugin.Media to achieve it. Here is running GIF. it cannot see the video preview in the application, but you do not need to use custom renderer to achieve it just serveral lines code.
private async void PlayStopButtonText_Clicked(object sender, EventArgs e)
{
// throw new NotImplementedException();
var file = await CrossMedia.Current.TakeVideoAsync(new StoreVideoOptions
{
SaveToAlbum = true,Directory = "Sample"
});
//Get the public album path
if (file == null)
return;
await DisplayAlert("File Location", file.AlbumPath, "OK");
}
In the android, add the following permission.
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
Here is running GIF(Over 2M, SO cannot allow upload it ).
https://imgur.com/a/9WE4szZ
I'm trying to use a Gradient Renderer for which I have written a class in PCL and written a renderer for both Android and iOS. Android renderer is working but iOS renderer is not showing the gradient colour at all.
I'm using this Gradient code from XLabs. I'm not sure what's broken. A hint in the right direction would help.
PCL Code:
using Xamarin.Forms;
namespace gradient
{
public enum GradientOrientation
{
Vertical,
Horizontal
}
public class GradientContentView : ContentView
{
public GradientOrientation Orientation
{
get { return (GradientOrientation)GetValue(OrientationProperty); }
set { SetValue(OrientationProperty, value); }
}
public static readonly BindableProperty OrientationProperty =
BindableProperty.Create<GradientContentView, GradientOrientation>(x => x.Orientation, GradientOrientation.Vertical, BindingMode.OneWay);
public Color StartColor
{
get { return (Color)GetValue(StartColorProperty); }
set { SetValue(StartColorProperty, value); }
}
public static readonly BindableProperty StartColorProperty =
BindableProperty.Create<GradientContentView, Color>(x => x.StartColor, Color.White, BindingMode.OneWay);
public Color EndColor
{
get { return (Color)GetValue(EndColorProperty); }
set { SetValue(EndColorProperty, value); }
}
public static readonly BindableProperty EndColorProperty =
BindableProperty.Create<GradientContentView, Color>(x => x.EndColor, Color.Black, BindingMode.OneWay);
}
}
iOS Renderer code:
using CoreAnimation;
using CoreGraphics;
using gradient;
using gradient.iOS;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(GradientContentView), typeof(GradientContentViewRenderer))]
namespace gradient.iOS
{
class GradientContentViewRenderer : VisualElementRenderer<ContentView>
{
private GradientContentView GradientContentView
{
get { return (GradientContentView)Element; }
}
protected CAGradientLayer GradientLayer { get; set; }
protected override void OnElementChanged(ElementChangedEventArgs<ContentView> e)
{
base.OnElementChanged(e);
if (GradientContentView != null &&
NativeView != null)
{
// Create a gradient layer and add it to the
// underlying UIView
GradientLayer = new CAGradientLayer();
GradientLayer.Frame = NativeView.Bounds;
GradientLayer.Colors = new CGColor[]
{
GradientContentView.StartColor.ToCGColor(),
GradientContentView.EndColor.ToCGColor()
};
SetOrientation();
NativeView.Layer.InsertSublayer(GradientLayer, 0);
}
}
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (GradientLayer != null && GradientContentView != null)
{
// Turn off Animations
CATransaction.Begin();
CATransaction.DisableActions = true;
if (e.PropertyName == GradientContentView.StartColorProperty.PropertyName)
GradientLayer.Colors[0] = GradientContentView.StartColor.ToCGColor();
if (e.PropertyName == GradientContentView.EndColorProperty.PropertyName)
GradientLayer.Colors[1] = GradientContentView.EndColor.ToCGColor();
if (e.PropertyName == VisualElement.WidthProperty.PropertyName ||
e.PropertyName == VisualElement.HeightProperty.PropertyName)
GradientLayer.Frame = NativeView.Bounds;
if (e.PropertyName == GradientContentView.OrientationProperty.PropertyName)
SetOrientation();
CATransaction.Commit();
}
}
void SetOrientation()
{
if (GradientContentView.Orientation == GradientOrientation.Horizontal)
{
GradientLayer.StartPoint = new CGPoint(0, 0.5);
GradientLayer.EndPoint = new CGPoint(1, 0.5);
}
else
{
GradientLayer.StartPoint = new CGPoint(0.5, 0);
GradientLayer.EndPoint = new CGPoint(0.5, 1);
}
}
}
}
This is my code for rendering a gradient background, i am not using orientation, but maybe it helps.
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if (e.OldElement == null) // perform initial setup
{
ModernOrderCalendar page = e.NewElement as ModernOrderCalendar;
var gradientLayer = new CAGradientLayer();
gradientLayer.Name = "gradient";
CGRect rect = View.Bounds;
gradientLayer.Frame = rect;
gradientLayer.Colors = new CGColor[] { page.StartColor.ToCGColor(), page.EndColor.ToCGColor() };
View.Layer.InsertSublayer(gradientLayer, 0);
}
}
public override void ViewWillLayoutSubviews()
{
base.ViewWillLayoutSubviews();
if (Xamarin.Forms.Device.Idiom == TargetIdiom.Tablet)
{
var gradientLayer = View.Layer.Sublayers.FirstOrDefault(l => l.Name == "gradient");
gradientLayer.Frame = View.Bounds;
View.Layer.Sublayers[0] = gradientLayer;
CGRect frame = View.Bounds;
View.Bounds = frame;
}
}
The main difference I see is that you don't seem to be overriding the ViewWillLayoutSubviews method. I had the same issue, which caused the gradient layer to be created with no height and width during the creation of the window (at that point the View has not layouted, yet).
Therefore I adapt the gradientlayer width and height when layouting the subviews, because at that point width and height of the view are definitely known.
You must update the layer's size in VisualElementRenderer.LayoutSubviews:
public override void LayoutSubviews()
{
base.LayoutSubviews();
CATransaction.Begin();
CATransaction.DisableActions = true;
GradientLayer.Frame = NativeView.Bounds;
CATransaction.Commit();
}
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 4 years ago.
Improve this question
How do you create circular clipping mask for images which works in Xamarin Cross-Platform, and how do you implement it within a ListView? I don't want to use plugins. I found some examples but they all target Android Apps only and most of them are written in Java, not C#.
Needs to Write CustomRederers for Image
PCL:
ImageCircle.cs
public class ImageCircle : Image
{
}
Xamarin.Android:
ImageCircleRenderer.cs
Above NameSpace
[assembly: ExportRenderer(typeof(ImageCircle), typeof(ImageCircleRenderer))]
public class ImageCircleRenderer : ImageRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
{
base.OnElementChanged(e);
if (e.OldElement == null)
{
if ((int)Android.OS.Build.VERSION.SdkInt < 18)
SetLayerType(LayerType.Software, null);
}
}
protected override bool DrawChild(Canvas canvas, global::Android.Views.View child, long drawingTime)
{
try
{
var radius = Math.Min(Width, Height) / 2;
var strokeWidth = 10;
radius -= strokeWidth / 2;
Path path = new Path();
path.AddCircle(Width / 2, Height / 2, radius, Path.Direction.Ccw);
canvas.Save();
canvas.ClipPath(path);
var result = base.DrawChild(canvas, child, drawingTime);
canvas.Restore();
path = new Path();
path.AddCircle(Width / 2, Height / 2, radius, Path.Direction.Ccw);
var paint = new Paint();
paint.AntiAlias = true;
paint.StrokeWidth = 5;
paint.SetStyle(Paint.Style.Stroke);
paint.Color = global::Android.Graphics.Color.White;
canvas.DrawPath(path, paint);
paint.Dispose();
path.Dispose();
return result;
}
catch (Exception ex)
{
var msg = ex.Message;
}
return base.DrawChild(canvas, child, drawingTime);
}
}
Xamarin.iOS
Above NameSpace
[assembly: ExportRenderer(typeof(ImageCircle), typeof(ImageCircleRender))]
ImageCircleRenderer.cs
public class ImageCircleRender : ImageRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
{
base.OnElementChanged(e);
if (e.OldElement != null || Element == null)
return;
CreateCircle();
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == VisualElement.HeightProperty.PropertyName ||
e.PropertyName == VisualElement.WidthProperty.PropertyName)
{
CreateCircle();
}
}
private void CreateCircle()
{
try
{
double min = Math.Min(Element.Width, Element.Height);
Control.Layer.CornerRadius = (float)(min / 2.0);
Control.Layer.MasksToBounds = false;
Control.Layer.BorderColor = Color.White.ToCGColor();
Control.Layer.BorderWidth = 1;
Control.ClipsToBounds = true;
}
catch (Exception ex)
{
Debug.WriteLine("Unable to create circle image: " + ex);
}
}
}
Use https://www.nuget.org/packages/Xam.Plugins.Forms.ImageCircle
In your iOS, Android, and Windows projects call:
Xamarin.Forms.Init();//platform specific init
ImageCircleRenderer.Init();
I am trying to figure out how I can ask the user for a signature area so they can sign with their finger then save that signature to a file and I am reaching a dead end. I have looked at Kimserey's blog, the CrossGraphics library and SkiaSharp but these all seem to be geared around making the image through code vs a user drawing with their finger. The solution needs to be used in a pcl project and will be deployed to Android, iOS, and UWP. Does anyone have an idea?
As #Jason commented, use SignaturePad.
The NuGet can be found here: https://www.nuget.org/packages/Xamarin.Controls.SignaturePad.Forms
It works on basically any platform.
Source: https://github.com/xamarin/SignaturePad
An example page might be this:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:signature="clr-namespace:SignaturePad.Forms;assembly=SignaturePad.Forms"
x:Class="Samples.Views.SignatureXamlView">
<Grid>
<signature:SignaturePadView />
</Grid>
</ContentPage>
SignaturePadDemoPage.xaml
<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
BackgroundColor="Gray"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-
namespace:SignaturePadDemo;assembly:SignaturePadDemo"
x:Class="SignaturePadDemo.SignaturePadDemoPage">
<Grid HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" Padding="30">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label Text="SignaturePad Demo" Grid.Row="0" VerticalOptions="Start" HorizontalOptions="Center" TextColor="White" FontSize="25"/>
<local:ImageWithTouch x:Name="imgSiganturePad" Grid.Row="1" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand" BackgroundColor="White" CurrentLineColor="Fuchsia"/>
<Grid Grid.Row="2" VerticalOptions="EndAndExpand">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Button x:Name="btnSave" Text="Save the Image" Grid.Row="0" HorizontalOptions="FillAndExpand" TextColor="Blue" BackgroundColor="White" Clicked="btnSaveImage_Click"/>
<Button x:Name="btnSee" Text="See the Image" Grid.Row="1" HorizontalOptions="FillAndExpand" TextColor="Blue" BackgroundColor="White" Clicked="btnSeeImage_Click"/>
<Button x:Name="btnClear" Text="Clear" Grid.Row="2" HorizontalOptions="FillAndExpand" TextColor="Blue" BackgroundColor="White" Clicked="btnClear_Click"/>
</Grid>
</Grid>
SignaturePadDemoPage.xaml.cs
using System;
using Xamarin.Forms;
namespace SignaturePadDemo
{
public partial class SignaturePadDemoPage : ContentPage
{
public SignaturePadDemoPage()
{
InitializeComponent();
}
private void btnSaveImage_Click(object sender, EventArgs e)
{
var imgPath = DependencyService.Get<ISign>().Sign();
imgSiganturePad.SavedImagePath = imgPath;
btnSee.IsEnabled = true;
DisplayAlert("SignaturePadDemo", "Your siganture saved succesfully", "Ok");
}
private void btnSeeImage_Click(object sender, EventArgs e)
{
Navigation.PushModalAsync(new
SignatureDetailPage(imgSiganturePad.SavedImagePath));
imgSiganturePad.ClearPath = !imgSiganturePad.ClearPath;
}
private void btnClear_Click(object sender, EventArgs e)
{
imgSiganturePad.ClearPath = !imgSiganturePad.ClearPath;
DisplayAlert("SignaturePadDemo", "Siganture was clear", "Ok");
}
}
}
CustomRenderer for Image
PCL:
ImageWithTouch.cs
using System;
using Xamarin.Forms;
namespace SignaturePadDemo
{
public class ImageWithTouch : Image
{
public static readonly BindableProperty CurrentLineColorProperty =
BindableProperty.Create((ImageWithTouch w) => w.CurrentLineColor, Color.Default);
public static readonly BindableProperty CurrentLineWidthProperty =
BindableProperty.Create((ImageWithTouch w) => w.CurrentLineWidth, 1);
public static readonly BindableProperty CurrentImageProperty =
BindableProperty.Create((ImageWithTouch w) => w.CurrentImagePath, "");
public static readonly BindableProperty ClearImagePathProperty =
BindableProperty.Create((ImageWithTouch w) => w.ClearPath, false);
public static readonly BindableProperty SavedImagePathProperty =
BindableProperty.Create((ImageWithTouch w) => w.SavedImagePath, "");
public Color CurrentLineColor
{
get
{
return (Color)GetValue(CurrentLineColorProperty);
}
set
{
SetValue(CurrentLineColorProperty, value);
}
}
public int CurrentLineWidth
{
get
{
return (int)GetValue(CurrentLineWidthProperty);
}
set
{
SetValue(CurrentLineWidthProperty, value);
}
}
public string CurrentImagePath
{
get
{
return (string)GetValue(CurrentImageProperty);
}
set
{
SetValue(CurrentImageProperty, value);
}
}
public bool ClearPath
{
get
{
return (bool)GetValue(ClearImagePathProperty);
}
set
{
SetValue(ClearImagePathProperty, value);
}
}
public string SavedImagePath
{
get
{
return (string)GetValue(SavedImagePathProperty);
}
set
{
SetValue(SavedImagePathProperty, value);
}
}
}
}
Xamarin.Android:
ImageWithTouchRenderer.cs
using System;
using System.ComponentModel;
using Xamarin.Forms.Platform.Android;
using Xamarin.Forms;
using Android.Graphics;
using Java.IO;
using System.IO;
using SignaturePadDemo;
using SignaturePadDemo.Droid;
[assembly: ExportRenderer(typeof(ImageWithTouch),
typeof(ImageWithTouchRenderer))]
namespace SignaturePadDemo.Droid
{
public class ImageWithTouchRenderer : ViewRenderer<ImageWithTouch, DrawView>
{
protected override void OnElementChanged(ElementChangedEventArgs<ImageWithTouch> e)
{
base.OnElementChanged(e);
if (e.OldElement == null)
{
SetNativeControl(new DrawView(Context));
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == ImageWithTouch.ClearImagePathProperty.PropertyName)
{
Control.Clear();
}
else if (e.PropertyName == ImageWithTouch.SavedImagePathProperty.PropertyName)
{
Bitmap curDrawingImage = Control.GetImageFromView();
Byte[] imgBytes = ImageHelper.BitmapToBytes(curDrawingImage);
Java.IO.File f = new Java.IO.File(Element.SavedImagePath);
f.CreateNewFile();
FileOutputStream fo = new FileOutputStream(f);
fo.Write(imgBytes);
fo.Close();
}
else
{
UpdateControl(true);
}
}
private void UpdateControl(bool bDisplayFlag)
{
Control.CurrentLineColor = Element.CurrentLineColor.ToAndroid();
Control.PenWidth = Element.CurrentLineWidth * 3;
Control.ImageFilePath = Element.CurrentImagePath;
if (bDisplayFlag)
{
Control.LoadImageFromFile();
Control.Invalidate();
}
}
}
}
Xamarin.iOS:
ImageWithTouchRenderer.cs
using System.Drawing;
using Xamarin.Forms.Platform.iOS;
using Xamarin.Forms;
using System.ComponentModel;
using UIKit;
using Foundation;
using SignaturePadDemo;
using SignaturePadDemo.iOS;
[assembly: ExportRenderer(typeof(ImageWithTouch), typeof(ImageWithTouchRenderer))]
namespace SignaturePadDemo.iOS
{
public class ImageWithTouchRenderer : ViewRenderer<ImageWithTouch, DrawView>
{
protected override void OnElementChanged(ElementChangedEventArgs<ImageWithTouch> e)
{
base.OnElementChanged(e);
SetNativeControl(new DrawView(RectangleF.Empty));
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == ImageWithTouch.ClearImagePathProperty.PropertyName)
{
Control.Clear();
}
else if (e.PropertyName == ImageWithTouch.SavedImagePathProperty.PropertyName)
{
UIImage curDrawingImage = Control.GetImageFromView();
NSData data = curDrawingImage.AsJPEG();
NSError error = new NSError();
bool bSuccess = data.Save(Element.SavedImagePath, true, out error);
}
else
{
UpdateControl(e.PropertyName == ImageWithTouch.CurrentLineColorProperty.PropertyName ||
e.PropertyName == ImageWithTouch.CurrentImageProperty.PropertyName ||
e.PropertyName == ImageWithTouch.CurrentLineWidthProperty.PropertyName);
}
}
private void UpdateControl(bool bDisplayFlag)
{
Control.CurrentLineColor = Element.CurrentLineColor.ToUIColor();
Control.PenWidth = Element.CurrentLineWidth;
if (Control.ImageFilePath != Element.CurrentImagePath)
{
Control.ImageFilePath = Element.CurrentImagePath;
Control.LoadImageFromFile();
}
if (bDisplayFlag)
{
Control.SetNeedsDisplay();
}
}
}
}
DependencyService to get path to store Image in Local storage
ISign.cs
using System;
namespace SignaturePadDemo
{
public interface ISign
{
string Sign();
}
}
Xamarin.Android:
ISignService.cs
using System;
using SignaturePadDemo.Droid;
using Xamarin.Forms;
[assembly: Dependency(typeof(ISignService))]
namespace SignaturePadDemo.Droid
{
public class ISignService : ISign
{
public string Sign()
{
string savedFileName = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + "/temp_" + DateTime.Now.ToString("yyyy_mm_dd_hh_mm_ss") + ".jpg";
return savedFileName;
}
}
}
DrawView.cs
using Android.Views;
using Android.Graphics;
using Android.Content;
using System;
namespace SignaturePadDemo.Droid
{
public class DrawView : View
{
public DrawView(Context context)
: base(context)
{
Start();
}
public Color CurrentLineColor { get; set; }
public String ImageFilePath { get; set; }
public float PenWidth { get; set; }
private Path DrawPath;
private Paint DrawPaint;
private Paint CanvasPaint;
private Canvas DrawCanvas;
private Bitmap CanvasBitmap;
private int w, h;
private Bitmap _image;
private void Start()
{
CurrentLineColor = Color.Black;
PenWidth = 5.0f;
DrawPath = new Path();
DrawPaint = new Paint
{
Color = CurrentLineColor,
AntiAlias = true,
StrokeWidth = PenWidth
};
DrawPaint.SetStyle(Paint.Style.Stroke);
DrawPaint.StrokeJoin = Paint.Join.Round;
DrawPaint.StrokeCap = Paint.Cap.Round;
CanvasPaint = new Paint
{
Dither = true
};
}
public void Clear()
{
try
{
DrawPath = new Path();
CanvasBitmap = Bitmap.CreateBitmap(w, h, Bitmap.Config.Argb8888);
DrawCanvas = new Canvas(CanvasBitmap);
DrawCanvas.DrawColor(Color.White, PorterDuff.Mode.Multiply);
CanvasBitmap.EraseColor(Color.Transparent);
DrawPaint = new Paint
{
Color = CurrentLineColor,
AntiAlias = true,
StrokeWidth = PenWidth
};
DrawPaint.SetStyle(Paint.Style.Stroke);
DrawPaint.StrokeJoin = Paint.Join.Round;
DrawPaint.StrokeCap = Paint.Cap.Round;
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
Invalidate();
}
protected override void OnSizeChanged(int w, int h, int oldw, int oldh)
{
base.OnSizeChanged(w, h, oldw, oldh);
if (w > 0 && h > 0)
{
try
{
CanvasBitmap = Bitmap.CreateBitmap(w, h, Bitmap.Config.Argb8888);
DrawCanvas = new Canvas(CanvasBitmap);
this.w = w;
this.h = h;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
protected override void OnDraw(Canvas canvas)
{
base.OnDraw(canvas);
DrawPaint.Color = CurrentLineColor;
DrawPaint.StrokeWidth = PenWidth;
canvas.DrawBitmap(CanvasBitmap, 0, 0, CanvasPaint);
canvas.DrawPath(DrawPath, DrawPaint);
}
public override bool OnTouchEvent(MotionEvent e)
{
var touchX = e.GetX();
var touchY = e.GetY();
switch (e.Action)
{
case MotionEventActions.Down:
DrawPath.MoveTo(touchX, touchY);
break;
case MotionEventActions.Move:
DrawPath.LineTo(touchX, touchY);
break;
case MotionEventActions.Up:
DrawCanvas.DrawPath(DrawPath, DrawPaint);
DrawPath.Reset();
break;
default:
return false;
}
Invalidate();
return true;
}
public void LoadImageFromFile()
{
if (ImageFilePath != null && ImageFilePath != "")
{
_image = BitmapFactory.DecodeFile(ImageFilePath);
}
}
public Bitmap GetImageFromView()
{
Bitmap tempBitmap = null;
try
{
tempBitmap = Bitmap.CreateBitmap(w, h, Bitmap.Config.Argb8888);
DrawCanvas = new Canvas(tempBitmap);
if (_image != null)
{
DrawPaint.SetStyle(Paint.Style.Fill);
DrawPaint.Color = Color.White;
DrawCanvas.DrawRect(new Rect(0, 0, w, h), DrawPaint);
float scaleX = (float)_image.Width / w;
float scaleY = (float)_image.Height / h;
Rect outRect = new Rect();
int outWidth, outHeight;
if (scaleX > scaleY)
{
outWidth = w;
outHeight = (int)(_image.Height / scaleX);
}
else
{
outWidth = (int)(_image.Width / scaleY);
outHeight = h;
}
outRect.Left = w / 2 - outWidth / 2;
outRect.Top = h / 2 - outHeight / 2;
outRect.Right = w / 2 + outWidth / 2;
outRect.Bottom = h / 2 + outHeight / 2;
DrawCanvas.DrawBitmap(_image, new Rect(0, 0, _image.Width, _image.Height), outRect, DrawPaint);
}
else
{
DrawPaint.SetStyle(Paint.Style.Fill);
DrawPaint.Color = Color.White;
DrawCanvas.DrawRect(new Rect(0, 0, w, h), DrawPaint);
}
DrawPaint.Color = CurrentLineColor;
DrawCanvas.DrawBitmap(CanvasBitmap, 0, 0, CanvasPaint);
DrawCanvas.DrawPath(DrawPath, DrawPaint);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return tempBitmap;
}
}
}
ImageHelper.cs
using Android.Views;
using Android.Graphics;
using Android.Content;
using System;
namespace SignaturePadDemo.Droid
{
public class DrawView : View
{
public DrawView(Context context)
: base(context)
{
Start();
}
public Color CurrentLineColor { get; set; }
public String ImageFilePath { get; set; }
public float PenWidth { get; set; }
private Path DrawPath;
private Paint DrawPaint;
private Paint CanvasPaint;
private Canvas DrawCanvas;
private Bitmap CanvasBitmap;
private int w, h;
private Bitmap _image;
private void Start()
{
CurrentLineColor = Color.Black;
PenWidth = 5.0f;
DrawPath = new Path();
DrawPaint = new Paint
{
Color = CurrentLineColor,
AntiAlias = true,
StrokeWidth = PenWidth
};
DrawPaint.SetStyle(Paint.Style.Stroke);
DrawPaint.StrokeJoin = Paint.Join.Round;
DrawPaint.StrokeCap = Paint.Cap.Round;
CanvasPaint = new Paint
{
Dither = true
};
}
public void Clear()
{
try
{
DrawPath = new Path();
CanvasBitmap = Bitmap.CreateBitmap(w, h, Bitmap.Config.Argb8888);
DrawCanvas = new Canvas(CanvasBitmap);
DrawCanvas.DrawColor(Color.White, PorterDuff.Mode.Multiply);
CanvasBitmap.EraseColor(Color.Transparent);
DrawPaint = new Paint
{
Color = CurrentLineColor,
AntiAlias = true,
StrokeWidth = PenWidth
};
DrawPaint.SetStyle(Paint.Style.Stroke);
DrawPaint.StrokeJoin = Paint.Join.Round;
DrawPaint.StrokeCap = Paint.Cap.Round;
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
Invalidate();
}
protected override void OnSizeChanged(int w, int h, int oldw, int oldh)
{
base.OnSizeChanged(w, h, oldw, oldh);
if (w > 0 && h > 0)
{
try
{
CanvasBitmap = Bitmap.CreateBitmap(w, h, Bitmap.Config.Argb8888);
DrawCanvas = new Canvas(CanvasBitmap);
this.w = w;
this.h = h;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
protected override void OnDraw(Canvas canvas)
{
base.OnDraw(canvas);
DrawPaint.Color = CurrentLineColor;
DrawPaint.StrokeWidth = PenWidth;
canvas.DrawBitmap(CanvasBitmap, 0, 0, CanvasPaint);
canvas.DrawPath(DrawPath, DrawPaint);
}
public override bool OnTouchEvent(MotionEvent e)
{
var touchX = e.GetX();
var touchY = e.GetY();
switch (e.Action)
{
case MotionEventActions.Down:
DrawPath.MoveTo(touchX, touchY);
break;
case MotionEventActions.Move:
DrawPath.LineTo(touchX, touchY);
break;
case MotionEventActions.Up:
DrawCanvas.DrawPath(DrawPath, DrawPaint);
DrawPath.Reset();
break;
default:
return false;
}
Invalidate();
return true;
}
public void LoadImageFromFile()
{
if (ImageFilePath != null && ImageFilePath != "")
{
_image = BitmapFactory.DecodeFile(ImageFilePath);
}
}
public Bitmap GetImageFromView()
{
Bitmap tempBitmap = null;
try
{
tempBitmap = Bitmap.CreateBitmap(w, h, Bitmap.Config.Argb8888);
DrawCanvas = new Canvas(tempBitmap);
if (_image != null)
{
DrawPaint.SetStyle(Paint.Style.Fill);
DrawPaint.Color = Color.White;
DrawCanvas.DrawRect(new Rect(0, 0, w, h), DrawPaint);
float scaleX = (float)_image.Width / w;
float scaleY = (float)_image.Height / h;
Rect outRect = new Rect();
int outWidth, outHeight;
if (scaleX > scaleY)
{
outWidth = w;
outHeight = (int)(_image.Height / scaleX);
}
else
{
outWidth = (int)(_image.Width / scaleY);
outHeight = h;
}
outRect.Left = w / 2 - outWidth / 2;
outRect.Top = h / 2 - outHeight / 2;
outRect.Right = w / 2 + outWidth / 2;
outRect.Bottom = h / 2 + outHeight / 2;
DrawCanvas.DrawBitmap(_image, new Rect(0, 0, _image.Width, _image.Height), outRect, DrawPaint);
}
else
{
DrawPaint.SetStyle(Paint.Style.Fill);
DrawPaint.Color = Color.White;
DrawCanvas.DrawRect(new Rect(0, 0, w, h), DrawPaint);
}
DrawPaint.Color = CurrentLineColor;
DrawCanvas.DrawBitmap(CanvasBitmap, 0, 0, CanvasPaint);
DrawCanvas.DrawPath(DrawPath, DrawPaint);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return tempBitmap;
}
}
}
Xamarin.iOS:
ISignService.cs
using System;
using SignaturePadDemo.iOS;
using Xamarin.Forms;
[assembly: Dependency(typeof(ISignService))]
namespace SignaturePadDemo.iOS
{
public class ISignService : ISign
{
public string Sign()
{
string savedFileName = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + "/temp_" + DateTime.Now.ToString("yyyy_mm_dd_hh_mm_ss") + ".jpg";
return savedFileName;
}
}
}
DrawView.cs
using System;
using UIKit;
using System.Drawing;
using CoreGraphics;
using System.Collections.Generic;
using Foundation;
namespace SignaturePadDemo.iOS
{
public class DrawView : UIView
{
public DrawView(RectangleF frame) : base(frame)
{
DrawPath = new CGPath();
CurrentLineColor = UIColor.Black;
PenWidth = 3.0f;
Lines = new List<VESLine>();
}
private PointF PreviousPoint;
private CGPath DrawPath;
private byte IndexCount;
private UIBezierPath CurrentPath;
private List<VESLine> Lines;
public UIColor CurrentLineColor { get; set; }
public String ImageFilePath { get; set; }
public float PenWidth { get; set; }
private UIImage _image = null;
public void Clear()
{
Lines.Clear();
DrawPath.Dispose();
DrawPath = new CGPath();
SetNeedsDisplay();
}
public override void TouchesBegan(NSSet touches, UIEvent evt)
{
IndexCount++;
var path = new UIBezierPath
{
LineWidth = PenWidth
};
var touch = (UITouch)touches.AnyObject;
PreviousPoint = (PointF)touch.PreviousLocationInView(this);
var newPoint = touch.LocationInView(this);
path.MoveTo(newPoint);
InvokeOnMainThread(SetNeedsDisplay);
CurrentPath = path;
var line = new VESLine
{
Path = CurrentPath,
Color = CurrentLineColor,
Index = IndexCount
};
Lines.Add(line);
}
public override void TouchesMoved(NSSet touches, UIEvent evt)
{
var touch = (UITouch)touches.AnyObject;
var currentPoint = touch.LocationInView(this);
if (Math.Abs(currentPoint.X - PreviousPoint.X) >= 4 ||
Math.Abs(currentPoint.Y - PreviousPoint.Y) >= 4)
{
var newPoint = new PointF((float)(currentPoint.X + PreviousPoint.X) / 2, (float)(currentPoint.Y + PreviousPoint.Y) / 2);
CurrentPath.AddQuadCurveToPoint(newPoint, PreviousPoint);
PreviousPoint = (PointF)currentPoint;
}
else
{
CurrentPath.AddLineTo(currentPoint);
}
InvokeOnMainThread(SetNeedsDisplay);
}
public override void TouchesEnded(NSSet touches, UIEvent evt)
{
InvokeOnMainThread(SetNeedsDisplay);
}
public override void TouchesCancelled(NSSet touches, UIEvent evt)
{
InvokeOnMainThread(SetNeedsDisplay);
}
public override void Draw(CGRect rect)
{
foreach (VESLine p in Lines)
{
p.Color.SetStroke();
p.Path.Stroke();
}
}
public void LoadImageFromFile()
{
if (ImageFilePath != null && ImageFilePath != "")
{
_image = ImageHelper.GetRotateImage(ImageFilePath);
}
}
public UIImage GetImageFromView()
{
RectangleF rect;
rect = (RectangleF)Frame;
UIGraphics.BeginImageContext(rect.Size);
CGContext context = UIGraphics.GetCurrentContext();
if (_image != null)
context.DrawImage(Frame, _image.CGImage);
this.Layer.RenderInContext(context);
UIImage image = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();
return image;
}
}
}
ImageHelper.cs
using System;
using System.Drawing;
using CoreGraphics;
using Foundation;
using UIKit;
namespace SignaturePadDemo.iOS
{
public class ImageHelper
{
public static UIImage GetRotateImage(String imagePath)
{
UIImage image = UIImage.FromFile(imagePath);
CGImage imgRef = image.CGImage;
float width = imgRef.Width;
float height = imgRef.Height;
CGAffineTransform transform = CGAffineTransform.MakeIdentity();
RectangleF bounds = new RectangleF(0, 0, width, height);
SizeF imageSize = new SizeF(width, height);
float boundHeight;
UIImageOrientation orient = image.Orientation;
switch (orient)
{
case UIImageOrientation.Up:
transform = CGAffineTransform.MakeIdentity();
break;
case UIImageOrientation.UpMirrored:
transform = CGAffineTransform.MakeTranslation(imageSize.Width, 0.0f);
transform.Scale(-1.0f, 1.0f);
break;
case UIImageOrientation.Down:
transform.Rotate((float)Math.PI);
transform.Translate(imageSize.Width, imageSize.Height);
break;
case UIImageOrientation.DownMirrored:
transform = CGAffineTransform.MakeTranslation(0.0f, imageSize.Height);
transform.Scale(1.0f, -1.0f);
break;
case UIImageOrientation.LeftMirrored:
boundHeight = bounds.Size.Height;
bounds.Height = bounds.Size.Width;
bounds.Width = boundHeight;
transform.Scale(-1.0f, 1.0f);
transform.Rotate((float)Math.PI / 2.0f);
break;
case UIImageOrientation.Left:
boundHeight = bounds.Size.Height;
bounds.Height = bounds.Size.Width;
bounds.Width = boundHeight;
transform = CGAffineTransform.MakeRotation((float)Math.PI / 2.0f);
transform.Translate(imageSize.Height, 0.0f);
break;
case UIImageOrientation.RightMirrored:
boundHeight = bounds.Size.Height;
bounds.Height = bounds.Size.Width;
bounds.Width = boundHeight;
transform = CGAffineTransform.MakeTranslation(imageSize.Height, imageSize.Width);
transform.Scale(-1.0f, 1.0f);
transform.Rotate(3.0f * (float)Math.PI / 2.0f);
break;
case UIImageOrientation.Right:
boundHeight = bounds.Size.Height;
bounds.Height = bounds.Size.Width;
bounds.Width = boundHeight;
transform = CGAffineTransform.MakeRotation(-(float)Math.PI / 2.0f);
transform.Translate(0.0f, imageSize.Width);
break;
default:
break;
}
UIGraphics.BeginImageContext(bounds.Size);
CGContext context = UIGraphics.GetCurrentContext();
context.ConcatCTM(transform);
context = UIGraphics.GetCurrentContext();
context.DrawImage(new RectangleF(0, 0, width, height), imgRef);
UIImage imageCopy = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();
return imageCopy;
}
public static bool SaveRotateImage(String imagePath)
{
UIImage imageCopy = GetRotateImage(imagePath);
NSData data = imageCopy.AsJPEG();
NSError error = new NSError();
bool bSuccess = data.Save(imagePath, true, out error);
return bSuccess;
}
}
}
VESLine.cs
using System;
using UIKit;
namespace SignaturePadDemo.iOS
{
public class VESLine
{
public UIBezierPath Path
{
get;
set;
}
public UIColor Color
{
get;
set;
}
public byte Index
{
get;
set;
}
}
}
You can download from here
I am using Xamarin.Forms and I want to globally make the buttons look a little nicer.
I have achieved this just fine for the Android version using a custom renderer, but I am having trouble doing the same with iOS.
When defining buttons in my XAML pages, I reference "CustomButton" instead of "Button", and then I have the following CustomButtonRenderer in my iOS app.
Most of the style changes work just fine (border radius, etc), but I cannot seem to make it render a background gradient for the button.
Here is my code so far, but the background just displays as white. How can I make it display a gradient with the text on top?
class CustomButtonRenderer : ButtonRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
if (Control != null)
{
var gradient = new CAGradientLayer();
gradient.Frame = Control.Layer.Bounds;
gradient.Colors = new CGColor[]
{
UIColor.FromRGB(51, 102, 204).CGColor,
UIColor.FromRGB(51, 102, 204).CGColor
};
Control.Layer.AddSublayer(gradient);
Control.Layer.CornerRadius = 10;
Control.Layer.BorderColor = UIColor.FromRGB(51, 102, 204).CGColor;
Control.Layer.BorderWidth = 1;
Control.VerticalAlignment = UIControlContentVerticalAlignment.Center;
}
}
}
1st) Do not use AddSublayer, use InsertSublayerBelow so that the Z-order will be correct and your Title text will be on top.
2nd) Override LayoutSubviews and update your CAGradientLayer frame to match your UIButton.
3rd) Enjoy your gradient:
Complete Example:
[assembly: ExportRenderer(typeof(CustomButton), typeof(CustomButtonRenderer))]
namespace AppCompatRender.iOS
{
public class CustomButtonRenderer : Xamarin.Forms.Platform.iOS.ButtonRenderer
{
public override void LayoutSubviews()
{
foreach (var layer in Control?.Layer.Sublayers.Where(layer => layer is CAGradientLayer))
layer.Frame = Control.Bounds;
base.LayoutSubviews();
}
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
if (e.OldElement == null)
{
var gradient = new CAGradientLayer();
gradient.CornerRadius = Control.Layer.CornerRadius = 10;
gradient.Colors = new CGColor[]
{
UIColor.FromRGB(51, 102, 104).CGColor,
UIColor.FromRGB(51, 202, 204).CGColor
};
var layer = Control?.Layer.Sublayers.LastOrDefault();
Control?.Layer.InsertSublayerBelow(gradient, layer);
}
}
}
}
Update:
If you are using iOS 10+ with newer version of Xamarin.Forms, the Control.Bounds during calls to LayoutSubViews will all be zeros. You will need to set the gradient layer Frame size during sets to the control's Frame property, i.e.:
public class CustomButtonRenderer : Xamarin.Forms.Platform.iOS.ButtonRenderer
{
public override CGRect Frame
{
get
{
return base.Frame;
}
set
{
if (value.Width > 0 && value.Height > 0)
{
foreach (var layer in Control?.Layer.Sublayers.Where(layer => layer is CAGradientLayer))
layer.Frame = new CGRect(0, 0, value.Width, value.Height);
}
base.Frame = value;
}
}
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
if (e.OldElement == null)
{
var gradient = new CAGradientLayer();
gradient.CornerRadius = Control.Layer.CornerRadius = 20;
gradient.Colors = new CGColor[]
{
UIColor.FromRGB(51, 102, 104).CGColor,
UIColor.FromRGB(51, 202, 204).CGColor
};
var layer = Control?.Layer.Sublayers.LastOrDefault();
Control?.Layer.InsertSublayerBelow(gradient, layer);
}
}
}
In the moment that OnElementChanged is called, Control.Layer.Bounds is completely zero'd out. In your rendered you will need to add methods to update the Gradient's Frame to match the Control.Layer's frame.
Responding to the comment by Robert Cafazzo, I can help to slightly adjust this render so that it works correctly:
public class GdyBtnRendererIos : ButtonRenderer
{
#region Colors
static Color rosecolor = (Color)App.Current.Resources["ClrGeneralrose"];
static Color orangecolor = (Color)App.Current.Resources["ClrRoseOrange"];
CGColor roseCGcolor = rosecolor.ToCGColor();
CGColor orangeCGcolor = orangecolor.ToCGColor();
#endregion
CAGradientLayer gradient;
public override CGRect Frame
{
get => base.Frame;
set
{
if (value.Width > 0 && value.Height > 0)
{
if (Control?.Layer.Sublayers != null)
foreach (var layer in Control?.Layer.Sublayers.Where(layer => layer is CAGradientLayer))
layer.Frame = new CGRect(0, 0, value.Width, value.Height);
}
base.Frame = value;
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == "Renderer")
{
gradient.Frame = new CGRect(0, 0, Frame.Width, Frame.Height);
}
}
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
if (e.OldElement != null) return;
gradient = new CAGradientLayer
{
CornerRadius = Control.Layer.CornerRadius,
Colors = new CGColor[] { roseCGcolor, orangeCGcolor },
StartPoint = new CGPoint(0.1, 0.5),
EndPoint = new CGPoint(1.1, 0.5)
};
var layer = Control?.Layer.Sublayers.LastOrDefault();
Control?.Layer.InsertSublayerBelow(gradient, layer);
base.Draw(Frame);
}