Round Corner Gridlayout Xamarin Android - xamarin

I want to Create RoundCorner GridLayout.I have added round corner to Gridlayot Control in Xamarin but Gridlayout BackgroundColor display in Back Side like this.Please help me to resolve issue.
GridLayout after Drawable Add
Xamarin.Forms Xaml Code :
xmlns:local="clr-namespace:XamainControl.Renderer;assembly=XamainKcsControl"
<Grid BackgroundColor="Purple" WidthRequest="300">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<local:KcsGrid Grid.Row="0"
BorderColor="Blue"
BackgroundColor="Yellow"
BorderRadius="20"
BorderWidth="10" Padding="15" >
<Label Text="Customization Page"
FontSize="25"
TextColor="Red"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand"/>
</local:KcsGrid>
</Grid>
Custom Grid Property Create Xamarin.Forms
public class KcsGrid : Grid
{
#region Constructor
public KcsGrid() : base()
{
this.Margin = new Thickness(0, 0, 0, -6);
}
#endregion
#region Properties
public static BindableProperty BorderColorProperty = BindableProperty.Create<KcsGrid, Color>(o => o.BorderColor, Color.Transparent);
public Color BorderColor
{
get { return (Color)GetValue(BorderColorProperty); }
set { SetValue(BorderColorProperty, value); }
}
public static BindableProperty BorderWidthProperty = BindableProperty.Create<KcsGrid, float>(o => o.BorderWidth, 0);
public float BorderWidth
{
get { return (float)GetValue(BorderWidthProperty); }
set { SetValue(BorderWidthProperty, value); }
}
public static BindableProperty BorderRadiusProperty = BindableProperty.Create<KcsGrid, float>(o => o.BorderRadius, 0);
public float BorderRadius
{
get { return (float)GetValue(BorderRadiusProperty); }
set { SetValue(BorderRadiusProperty, value); }
}
public static BindableProperty WidthValueProperty = BindableProperty.Create<KcsGrid, int>(o => o.WidthValue,100);
public int WidthValue
{
get { return (int)GetValue(WidthValueProperty); }
set { SetValue(WidthValueProperty, value); }
}
public static BindableProperty HeightValueProperty = BindableProperty.Create<KcsGrid, int>(o => o.HeightValue,50);
public int HeightValue
{
get { return (int)GetValue(HeightValueProperty); }
set { SetValue(HeightValueProperty, value); }
}
#endregion
}
Xamarin.Android Code :
namespace XamainKcsControl.Droid.Renderer
{
public class CustomGridRender : ViewRenderer<KcsGrid,GridLayout>
{
private BorderRenderer _renderer;
GridLayout Cusgd;
protected override void OnElementChanged(ElementChangedEventArgs<KcsGrid> e)
{
base.OnElementChanged(e);
if (e.OldElement != null || this.Element == null)
return;
Cusgd = new GridLayout(Context);
if (Control == null)
{
var curgd = Element as KcsGrid;
SetNativeControl(Cusgd);
UpdateBackground(curgd);
//UpdateMeasurelayout(curgd);
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (Element == null)
return;
var Gd = Element as KcsGrid;
if (e.PropertyName == KcsGrid.BorderColorProperty.PropertyName || e.PropertyName == KcsGrid.BorderWidthProperty.PropertyName || e.PropertyName == KcsGrid.BorderRadiusProperty.PropertyName)
{
UpdateBackground(Gd);
}
if (e.PropertyName == KcsGrid.WidthValueProperty.PropertyName || e.PropertyName == KcsGrid.HeightValueProperty.PropertyName)
{
UpdateMeasurelayout(Gd);
}
//SetNativeControl(Cusgd);
}
void UpdateMeasurelayout(KcsGrid Gd)
{
GridLayout.LayoutParams Gdparm = new GridLayout.LayoutParams();
Gdparm.Width = (int)Forms.Context.ToPixels(Gd.WidthValue);
Gdparm.Height = (int)Forms.Context.ToPixels(Gd.HeightValue);
Cusgd.LayoutParameters = Gdparm;
//SetNativeControl(Cusgd);
}
void UpdateBackground(KcsGrid Gd)
{
if (_renderer != null)
{
_renderer.Dispose();
_renderer = null;
}
_renderer = new BorderRenderer();
Cusgd.Background = _renderer.GetBorderBackground(Gd.BorderColor,Gd.BackgroundColor, Gd.BorderWidth, Gd.BorderRadius);
}
}
}
Gradient Drawable
public Drawable GetBorderBackground(Color borderColor, Color backgroundColor, float borderWidth, float borderRadius)
{
if (_background != null)
{
_background.Dispose();
_background = null;
}
borderWidth = borderWidth > 0 ? borderWidth : 0;
borderRadius = borderRadius > 0 ? borderRadius : 0;
borderColor = borderColor != Color.Default ? borderColor : Color.Transparent;
backgroundColor = backgroundColor != Color.Default ? backgroundColor : Color.Transparent;
var strokeWidth = Xamarin.Forms.Forms.Context.ToPixels(borderWidth);
var radius = Xamarin.Forms.Forms.Context.ToPixels(borderRadius);
_background = new GradientDrawable();
_background.SetColor(backgroundColor.ToAndroid());
if (radius > 0)
_background.SetCornerRadius(radius);
if (borderColor != Color.Transparent && strokeWidth > 0)
{
_background.SetStroke((int)strokeWidth, borderColor.ToAndroid());
}
return _background;
}

Related

How to set corner radius for button in xamarin uwp by programatically?

I am having an grid holding in Button. I want to set corner radius for this button by programmatically in xamarin.uwp. If it possible means i want to set corner radius for Bottom left alone. Please suggest your ideas for this query.
For your requirement, you could custom BottomLeft BindableProperty. then use it in your custom button render like the following.
Custom Forms buttom
public class MyButton : Button
{
public static readonly BindableProperty BottomLeftProperty = BindableProperty.Create(
propertyName: "BottomLeft",
returnType: typeof(int),
declaringType: typeof(MyButton),
defaultValue: default(int));
public int BottomLeft
{
get { return (int)GetValue(BottomLeftProperty); }
set { SetValue(BottomLeftProperty, value); }
}
}
CustomButtonRenderer.cs
public class CustomButtonRenderer : ButtonRenderer
{
Windows.UI.Xaml.Controls.ContentPresenter _contentPresenter;
MyButton _myElement;
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
if (Control == null)
{
SetNativeControl(new FormsButton());
}
if (e.NewElement != null)
{
Control.Loaded += Control_Loaded;
_myElement = Element as MyButton;
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == MyButton.BottomLeftProperty.PropertyName)
{
UpdateBottomLeftBorderRadius(_myElement.BottomLeft);
}
}
private void Control_Loaded(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
_contentPresenter = MyFindChildByName(Control, "ContentPresenter") as Windows.UI.Xaml.Controls.ContentPresenter;
if (_myElement.IsSet(MyButton.BottomLeftProperty) && _myElement.BottomLeft != (int)MyButton.BottomLeftProperty.DefaultValue)
{
UpdateBottomLeftBorderRadius(_myElement.BottomLeft);
}
}
private void UpdateBottomLeftBorderRadius(int bottomLeft)
{
if (_contentPresenter != null)
{
_contentPresenter.CornerRadius = new CornerRadius(0, 0, 0, bottomLeft);
}
}
public static DependencyObject MyFindChildByName(DependencyObject parant, string ControlName)
{
int count = VisualTreeHelper.GetChildrenCount(parant);
for (int i = 0; i < count; i++)
{
var MyChild = VisualTreeHelper.GetChild(parant, i);
if (MyChild is FrameworkElement && ((FrameworkElement)MyChild).Name == ControlName)
return MyChild;
var FindResult = MyFindChildByName(MyChild, ControlName);
if (FindResult != null)
return FindResult;
}
return null;
}
}
Usage
<local:MyButton x:Name="Hello" Text="hello"
WidthRequest="100"
HeightRequest="100"
Margin="0,100,0,0"
BottomLeft="15"
VerticalOptions="Center"
HorizontalOptions="Center"
Clicked="MyButton_Clicked"/>
For edit the bottom left property programatically.
HelloBtn.BottomLeft = 30;

