I'm trying to add a red dot as shown below for TabbedPage. I have searched the internet but it doesn't look like anything. It looks like Badges but it's not. I want to permanently assign it at any time. I also don't know what keywords to search for exactly. For Xamarin iOS and Android.
MainView.xaml
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:views="clr-namespace:Appssss.Views"
x:Class="Appssss.Views.Mainview">
<!--Pages can be added as references or inline-->
<views:AboutPage Title="Page 1" IconImageSource="homeicon" BackgroundColor="#fff"/>
<views:LoginPage Title="Page 2" IconImageSource="feeds" BackgroundColor="#fff"/>
<views:AboutPage Title="Page 3" IconImageSource="moneys" BackgroundColor="#fbfbfb" />
<views:AboutPage Title="Page 4" IconImageSource="chats" BackgroundColor="#fff" />
<views:AboutPage Title="Page 5" IconImageSource="usericon" BackgroundColor="#fff"/>
</TabbedPage>
Ask for help from everyone. Thank you
Update
MyTabDotRenderer.cs
[assembly: ExportRenderer(typeof(TabbedPage), typeof(MyTabDotRenderer))]
namespace XXXX.iOS
{
internal class MyTabDotRenderer : TabbedRenderer
{
private bool _initialized;
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
//TabBar.ClipsToBounds = true;
TabBar.TintColor = UIColor.Gray;
TabBar.BarTintColor = UIColor.White;
TabBar.BackgroundColor = UIColor.White;
MessagingCenter.Subscribe<object, int>(this, "Add", (obj, index) => {
TabBar.addItemBadge(index);
});
MessagingCenter.Subscribe<object, int>(this, "Remove", (obj, index) => {
TabBar.removeItemBadge(index);
});
}
public override void ViewWillAppear(bool animated)
{
if (!_initialized)
{
if (TabBar?.Items == null)
return;
foreach (var item in TabBar.Items)
{
item.Image = ScalingImageToSize(item.Image, new CGSize(20, 20)); // set the size here as you want
}
var tabs = Element as TabbedPage;
if (tabs != null)
{
for (int i = 0; i < TabBar.Items.Length; i++)
{
UpdateItem(TabBar.Items[i], tabs.Children[i].Icon, tabs.Children[i].StyleId);
}
}
_initialized = true;
}
base.ViewWillAppear(animated);
}
private void UpdateItem(UITabBarItem item, string icon, string badgeValue)
{
if (item == null) return;
if (UIDevice.CurrentDevice.CheckSystemVersion(13, 0))
{
//change icon select
if (icon.EndsWith(".png"))
icon = icon.Replace(".png", "_selected.png");
else
icon += "_selected";
item.SelectedImage = UIImage.FromBundle(icon);
item.SelectedImage.AccessibilityIdentifier = icon;
item.SelectedImage = ScalingImageToSize(item.SelectedImage, new CGSize(20, 20)); // set the size here as you want
//change icon select
UITabBarAppearance app = new UITabBarAppearance();
app.ConfigureWithOpaqueBackground();
app.BackgroundColor = UIColor.Clear;
app.StackedLayoutAppearance.Normal.TitleTextAttributes = new UIStringAttributes() { Font = UIFont.FromName("Roboto Medium", 12), ForegroundColor = Color.FromHex("#808080").ToUIColor() };
app.StackedLayoutAppearance.Selected.TitleTextAttributes = new UIStringAttributes() { Font = UIFont.FromName("Roboto Medium", 13), ForegroundColor = Color.FromHex("#00AA13").ToUIColor() };
item.StandardAppearance = app;
if (UIDevice.CurrentDevice.CheckSystemVersion(15, 0))
{
item.ScrollEdgeAppearance = item.StandardAppearance;
}
}
}
public UIImage ScalingImageToSize(UIImage sourceImage, CGSize newSize)
{
if (UIScreen.MainScreen.Scale == 2.0) //#2x iPhone 6 7 8
{
UIGraphics.BeginImageContextWithOptions(newSize, false, 2.0f);
}
else if (UIScreen.MainScreen.Scale == 3.0) //#3x iPhone 6p 7p 8p...
{
UIGraphics.BeginImageContextWithOptions(newSize, false, 3.0f);
}
else
{
UIGraphics.BeginImageContext(newSize);
}
sourceImage.Draw(new CGRect(0, 0, newSize.Width, newSize.Height));
UIImage newImage = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();
return newImage;
}
}
public static class TabbarExtensions
{
readonly static int tabBarItemTag = 9999;
public static void addItemBadge(this UITabBar tabbar, int index)
{
if (tabbar.Items != null && tabbar.Items.Length == 0) return;
if (index >= tabbar.Items.Length) return;
removeItemBadge(tabbar, index);
var badgeView = new UIView();
badgeView.Tag = tabBarItemTag + index;
badgeView.Layer.CornerRadius = 5;
badgeView.BackgroundColor = UIColor.Red;
var tabFrame = tabbar.Frame;
var percentX = (index + 0.56) / tabbar.Items.Length;
var x = percentX * tabFrame.Width;
var y = tabFrame.Height * 0.1;
badgeView.Frame = new CoreGraphics.CGRect(x, y, 10, 10);
tabbar.AddSubview(badgeView);
}
public static bool removeItemBadge(this UITabBar tabbar, int index)
{
foreach (var subView in tabbar.Subviews)
{
if (subView.Tag == tabBarItemTag + index)
{
subView.RemoveFromSuperview();
return true;
}
}
return false;
}
}
}
MainView.xaml.cs
public MainView(int index)
{
NavigationPage.SetHasNavigationBar(this, false);
InitializeComponent();
On<Xamarin.Forms.PlatformConfiguration.Android>().SetToolbarPlacement(ToolbarPlacement.Bottom);
SetPage(index);
MessagingCenter.Send<object, int>(this, "Add", 1);
}
void SetPage(int index)
{
CurrentPage = Children[index];
}
Update 2
CustomTabbedPageRenderer.cs in project Android
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Appssss.Droid.Renderers;
using Google.Android.Material.BottomNavigation;
using Google.Android.Material.Tabs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Xamarin.Forms.Platform.Android.AppCompat;
using static Google.Android.Material.BottomNavigation.BottomNavigationView;
using static Google.Android.Material.Tabs.TabLayout;
using View = Android.Views.View;
[assembly: ExportRenderer(typeof(TabbedPage), typeof(CustomTabbedPageRenderer))]
namespace Appssss.Droid.Renderers
{
public class CustomTabbedPageRenderer : TabbedPageRenderer
{
TabbedPage tabbedPage;
List<Android.Views.View> list = new List<Android.Views.View>();
public CustomTabbedPageRenderer(Context context) : base(context)
{
for (int i = 0; i < 5; i++)
{
Android.Views.View view = LayoutInflater.From(Context).Inflate(Resource.Drawable.MyView, null);
list.Add(view);
}
}
protected override void OnElementChanged(Xamarin.Forms.Platform.Android.ElementChangedEventArgs<Xamarin.Forms.TabbedPage> e)
{
base.OnElementChanged(e);
if (e.NewElement == null || e.OldElement != null)
return;
if (e.NewElement != null)
{
MessagingCenter.Subscribe<object, int>(this, "Add", (obj, index) => {
TabLayout tablayout = (TabLayout)ViewGroup.GetChildAt(1);
ViewGroup vgroup = (ViewGroup)tablayout.GetChildAt(0);
for (int i = 0; i < vgroup.ChildCount; i++)
{
if (index == i)
{
var view = list[i];
if (view.Parent != null) break;
ViewGroup vvgroup = (ViewGroup)vgroup.GetChildAt(i);
MarginLayoutParams layout1 = new MarginLayoutParams(MarginLayoutParams.MatchParent, MarginLayoutParams.MatchParent);
layout1.SetMargins(120, -135, 0, 0);
vvgroup.AddView(view, layout1);
var ll = vvgroup.LayoutParameters as LinearLayout.LayoutParams;
ll.SetMargins(0, 30, 0, 0);
vvgroup.LayoutParameters = ll;
}
}
});
MessagingCenter.Subscribe<object, int>(this, "Remove", (obj, index) => {
TabLayout tablayout = (TabLayout)ViewGroup.GetChildAt(1);
ViewGroup vgroup = (ViewGroup)tablayout.GetChildAt(0);
for (int i = 0; i < vgroup.ChildCount; i++)
{
if (index == i)
{
var view = list[i];
if (view.Parent == null) break;
ViewGroup vvgroup = (ViewGroup)vgroup.GetChildAt(i);
vvgroup.RemoveView(view);
var ll = vvgroup.LayoutParameters as LinearLayout.LayoutParams;
ll.SetMargins(0, 0, 0, 0);
vvgroup.LayoutParameters = ll;
}
}
});
IEnumerable<View> children = GetAllChildViews(ViewGroup);
BottomNavigationView bottomNavBar = (BottomNavigationView)children.SingleOrDefault(view => view is BottomNavigationView);
if (bottomNavBar != null)
{
tabbedPage = e.NewElement;
bottomNavBar.NavigationItemSelected += BottomNavBar_NavigationItemSelected;
bottomNavBar.Menu.GetItem(0).SetIcon(Resource.Drawable.homeiconselect);
}
}
}
int previous;
private void BottomNavBar_NavigationItemSelected(object sender, BottomNavigationView.NavigationItemSelectedEventArgs e)
{
var current = e.Item.ItemId;
switch (e.Item.ToString())
{
case "Page 1":
e.Item.SetIcon(Resource.Drawable.homeiconselect);
break;
case "Page 2":
e.Item.SetIcon(Resource.Drawable.feedsselect);
break;
case "Page 3":
e.Item.SetIcon(Resource.Drawable.moneysselect);
break;
case "Page 4":
e.Item.SetIcon(Resource.Drawable.chatsselect);
break;
case "Page 5":
e.Item.SetIcon(Resource.Drawable.usericonselect);
break;
default:
break;
}
var previousView = sender as BottomNavigationView;
IMenu menu = previousView.Menu;
var previousItem = menu.GetItem(previous);
if (previous != current)
{
if (previousItem.IsChecked)
{
switch (previousItem.ToString())
{
case "Page 1":
previousItem.SetIcon(Resource.Drawable.homeicon);
break;
case "Page 2":
previousItem.SetIcon(Resource.Drawable.feeds);
break;
case "Page 3":
previousItem.SetIcon(Resource.Drawable.moneys);
break;
case "Page 4":
previousItem.SetIcon(Resource.Drawable.chats);
break;
case "Page 5":
previousItem.SetIcon(Resource.Drawable.usericon);
break;
default:
break;
}
}
}
tabbedPage.CurrentPage = tabbedPage.Children[current];
previous = current;
}
private IEnumerable<View> GetAllChildViews(View view)
{
if (!(view is ViewGroup group))
return new List<View> { view };
List<View> result = new List<View>();
int childCount = group.ChildCount;
for (int i = 0; i < childCount; i++)
{
View child = group.GetChildAt(i);
List<View> childList = new List<View> { child };
childList.AddRange(GetAllChildViews(child));
result.AddRange(childList);
}
return result.Distinct();
}
}
}
MainView.xaml.cs
public Mainview(int index)
{
InitializeComponent();
On<Xamarin.Forms.PlatformConfiguration.Android>().SetToolbarPlacement(ToolbarPlacement.Bottom);
SetPage(index);
}
void SetPage(int index)
{
CurrentPage = Children[index];
}
protected override void OnAppearing()
{
base.OnAppearing();
MessagingCenter.Send<object, int>(this, "Add", 1);
///On iOS this works fine
}
Add a xml in drawable folder called MyView.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button android:background="#drawable/Reddot"
android:layout_width="12dp"
android:layout_height="12dp"/>
</LinearLayout>
Add a xml in drawable folder called Reddot.xml
<?xml version="1.0" encoding="utf-8" ?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<solid android:color="#ff0000"/>
<size android:width="12dp" android:height="12dp"/>
</shape>
It could be achieved by creating a custom renderer for TabbedPage .
iOS Solution
[assembly: ExportRenderer(typeof(TabbedPage), typeof(MyTabRenderer))]
namespace MyForms.iOS
{
internal class MyTabRenderer : TabbedRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
MessagingCenter.Subscribe<object, int>(this, "Add", (obj,index) => {
TabBar.addItemBadge(index);
});
MessagingCenter.Subscribe<object, int>(this, "Remove", (obj,index) => {
TabBar.removeItemBadge(index);
});
}
}
public static class TabbarExtensions
{
readonly static int tabBarItemTag = 9999;
public static void addItemBadge(this UITabBar tabbar, int index)
{
if (tabbar.Items != null && tabbar.Items.Length == 0) return;
if (index >= tabbar.Items.Length) return;
removeItemBadge(tabbar, index);
var badgeView = new UIView();
badgeView.Tag = tabBarItemTag + index;
badgeView.Layer.CornerRadius = 5;
badgeView.BackgroundColor = UIColor.Red;
var tabFrame = tabbar.Frame;
var percentX = (index + 0.56) / tabbar.Items.Length;
var x = percentX * tabFrame.Width;
var y = tabFrame.Height * 0.1;
badgeView.Frame = new CoreGraphics.CGRect(x, y, 10, 10);
tabbar.AddSubview(badgeView);
}
public static bool removeItemBadge(this UITabBar tabbar, int index)
{
foreach(var subView in tabbar.Subviews)
{
if(subView.Tag == tabBarItemTag + index)
{
subView.RemoveFromSuperview();
return true;
}
}
return false;
}
}
}
Android Solution
Add a xml in drawable folder called Reddot.xml .
<?xml version="1.0" encoding="utf-8" ?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<solid android:color="#ff0000"/>
<size android:width="12dp" android:height="12dp"/>
</shape>
Add a xml in drawable folder called MyView.xml .
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button android:background="#drawable/Reddot"
android:layout_width="12dp"
android:layout_height="12dp"/>
</LinearLayout>
3.custom renderer
[assembly: ExportRenderer(typeof(TabbedPage), typeof(CustomTabbedPageRenderer))]
namespace MyForms.Droid
{
public class CustomTabbedPageRenderer : TabbedPageRenderer
{
List<Android.Views.View> list = new List<Android.Views.View>();
public CustomTabbedPageRenderer(Context context) : base(context)
{
for (int i = 0; i < 3;i++)
{
Android.Views.View view = LayoutInflater.From(Context).Inflate(Resource.Drawable.MyView, null);
list.Add(view);
}
}
protected override void OnElementChanged(Xamarin.Forms.Platform.Android.ElementChangedEventArgs<Xamarin.Forms.TabbedPage> e)
{
base.OnElementChanged(e);
if (e.NewElement == null || e.OldElement != null)
return;
MessagingCenter.Subscribe<object, int>(this, "Add", (obj, index) => {
TabLayout tablayout = (TabLayout)ViewGroup.GetChildAt(1);
ViewGroup vgroup = (ViewGroup)tablayout.GetChildAt(0);
for (int i = 0; i < vgroup.ChildCount; i++)
{
if(index == i)
{
var view = list[i];
if (view.Parent != null) break;
ViewGroup vvgroup = (ViewGroup)vgroup.GetChildAt(i);
MarginLayoutParams layout1 = new MarginLayoutParams(MarginLayoutParams.MatchParent, MarginLayoutParams.MatchParent);
layout1.SetMargins(120, -135, 0, 0);
vvgroup.AddView(view,layout1);
var ll = vvgroup.LayoutParameters as LinearLayout.LayoutParams;
ll.SetMargins(0, 30, 0,0 );
vvgroup.LayoutParameters = ll;
}
}
});
MessagingCenter.Subscribe<object, int>(this, "Remove", (obj, index) => {
TabLayout tablayout = (TabLayout)ViewGroup.GetChildAt(1);
ViewGroup vgroup = (ViewGroup)tablayout.GetChildAt(0);
for (int i = 0; i < vgroup.ChildCount; i++)
{
if(index == i)
{
var view = list[i];
if (view.Parent == null) break;
ViewGroup vvgroup = (ViewGroup)vgroup.GetChildAt(i);
vvgroup.RemoveView(view);
var ll = vvgroup.LayoutParameters as LinearLayout.LayoutParams;
ll.SetMargins(0, 0, 0, 0);
vvgroup.LayoutParameters = ll;
}
}
});
}
}
}
Usage in Forms project
//add red dot
MessagingCenter.Send<object, int>(this, "Add", 1);
//remove red dot
MessagingCenter.Send<object, int>(this, "Remove", 1);
Screen shot
Related
I'm wondering about the Custom TabbedPageRenderer issue. I have created a file MyTabbedPageRenderer.cs in Xamarin iOS. Everything I set up in this is fine. Until I used Plugin.Badge. I have installed and configured in Project Xamarin iOS: AssemblyInfo.cs add: [assembly: ExportRenderer(typeof(TabbedPage), typeof(BadgedTabbedPageRenderer))].
Yes, It does accept the Badge. However everything I set in: MyTabbedPageRenderer.cs becomes useless.
I try to move what is in BadgedTabbedPageRenderer.cs here into MyTabbedPageRenderer.cs. And I change [assembly: ExportRenderer(typeof(TabbedPage), typeof(BadgedTabbedPageRenderer))] to [assembly: ExportRenderer(typeof(TabbedPage), typeof(MyTabbedPageRenderer))]. However it still doesn't work.
MyTabbedPageRenderer.cs
[assembly: ExportRenderer(typeof(TabbedPage), typeof(MyTabbedPageRenderer))]
namespace xxxx.iOS
{
internal class MyTabbedPageRenderer : TabbedRenderer
{
private bool _initialized;
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
//TabBar.ClipsToBounds = true;
TabBar.TintColor = UIColor.Gray;
TabBar.BarTintColor = UIColor.White;
TabBar.BackgroundColor = UIColor.White;
MessagingCenter.Subscribe<object, int>(this, "Add", (obj, index) => {
TabBar.addItemBadge(index);
});
MessagingCenter.Subscribe<object, int>(this, "Remove", (obj, index) => {
TabBar.removeItemBadge(index);
});
}
public override void ViewWillAppear(bool animated)
{
if (!_initialized)
{
if (TabBar?.Items == null)
return;
foreach (var item in TabBar.Items)
{
item.Image = ScalingImageToSize(item.Image, new CGSize(20, 20)); // set the size here as you want
}
var tabs = Element as TabbedPage;
if (tabs != null)
{
for (int i = 0; i < TabBar.Items.Length; i++)
{
UpdateItem(TabBar.Items[i], tabs.Children[i].Icon, tabs.Children[i].StyleId);
}
}
_initialized = true;
}
base.ViewWillAppear(animated);
}
private void UpdateItem(UITabBarItem item, string icon, string badgeValue)
{
if (item == null) return;
if (UIDevice.CurrentDevice.CheckSystemVersion(13, 0))
{
//change icon select
if (icon.EndsWith(".png"))
icon = icon.Replace(".png", "_selected.png");
else
icon += "_selected";
item.SelectedImage = UIImage.FromBundle(icon);
item.SelectedImage.AccessibilityIdentifier = icon;
item.SelectedImage = ScalingImageToSize(item.SelectedImage, new CGSize(20, 20)); // set the size here as you want
//change icon select
UITabBarAppearance app = new UITabBarAppearance();
app.ConfigureWithOpaqueBackground();
app.BackgroundColor = UIColor.Clear;
app.StackedLayoutAppearance.Normal.TitleTextAttributes = new UIStringAttributes() { Font = UIFont.FromName("Roboto Medium", 12), ForegroundColor = Color.FromHex("#808080").ToUIColor() };
app.StackedLayoutAppearance.Selected.TitleTextAttributes = new UIStringAttributes() { Font = UIFont.FromName("Roboto Medium", 13), ForegroundColor = Color.FromHex("#00AA13").ToUIColor() };
item.StandardAppearance = app;
if (UIDevice.CurrentDevice.CheckSystemVersion(15, 0))
{
item.ScrollEdgeAppearance = item.StandardAppearance;
}
}
}
public UIImage ScalingImageToSize(UIImage sourceImage, CGSize newSize)
{
if (UIScreen.MainScreen.Scale == 2.0) //#2x iPhone 6 7 8
{
UIGraphics.BeginImageContextWithOptions(newSize, false, 2.0f);
}
else if (UIScreen.MainScreen.Scale == 3.0) //#3x iPhone 6p 7p 8p...
{
UIGraphics.BeginImageContextWithOptions(newSize, false, 3.0f);
}
else
{
UIGraphics.BeginImageContext(newSize);
}
sourceImage.Draw(new CGRect(0, 0, newSize.Width, newSize.Height));
UIImage newImage = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();
return newImage;
}
}
public static class TabbarExtensions
{
readonly static int tabBarItemTag = 9999;
public static void addItemBadge(this UITabBar tabbar, int index)
{
if (tabbar.Items != null && tabbar.Items.Length == 0) return;
if (index >= tabbar.Items.Length) return;
removeItemBadge(tabbar, index);
var badgeView = new UIView();
badgeView.Tag = tabBarItemTag + index;
badgeView.Layer.CornerRadius = 3;
badgeView.BackgroundColor = UIColor.Red;
var tabFrame = tabbar.Frame;
var percentX = (index + 0.56) / tabbar.Items.Length;
var x = percentX * tabFrame.Width;
var y = tabFrame.Height * 0.1;
badgeView.Frame = new CoreGraphics.CGRect(x, y, 7, 7);
tabbar.AddSubview(badgeView);
}
public static bool removeItemBadge(this UITabBar tabbar, int index)
{
foreach (var subView in tabbar.Subviews)
{
if (subView.Tag == tabBarItemTag + index)
{
subView.RemoveFromSuperview();
return true;
}
}
return false;
}
}
}
Update
MainView.xaml
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
BackgroundColor="#fff"
xmlns:plugin="clr-namespace:Plugin.Badge.Abstractions;assembly=Plugin.Badge.Abstractions"
xmlns:views="clr-namespace:xxx.Views"
x:Class="xxx.Views.MainView">
<!--Pages can be added as references or inline-->
<views:Page1 Title="Page 1" IconImageSource="homeicon" BackgroundColor="#fff"/>
<views:Page2 Title="Page 2" IconImageSource="feeds" BackgroundColor="#fff"/>
<views:Page3 Title="Page 3" IconImageSource="moneys" BackgroundColor="#fbfbfb" />
<views:Page4 Title="Page 4" IconImageSource="chats" BackgroundColor="#fff" plugin:TabBadge.BadgeColor="Red"
plugin:TabBadge.BadgeText="1"/>
<views:Page5 Title="Page 5" IconImageSource="usericon" BackgroundColor="#fff"/>
</TabbedPage>
Enable Plugin.Badge I added this line in AssemblyInfo.cs: [assembly: ExportRenderer(typeof(TabbedPage), typeof(BadgedTabbedPageRenderer))] ----> Plugin.Badge it works, however everything I set in MyTabbedPageRenderer.cs not working
Everything works fine if I don't use: [assembly: ExportRenderer(typeof(TabbedPage), typeof(BadgedTabbedPageRenderer))]
---> And this is what I want:
Ask for help from everyone. Thanks
I have tested it on my side.The badge effect works well,below is the step that might give you some insights.
As you mentioned, it could be achieved by creating a custom renderer for TabbedPage:
Step1: Move what is in BadgedTabbedPageRenderer.cs here into MyTabbedPageRenderer.cs
Step2: Add [assembly: ExportRenderer(typeof(TabbedPage), typeof(MyTabbedPageRenderer))] in AssemblyInfo.cs and MyTabbedPageRenderer.cs.
Step3: Add two lines in the page you want to achieve the badge effect in the child page.
plugin:TabBadge.BadgeColor="Red"
plugin:TabBadge.BadgeText="1"
I am trying to get the above look on my tabbed bar in xamarin forms, i tried customizing the tabbed bar using renderer and still was not able to get the expected output
output i am getting
till now this is what i have tried
[assembly: ExportRenderer(typeof(BottomNavTabPage), typeof(BottomNavTabPageRenderer))]
namespace HealthMobile.Droid.Renderers
{
public class BottomNavTabPageRenderer : TabbedPageRenderer
{
private bool _isShiftModeSet;
public BottomNavTabPageRenderer(Context context)
: base(context)
{
}
protected override void OnVisibilityChanged(Android.Views.View changedView, [GeneratedEnum] ViewStates visibility)
{
base.OnVisibilityChanged(changedView, visibility);
var tabs = changedView.FindViewById<TabLayout>(Resource.Id.sliding_tabs);
if (tabs != null)
{
ViewGroup vg = (ViewGroup)tabs.GetChildAt(0);
int tabsCount = vg.ChildCount;
}
}
//protected override void DispatchDraw (global::Android.Graphics.Canvas canvas)
// {
// base.DispatchDraw (canvas);
// SetTabIcons();
// // var tabLayout = (TabLayout)GetChildAt(1);
// }
// private void SetTabIcons()
// {
// var element = this.Element;
// if (null == element)
// {
// return;
// }
// Activity activity = this.Context as Activity;
// if ((null != activity) && (null != activity.ActionBar) && (activity.ActionBar.TabCount > 0))
// {
// for (int i = 0; i < element.Children.Count; i += 1)
// {
// var tab = activity.ActionBar.GetTabAt(i);
// var page = element.Children[i];
// if ((null != tab) && (null != page) && (null != page.Icon)
// && (tab.CustomView == null))
// {
// var resourceId = activity.Resources.GetIdentifier(page.Icon.File.ToLowerInvariant(), "drawable", this.Context.PackageName);
// LinearLayout tabHeader
// = new LinearLayout(activity) { Orientation = Orientation.Vertical };
// ImageView tabImg = new ImageView(activity);
// TextView tabTitle = new TextView(activity);
// tabImg.SetImageResource(resourceId);
// tabTitle.Text = page.Title;
// tabTitle.SetTextColor(Android.Graphics.Color.White);
// tabHeader.AddView(tabTitle);
// tabHeader.AddView(tabImg);
// tab.SetCustomView(tabHeader);
// }
// }
// }
// }
protected override void OnElementChanged(ElementChangedEventArgs<TabbedPage> e)
{
base.OnElementChanged(e);
var childViews = GetAllChildViews(ViewGroup);
//tab.SetIcon(Resource.Drawable.icon);
var scale = Resources.DisplayMetrics.Density;
var paddingDp = 0;
var dpAsPixels = (int)(paddingDp * scale + 0.5f);
foreach (var childView in childViews)
{
if (childView is BottomNavigationItemView tab)
{
//tab.SetPadding(-50, -100, -50, -100);
}
else if (childView is TextView textView)
{
textView.SetTextColor(Android.Graphics.Color.Transparent);
}
}
}
protected override void SetTabIcon(TabLayout.Tab tab, FileImageSource icon)
{
base.SetTabIcon(tab, icon);
}
protected override void OnLayout(bool changed, int l, int t, int r, int b)
{
base.OnLayout(changed, l, t, r, b);
try
{
if (!_isShiftModeSet)
{
var children = GetAllChildViews(ViewGroup);
if (children.SingleOrDefault(x => x is BottomNavigationView) is BottomNavigationView bottomNav)
{
bottomNav.SetShiftMode(false, false);
_isShiftModeSet = true;
}
}
}
catch (Exception e)
{
Console.WriteLine($"Error setting ShiftMode: {e}");
}
}
private List<View> GetAllChildViews(View view)
{
if (!(view is ViewGroup group))
{
return new List<View> {view };
}
var result = new List<View>();
for (int i = 0; i < group.ChildCount; i++)
{
var child = group.GetChildAt(i);
var childList = new List<View> {child};
childList.AddRange(GetAllChildViews(child));
result.AddRange(childList);
}
return result.Distinct().ToList();
}
}
}
i am trying to make this look like this somewhat
output expecting
also i tried setting up the icons but SetTabIcons method never get triggered
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.
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 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;
}