Custom Renderer for Picker in Xamarin.Forms

I want to customize my picker. I created a custom renderer for my picker but I dont know how the customization settings. How can I change the font style and size of the item? and How can I remove the two lines?
public class CustomPickerRenderer : PickerRenderer
{
public CustomPickerRenderer(Context context) : base(context)
{
AutoPackage = false;
}
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged(e);
if (e.OldElement == null)
{
Control.Background = null;
var layoutParams = new MarginLayoutParams(Control.LayoutParameters);
layoutParams.SetMargins(0, 0, 0, 0);
Control.LayoutParameters = layoutParams;
Control.SetPadding(0, 0, 0, 0);
SetPadding(0, 0, 0, 0);
}
}
}
If you want to set the fontSize of the text , you first need to customize a subclass extends from NumberPicker and overwrite the method AddView.
public class TextColorNumberPicker: NumberPicker
{
public TextColorNumberPicker(Context context) : base(context)
{
}
public override void AddView(View child, int index, ViewGroup.LayoutParams #params)
{
base.AddView(child, index, #params);
UpdateView(child);
}
public void UpdateView(View view)
{
if ( view is EditText ) {
//set the font of text
((EditText)view).TextSize = 8;
}
}
}
If you want to remove the lines,you should rewrite the NumberPicker
in Android Custom Renderer
public class MyAndroidPicker:PickerRenderer
{
IElementController ElementController => Element as IElementController;
public MyAndroidPicker()
{
}
private AlertDialog _dialog;
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged(e);
if (e.NewElement == null || e.OldElement != null)
return;
Control.Click += Control_Click;
}
protected override void Dispose(bool disposing)
{
Control.Click -= Control_Click;
base.Dispose(disposing);
}
private void SetPickerDividerColor(TextColorNumberPicker picker)
{
Field[] fields = picker.Class.GetDeclaredFields();
foreach (Field pf in fields)
{
if(pf.Name.Equals("mSelectionDivider"))
{
pf.Accessible = true;
// set the color as transparent
pf.Set(picker, new ColorDrawable(this.Resources.GetColor(Android.Resource.Color.Transparent)));
}
}
}
private void Control_Click(object sender, EventArgs e)
{
Picker model = Element;
var picker = new TextColorNumberPicker(Context);
if (model.Items != null && model.Items.Any())
{
picker.MaxValue = model.Items.Count - 1;
picker.MinValue = 0;
picker.SetBackgroundColor(Android.Graphics.Color.Yellow);
picker.SetDisplayedValues(model.Items.ToArray());
//call the method after you setting DisplayedValues
SetPickerDividerColor(picker);
picker.WrapSelectorWheel = false;
picker.Value = model.SelectedIndex;
}
var layout = new LinearLayout(Context) { Orientation = Orientation.Vertical };
layout.AddView(picker);
ElementController.SetValueFromRenderer(VisualElement.IsFocusedProperty, true);
var builder = new AlertDialog.Builder(Context);
builder.SetView(layout);
builder.SetTitle(model.Title ?? "");
builder.SetNegativeButton("Cancel ", (s, a) =>
{
ElementController.SetValueFromRenderer(VisualElement.IsFocusedProperty, false);
// It is possible for the Content of the Page to be changed when Focus is changed.
// In this case, we'll lose our Control.
Control?.ClearFocus();
_dialog = null;
});
builder.SetPositiveButton("Ok ", (s, a) =>
{
ElementController.SetValueFromRenderer(Picker.SelectedIndexProperty, picker.Value);
// It is possible for the Content of the Page to be changed on SelectedIndexChanged.
// In this case, the Element & Control will no longer exist.
if (Element != null)
{
if (model.Items.Count > 0 && Element.SelectedIndex >= 0)
Control.Text = model.Items[Element.SelectedIndex];
ElementController.SetValueFromRenderer(VisualElement.IsFocusedProperty, false);
// It is also possible for the Content of the Page to be changed when Focus is changed.
// In this case, we'll lose our Control.
Control?.ClearFocus();
}
_dialog = null;
});
_dialog = builder.Create();
_dialog.DismissEvent += (ssender, args) =>
{
ElementController?.SetValueFromRenderer(VisualElement.IsFocusedProperty, false);
};
_dialog.Show();
}
}
I also used this CustomRenderer which was posted before only instead of overriding it you can change the properties like this.
private void Control_Click(object sender, EventArgs e)
{
Picker model = Element;
var picker = new MyNumberPicker(Context);
if (model.Items != null && model.Items.Any())
{
// set style here
picker.MaxValue = model.Items.Count - 1;
picker.MinValue = 0;
picker.SetBackgroundColor(Android.Graphics.Color.Transparent);
picker.SetDisplayedValues(model.Items.ToArray());
//call the method after you setting DisplayedValues
SetPickerDividerColor(picker);
picker.WrapSelectorWheel = false;
picker.Value = model.SelectedIndex;
// change Text Size and Divider
picker.TextSize = 30;
picker.SelectionDividerHeight = 1;
}

Xamarin Forms Gradient Renderer not working on iOS

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();
}

Buttons inside infowindow doesn't get clicked - Xamarin forms

Hi everyone, i had to create custom info-window in xamarin forms as shown in the screenshot above. I have created a custom renderer for that but the problem i am having right now is that buttons are not getting clicked. Xamarin is taking entire info-window as a clickable view. Please guide me what i am doing wrong or is it possible to achieve button clicks in Xamarin forms. Thanks in advance.
Here is the code for custom renderer:
using System;
using System.Collections.Generic;
using Android.Content;
using Android.Gms.Maps;
using Android.Gms.Maps.Model;
using Xamarin.Forms;
using Xamarin.Forms.Maps;
using Xamarin.Forms.Maps.Android;
using SalesApp.Droid.CustomRenderer;
using Android.Widget;
using SalesApp.CustomControls;
using SalesApp.Droid.Listeners;
[assembly: ExportRenderer(typeof(CustomMap), typeof(CustomMapRenderer))]
namespace SalesApp.Droid.CustomRenderer
{
public class CustomMapRenderer : MapRenderer, GoogleMap.IInfoWindowAdapter, IOnMapReadyCallback
{
GoogleMap map;
List<CustomPin> customPins;
bool isDrawn;
protected override void OnElementChanged(Xamarin.Forms.Platform.Android.ElementChangedEventArgs<Map> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
map.InfoWindowClick -= OnInfoWindowClick;
}
if (e.NewElement != null)
{
var formsMap = (CustomMap)e.NewElement;
customPins = formsMap.CustomPins
Control.GetMapAsync(this);
}
}
public void OnMapReady(GoogleMap googleMap)
{
map = googleMap;
map.InfoWindowClick += OnInfoWindowClick;
map.SetInfoWindowAdapter(this);
}
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName.Equals("VisibleRegion") && !isDrawn)
{
map.Clear();
if (customPins != null)
{
foreach (var pin in customPins)
{
var marker = new MarkerOptions();
marker.SetPosition(new LatLng(pin.Pin.Position.Latitude, pin.Pin.Position.Longitude));
marker.SetTitle(pin.Pin.Label);
marker.SetSnippet(pin.Pin.Address);
marker.SetIcon(BitmapDescriptorFactory.FromResource((int)typeof(Resource.Drawable).GetField(pin.Image).GetValue(null)));
map.AddMarker(marker);
}
isDrawn = true;
}
}
}
protected override void OnLayout(bool changed, int l, int t, int r, int b)
{
base.OnLayout(changed, l, t, r, b);
if (changed)
{
isDrawn = false;
}
}
void OnInfoWindowClick(object sender, GoogleMap.InfoWindowClickEventArgs e)
{
var customPin = GetCustomPin(e.Marker);
if (customPin == null)
{
throw new Exception("Custom pin not found");
}
//if (!string.IsNullOrWhiteSpace(customPin.Url))
//{
// var url = Android.Net.Uri.Parse(customPin.Url);
// var intent = new Intent(Intent.ActionView, url);
// intent.AddFlags(ActivityFlags.NewTask);
// Android.App.Application.Context.StartActivity(intent);
//}
Android.App.Application.Context.StartActivity(new Intent(Android.App.Application.Context, typeof(DialogActivity)));
}
void IOnMapReadyCallback.OnMapReady(GoogleMap googleMap)
{
InvokeOnMapReadyBaseClassHack(googleMap);
map = googleMap;
map.SetInfoWindowAdapter(this);
map.InfoWindowClick += OnInfoWindowClick;
}
public Android.Views.View GetInfoContents(Marker marker)
{
var inflater = Android.App.Application.Context.GetSystemService(Context.LayoutInflaterService) as Android.Views.LayoutInflater;
if (inflater != null)
{
Android.Views.View view;
var customPin = GetCustomPin(marker);
if (customPin == null)
{
throw new Exception("Custom pin not found");
}
if (customPin.Id == "Xamarin")
{
view = inflater.Inflate(Resource.Layout.XamarinMapInfoWindow, null);
}
else
{
view = inflater.Inflate(Resource.Layout.MapInfoWindow, null);
}
var infoTitle = view.FindViewById<TextView>(Resource.Id.InfoWindowTitle);
var address = view.FindViewById<TextView>(Resource.Id.Address);
var contactPerson = view.FindViewById<TextView>(Resource.Id.ContactPerson);
var phone = view.FindViewById<TextView>(Resource.Id.Phone);
var zip = view.FindViewById<TextView>(Resource.Id.Zip);
var email = view.FindViewById<TextView>(Resource.Id.Email);
var cvr = view.FindViewById<TextView>(Resource.Id.CVR);
var turnover = view.FindViewById<TextView>(Resource.Id.Turnover);
var noOfEmp = view.FindViewById<TextView>(Resource.Id.NoOfEmp);
var leadStatus = view.FindViewById<TextView>(Resource.Id.LeadStatus);
var category = view.FindViewById<TextView>(Resource.Id.Cat);
if (infoTitle != null)
{
infoTitle.Text = customPin.LeedsAndCustomersData.FullName;
}
if (address != null)
{
address.Text = customPin.LeedsAndCustomersData.Address + " " + customPin.LeedsAndCustomersData.CityName;
}
if (contactPerson != null)
{
contactPerson.Text = customPin.LeedsAndCustomersData.FirstName;
}
if (phone != null)
{
phone.Text = customPin.LeedsAndCustomersData.Phone;
}
if (zip != null)
{
zip.Text = customPin.LeedsAndCustomersData.ZipCode;
}
if (email != null)
{
email.Text = customPin.LeedsAndCustomersData.Email;
}
if (cvr != null)
{
cvr.Text = customPin.LeedsAndCustomersData.CVR;
}
if (turnover != null)
{
turnover.Text = customPin.LeedsAndCustomersData.Turnover;
}
if (noOfEmp != null)
{
noOfEmp.Text = customPin.LeedsAndCustomersData.NoOfEmployees.ToString();
}
if (leadStatus != null)
{
leadStatus.Text = customPin.LeedsAndCustomersData.LeadStatus;
}
if (category != null)
{
category.Text = customPin.LeedsAndCustomersData.BusinessType;
}
//add listeners
OnTouchPhoneListener callButtonListener = new OnTouchPhoneListener();
phone.SetOnTouchListener(callButtonListener);
return view;
}
return null;
}
public Android.Views.View GetInfoWindow(Marker marker)
{
return null;
}
CustomPin GetCustomPin(Marker annotation)
{
var position = new Position(annotation.Position.Latitude, annotation.Position.Longitude);
foreach (var pin in customPins)
{
if (pin.Pin.Position == position)
{
return pin;
}
}
return null;
}
void InvokeOnMapReadyBaseClassHack(GoogleMap googleMap)
{
System.Reflection.MethodInfo onMapReadyMethodInfo = null;
Type baseType = typeof(MapRenderer);
foreach (var currentMethod in baseType.GetMethods(System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.Instance |
System.Reflection.BindingFlags.DeclaredOnly))
{
if (currentMethod.IsFinal && currentMethod.IsPrivate)
{
if (string.Equals(currentMethod.Name, "OnMapReady", StringComparison.Ordinal))
{
onMapReadyMethodInfo = currentMethod;
break;
}
if (currentMethod.Name.EndsWith(".OnMapReady", StringComparison.Ordinal))
{
onMapReadyMethodInfo = currentMethod;
break;
}
}
}
if (onMapReadyMethodInfo != null)
{
onMapReadyMethodInfo.Invoke(this, new[] { googleMap });
}
}
}
}
OnInfoWindowElemTouchListener:
using Android.OS;
using Android.Views;
using Android.Widget;
using System.Threading.Tasks;
using Android.Gms.Maps.Model;
using Android.Graphics.Drawables;
using Java.Lang;
namespace SalesApp.Droid.Listeners
{
public abstract class OnInfoWindowElemTouchListener : Java.Lang.Object, View.IOnTouchListener
{
private View view;
private Drawable bgDrawableNormal;
private Drawable bgDrawablePressed;
private Handler handler = new Handler();
private Marker marker;
private static bool endPressStatus = false;
private bool pressed = false;
//public OnInfoWindowElemTouchListener(View view, Drawable bgDrawableNormal, Drawable bgDrawablePressed)
//{
// this.view = this.view;
// this.bgDrawableNormal = this.bgDrawableNormal;
// this.bgDrawablePressed = this.bgDrawablePressed;
//}
public OnInfoWindowElemTouchListener(View view)
{
this.view = this.view;
}
public OnInfoWindowElemTouchListener(Button button)
{
}
public OnInfoWindowElemTouchListener()
{
}
public void setMarker(Marker marker)
{
this.marker = this.marker;
}
/*public bool OnTouch(View v, MotionEvent e)
{
if (e.Action == MotionEventActions.Down)
{
// do stuff
return true;
}
if (e.Action == MotionEventActions.Up)
{
// do other stuff
return true;
}
return false;
}*/
public bool OnTouch(View vv, MotionEvent e)
{
if (0 <= e.GetX() && e.GetX() <= vv.Width && 0 <= e.GetY() && e.GetY() <= vv.Height)
{
switch (e.ActionMasked)
{
case MotionEventActions.Down:
startPress();
break;
// We need to delay releasing of the view a little so it shows the
// pressed state on the screen
case MotionEventActions.Up:
//handler.PostDelayed(ConfirmClickRunnable, 150);
//Task.Factory.StartNew(() =>onClickConfirmed(view, marker));
Task.Factory.StartNew(() => onClickConfirmed());
Task.Delay(150);
break;
case MotionEventActions.Cancel:
endPress();
break;
default:
break;
}
}
else
{
// If the touch goes outside of the view's area
// (like when moving finger out of the pressed button)
// just release the press
endPress();
}
return false;
}
private void startPress()
{
if (!pressed)
{
pressed = true;
//handler.RemoveCallbacks(ConfirmClickRunnable);
if ((marker != null))
{
marker.ShowInfoWindow();
}
}
}
public bool endPress()
{
if (pressed)
{
this.pressed = false;
//handler.RemoveCallbacks(ConfirmClickRunnable);
view.SetBackgroundColor(Android.Graphics.Color.Green);
if ((marker != null))
{
marker.ShowInfoWindow();
}
endPressStatus = true;
return true;
}
else
{
endPressStatus = false;
return false;
}
}
//private Runnable confirmClickRunnable = new RunnableAnonymousInnerClassHelper(this);
private Runnable ConfirmClickRunnable = new Java.Lang.Runnable(() =>
{
if (endPressStatus)
{
//onClickConfirmed(view, marker);
}
});
/*private class RunnableAnonymousInnerClassHelper : Java.Lang.Object, Java.Lang.IRunnable
{
private readonly Context outerInstance;
public RunnableAnonymousInnerClassHelper(Context outerInstance)
{
this.outerInstance = outerInstance;
}
public void Run()
{
if (endPressStatus)
{
onClickConfirmed();
}
}
}*/
//public abstract void onClickConfirmed(View v, Marker marker);
public abstract void onClickConfirmed();
}
}
OnTouchPhoneListener:
using Android.Widget;
using System;
namespace SalesApp.Droid.Listeners
{
public class OnTouchPhoneListener : OnInfoWindowElemTouchListener
//public class OnTouchPhoneListener
{
Button button;
public OnTouchPhoneListener(Button button)
{
}
public OnTouchPhoneListener()
{
}
public override void onClickConfirmed() {
Console.WriteLine("call Button Clicked");
}
}
}
Why is the info window implemented in your custom renderer? This makes the class CustomMapRenderer do (at least) do two things, violate the SRP and makes the code way harder to understand.
Instead I'd go with the custom renderer just for the map. Then in Xamarin.Forms you can implement the overlay with Xamarin.Forms means (e.g. the map view in an absolute or relative layout and the overlay in the same layout but a reduced size). Please see the following pseudo-XAML
<ContentPage [...]>
<AbsoluteLayout>
<local:MapView x:Name="MapView" AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutBounds="0,0,1,1" />
<local:MapOverlay AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutBounds=".5, .5, .5, .5" />
</AbsoluteLayout>
</ContentPage>
Then you can introduce a viewmodel PinInfoViewModel which is exposed by MapView.SelectedPinInfo and can be set as MapOverlay.BindingContext
<local:MapOverlay AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutBounds=".5, .5, .5, .5" BindingContext="{Binding Source={x:Reference MapView}, Path=SelectedPinInfo}" />
MapOverlay - in turn - binds to all the properties of PinInfoViewModel. Last thing we'll have to do is making the overlay invisible if no pin is selected. For this purpose we expose MapView.IsPinSelected and bind MapOverlay.IsVisible to it
<local:MapOverlay AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutBounds=".5, .5, .5, .5" BindingContext="{Binding Source={x:Reference MapView}, Path=SelectedPinInfo}" IsVisible="{Binding Source=MapView, Path=IsPinSelected}" />
While up to this point we have not done anything to solve your issue, you can now implement the overlay way simpler, e.g. with a StackLayout. And you can bind the Buttons commands to do whatever you'd like to do.

Capturing a hand-drawn signature with Xamarin Forms (PCL)

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

Resources