How can I set up different footers for TableSections when using a Custom TableView Renderer - xamarin

I am using a renderer to allow me to set a custom footer in my TableView. The renderer works but I would like to have the capability to set up different footers for the different table sections. For example one footer for table section 0 and another for table section 1, all the way up to table section 5.
Here's the XAML that I am using:
<!-- <local:ExtFooterTableView x:Name="tableView" Intent="Settings" HasUnevenRows="True">-->
<TableView x:Name="tableView" Intent="Settings" HasUnevenRows="True">
<TableSection Title="Cards1">
<ViewCell Height="50">
<Label Text="Hello1" />
</ViewCell>
<ViewCell Height="50">
<Label Text="Hello2" />
</ViewCell>
</TableSection>
<TableSection Title="Cards2">
<TextCell Height="50" Text="Hello"></TextCell>
</TableSection>
</TableSection>
<!-- </local:ExtFooterTableView>-->
</TableView>
and here is the C# class and renderer:
public class ExtFooterTableView : TableView
{
public ExtFooterTableView()
{
}
}
and:
using System;
using Japanese;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(ExtFooterTableView), typeof(Japanese.iOS.ExtFooterTableViewRenderer))]
namespace Japanese.iOS
{
public class ExtFooterTableViewRenderer : TableViewRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<TableView> e)
{
base.OnElementChanged(e);
if (Control == null)
return;
var tableView = Control as UITableView;
var formsTableView = Element as TableView;
tableView.WeakDelegate = new CustomFooterTableViewModelRenderer(formsTableView);
}
private class CustomFooterTableViewModelRenderer : TableViewModelRenderer
{
public CustomFooterTableViewModelRenderer(TableView model) : base(model)
{
}
public override UIView GetViewForFooter(UITableView tableView, nint section)
{
Debug.WriteLine("xx");
if (section == 0)
{
return new UILabel()
{
// Text = TitleForFooter(tableView, section), // or use some other text here
Text = "abc",
TextAlignment = UITextAlignment.Left
// TextAlignment = NSTextAlignment.NSTextAlignmentJustified
};
}
else
{
return new UILabel()
{
// Text = TitleForFooter(tableView, section), // or use some other text here
Text = "def",
TextAlignment = UITextAlignment.Left
// TextAlignment = NSTextAlignment.NSTextAlignmentJustified
};
}
}
}
}
}
The code works but I would like to find out how I can set up a different footer text for different sections in the XAML. Something like this:
From what I see it looks like the code is partly there TitleForFooter(tableView, section) but I am not sure how to use it and how I could set it up. Note that I am not really looking for a view model solution. I would be happy to be simply able to specify the section footer text as part of the TableView XAML.
I'd appreciate if anyone could give me some advice on this.

First of all, in order to be able to specify the section footer text in XAML - simplest option would be to create a bindable property in TableSection. But as TableSection is sealed, we can't derive it to define our custom bindable properties.
So, the next option is to create a attached bindable property.
public class Ex
{
public static readonly BindableProperty FooterTextProperty =
BindableProperty.CreateAttached("FooterText", typeof(string), typeof(Ex), defaultValue: default(string));
public static string GetFooterText(BindableObject view)
{
return (string)view.GetValue(FooterTextProperty);
}
public static void SetFooterText(BindableObject view, string value)
{
view.SetValue(FooterTextProperty, value);
}
}
Next step would be to update renderer to retrieve this value for every section:
private class CustomFooterTableViewModelRenderer : TableViewModelRenderer
{
public CustomFooterTableViewModelRenderer(TableView model) : base(model)
{
}
public override UIView GetViewForFooter(UITableView tableView, nint section)
{
return new UILabel()
{
Text = TitleForFooter(tableView, section), // or use some other text here
Font = UIFont.SystemFontOfSize(14),
ShadowColor = Color.White.ToUIColor(),
ShadowOffset = new CoreGraphics.CGSize(0, 1),
TextColor = Color.DarkGray.ToUIColor(),
BackgroundColor = Color.Transparent.ToUIColor(),
Opaque = false,
TextAlignment = UITextAlignment.Center
};
}
//Retrieves the footer text for corresponding section through the attached property
public override string TitleForFooter(UITableView tableView, nint section)
{
var tblSection = View.Root[(int)section];
return Ex.GetFooterText(tblSection);
}
}
Sample Usage
<local:ExtFooterTableView x:Name="tableView" Intent="Settings" HasUnevenRows="True">
<TableSection Title="Cards1" local:Ex.FooterText="Sample description">
<ViewCell Height="50">
<Label Margin="20,0,20,0" Text="Hello1" />
</ViewCell>
<ViewCell Height="50">
<Label Margin="20,0,20,0" Text="Hello2" />
</ViewCell>
</TableSection>
<TableSection Title="Cards2" local:Ex.FooterText="Disclaimer note">
<TextCell Height="50" Text="Hello"></TextCell>
</TableSection>
</local:ExtFooterTableView>

It is very simple. you need to add the bindable property for pass value from XAML to CustomRenderer in CustomControl like this:
Customer TableView
public class ExtFooterTableView : TableView
{
public ExtFooterTableView()
{
}
}
Xaml control code
<local:ExtFooterTableView x:Name="tableView" Intent="Settings" HasUnevenRows="True">
Renderer class
using System;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using yournamespace;
using System.ComponentModel;
[assembly: ExportRenderer(typeof(ExtFooterTableView), typeof(FooterTableViewRenderer))]
namespace yournamespace
{
public class FooterTableViewRenderer : TableViewRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<TableView> e)
{
base.OnElementChanged(e);
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
var view = (ExtFooterTableView)Element;
if (e.PropertyName == ExtFooterTableView.IntentProperty.PropertyName)
{
string intent = view.Intent;
// Do your stuff for intent property
}
if (e.PropertyName == ExtFooterTableView.HasUnevenRowsProperty.PropertyName)
{
bool hasUnevenRows = view.HasUnevenRows;
// Do yout stuff for HasUnevenRow
}
}
}
}

Related

Xamarin IOS Custom Renderer overriden Draw method not called

I am trying to load a customized slider control in a listview (with accordeon behaviour). When the View loads all the listview elements are collapsed so the slider control visibility is false. I observed that the overriden Draw method within the ios renderer is not called while the control is not visible so I end up having the native control within my listview.
I have reproduced the issue in a separate project:
I have the IOS custom renderer:
public class CustomGradientSliderRenderer : SliderRenderer
{
public CGColor StartColor { get; set; }
public CGColor CenterColor { get; set; }
public CGColor EndColor { get; set; }
protected override void OnElementChanged(ElementChangedEventArgs<Slider> e)
{
if (Control == null)
{
var customSlider = e.NewElement as CustomGradientSlider;
StartColor = customSlider.StartColor.ToCGColor();
CenterColor = customSlider.CenterColor.ToCGColor();
EndColor = customSlider.EndColor.ToCGColor();
var slider = new SlideriOS
{
Continuous = true,
Height = (nfloat)customSlider.HeightRequest
};
SetNativeControl(slider);
}
base.OnElementChanged(e);
}
public override void Draw(CGRect rect)
{
base.Draw(rect);
if (Control != null)
{
Control.SetMinTrackImage(CreateGradientImage(rect.Size), UIControlState.Normal);
}
}
void OnControlValueChanged(object sender, EventArgs eventArgs)
{
((IElementController)Element).SetValueFromRenderer(Slider.ValueProperty, Control.Value);
}
public UIImage CreateGradientImage(CGSize rect)
{
var gradientLayer = new CAGradientLayer()
{
StartPoint = new CGPoint(0, 0.5),
EndPoint = new CGPoint(1, 0.5),
Colors = new CGColor[] { StartColor, CenterColor, EndColor },
Frame = new CGRect(0, 0, rect.Width, rect.Height),
CornerRadius = 5.0f
};
UIGraphics.BeginImageContext(gradientLayer.Frame.Size);
gradientLayer.RenderInContext(UIGraphics.GetCurrentContext());
var image = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();
return image.CreateResizableImage(UIEdgeInsets.Zero);
}
}
public class SlideriOS : UISlider
{
public nfloat Height { get; set; }
public override CGRect TrackRectForBounds(CGRect forBounds)
{
var rect = base.TrackRectForBounds(forBounds);
return new CGRect(rect.X, rect.Y, rect.Width, Height);
}
}
The View with codebehind:
Main.xaml:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
x:Class="GradientSlider.MainPage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:GradientSlider">
<ContentPage.Content>
<Grid>
<StackLayout x:Name="SliderContainer">
<local:CustomGradientSlider
x:Name="mySlider"
CenterColor="#feeb2f"
CornerRadius="16"
EndColor="#ba0f00"
HeightRequest="20"
HorizontalOptions="FillAndExpand"
Maximum="10"
Minimum="0"
StartColor="#6bab29"
VerticalOptions="CenterAndExpand"
MaximumTrackColor="Transparent"
ThumbColor="green"
/>
<Label x:Name="lblText" Text="txt"
VerticalOptions="Center" HorizontalOptions="Center"/>
</StackLayout>
<Button Text="Magic" Clicked="Button_Tapped" WidthRequest="100" HeightRequest="50" VerticalOptions="Center" HorizontalOptions="Center"/>
</Grid>
</ContentPage.Content>
</ContentPage>
Main.xaml.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace GradientSlider
{
public partial class MainPage : ContentPage, INotifyPropertyChanged
{
public MainPage()
{
InitializeComponent();
SliderContainer.IsVisible = false;
}
void Button_Tapped(object sender,ClickedEventArgs a)
{
SliderContainer.IsVisible = !SliderContainer.IsVisible;
}
}
}
So in the scenario above you can see that when I load the main.xaml the control is invisible (SliderContainer.IsVisible = false;) in this case I get a native slider control and not my custom one. If I change in the constructor SliderContainer.IsVisible = true; then I get my custom control.
After an investigation I realised that if the control is not visible when the view loads the public override void Draw(CGRect rect) is not called. I could not find any solution to trigger the Draw method while the control is invisible.
Anybody has an idea how to load a custom renderer correctly while the control is not visible ?
Thank you!
Assuming the renderer is overriding OnElementPropertyChanged:
protected override void OnElementChanged(ElementChangedEventArgs<MyFormsSlider> e)
{
if (e.NewElement != null)
{
if (Control == null)
{
// Instantiate the native control and assign it to the Control property with
// the SetNativeControl method
SetNativeControl(new MyNativeControl(...
...
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
//assuming MyFormsSlider derives from View / VisualElement; the latter has IsVisibleProperty
if (e.PropertyName == MyFormsSlider.IsVisibleProperty.PropertyName)
{
//Control is the control set with SetNativeControl
Control. ...
}
...
}

How to create a Xamarin Tooltip in code-behind

I am testing using the following example. https://github.com/CrossGeeks/TooltipSample
The sample works fine, it even works with Labels (sample uses buttons, images and boxviews). The issue is in my main App I need to create the tooltips in code behind.
To test how to do it, in the very same solution (from that above example) I created a TestPage and made it my MainPage in App.xaml.cs. The XAML looks like this:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ToolTipSample.TestPage">
<ContentPage.Content>
<StackLayout
x:Name="mainLayout"
BackgroundColor="Yellow">
<StackLayout.GestureRecognizers>
<TapGestureRecognizer Tapped="Handle_Tapped"/>
</StackLayout.GestureRecognizers>
</StackLayout>
</ContentPage.Content>
The code-behind looks like this:
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using ToolTipSample.Effects;
namespace ToolTipSample
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class TestPage : ContentPage
{
public TestPage()
{
InitializeComponent();
var actionLabel = new Label
{
Text = "Show Tooltip",
WidthRequest = 150,
VerticalOptions = LayoutOptions.StartAndExpand,
HorizontalOptions = LayoutOptions.Center,
BackgroundColor = Color.Wheat
};
// Add tooltip to action label
TooltipEffect.SetPosition(actionLabel, TooltipPosition.Bottom);
TooltipEffect.SetBackgroundColor(actionLabel, Color.Silver);
TooltipEffect.SetTextColor(actionLabel, Color.Teal);
TooltipEffect.SetText(actionLabel, "This is the tooltip");
TooltipEffect.SetHasTooltip(actionLabel, true);
actionLabel.Effects.Add(Effect.Resolve($"CrossGeeks.{nameof(TooltipEffect)}"));
mainLayout.Children.Add(actionLabel);
}
void Handle_Tapped(object sender, System.EventArgs e)
{
foreach (var c in mainLayout.Children)
{
if (TooltipEffect.GetHasTooltip(c))
{
TooltipEffect.SetHasTooltip(c, false);
TooltipEffect.SetHasTooltip(c, true);
}
}
}
}
}
All other code unchanged.
When I tap the label, the tooltip appears as expected. But when I tap the background it does not disappear (like those created in XAML in the sample).
One other thing. If I tap twice it disappears.
Can anyone see what I am missing?
Thanks.
According to your description and code, you can delete the following line code to achieve your requirement.
actionLabel.Effects.Add(Effect.Resolve($"CrossGeeks.{nameof(TooltipEffect)}"));
You don't need to add effect for control when page load, because this effect will be added when you click this control by these code:
static void OnHasTooltipChanged(BindableObject bindable, object oldValue, object newValue)
{
var view = bindable as View;
if (view == null)
{
return;
}
bool hasTooltip = (bool)newValue;
if (hasTooltip)
{
view.Effects.Add(new ControlTooltipEffect());
}
else
{
var toRemove = view.Effects.FirstOrDefault(e => e is ControlTooltipEffect);
if (toRemove != null)
{
view.Effects.Remove(toRemove);
}
}
}

Is there a way to center the page title on Android when using Xamarin Forms Shell?

I recently changed to Xamarin Forms and notice that the title isn't centered at the top of the page for Android devices.
Is there a way that I can do this?
Here's an example of what I mean with the title:
You can use the TitleView:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:TitleViewSample"
x:Class="TitleViewSample.MainPage">
<NavigationPage.TitleView>
<Label Text="Hello World" HorizontalTextAlignement="Center"/>
</NavigationPage.TitleView>
<ContentPage.Content>
<StackLayout>
<!-- Place new controls here -->
<Label Text="Welcome to Xamarin.Forms!"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage.Content>
</ContentPage>
https://www.andrewhoefling.com/Blog/Post/xamarin-forms-title-view-a-powerful-navigation-view
You will have to implement ShellRenderer in this case as you have Xamarin.Forms Shell Project.
using System;
using System.Collections.Specialized;
using System.ComponentModel;
using Android.App;
using Android.Content;
using Android.Content.Res;
using Android.Support.V4.Widget;
using Android.Support.V7.Widget;
using Android.Util;
using Android.Widget;
using Japanese.Droid.CustomRenderers;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Toolbar = Android.Support.V7.Widget.Toolbar;
[assembly: ExportRenderer(typeof(Xamarin.Forms.Shell), typeof(MyShellRenderer))]
namespace MyProject.Droid.CustomRenderers
{
public class MyShellRenderer : ShellRenderer
{
public MyShellRenderer(Context context) : base(context)
{
}
protected override IShellToolbarAppearanceTracker CreateToolbarAppearanceTracker()
{
return new MyShellToolbarAppearanceTracker(this);
}
protected override IShellToolbarTracker CreateTrackerForToolbar(Toolbar toolbar)
{
return new MyShellToolbarTracker(this, toolbar, ((IShellContext)this).CurrentDrawerLayout);
}
}
public class MyShellToolbarAppearanceTracker : ShellToolbarAppearanceTracker
{
public MyShellToolbarAppearanceTracker(IShellContext context) : base(context)
{
}
public override void SetAppearance(Android.Support.V7.Widget.Toolbar toolbar, IShellToolbarTracker toolbarTracker, ShellAppearance appearance)
{
base.SetAppearance(toolbar, toolbarTracker, appearance);
//Change the following code to change the icon of the Header back button.
toolbar?.SetNavigationIcon(Resource.Drawable.back);
}
}
public class MyShellToolbarTracker : ShellToolbarTracker
{
public MyShellToolbarTracker(IShellContext shellContext, Toolbar toolbar, DrawerLayout drawerLayout) : base(shellContext, toolbar, drawerLayout)
{
}
protected override void UpdateTitleView(Context context, Toolbar toolbar, View titleView)
{
base.UpdateTitleView(context, toolbar, titleView);
for (int index = 0; index < toolbar.ChildCount; index++)
{
if (toolbar.GetChildAt(index) is TextView)
{
var title = toolbar.GetChildAt(index) as TextView;
//Change the following code to change the font size of the Header title.
title.SetTextSize(ComplexUnitType.Sp, 20);
toolbar.SetTitleMargin(MainActivity.displayMetrics.WidthPixels / 4 - Convert.ToInt32(title.TextSize) - title.Text.Length / 2, 0, 0, 0);
}
}
}
}
}
Here is the code for MainActivity.cs
public class MainActivity : FormsAppCompatActivity
{
public static DisplayMetrics displayMetrics;
protected override void OnCreate(Bundle savedInstanceState)
{
Window.AddFlags(WindowManagerFlags.Fullscreen);
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
displayMetrics = new DisplayMetrics();
WindowManager.DefaultDisplay.GetRealMetrics(displayMetrics);
LoadApplication(new App());
if (Window != null) Window.SetStatusBarColor(Android.Graphics.Color.Transparent);
if (isPhone(this)) RequestedOrientation = ScreenOrientation.Portrait;
}
}
As the title textview having wrapped width within toolbar not updating alignment on TextAlignment with center, you can update the layout params of the toolbar to matchparent and textview gravity as follows.
If the hamburger image added with custom image then need check that resoulution if that is too big just reduce it lower one
[assembly: ExportRenderer(typeof(MainPage), typeof(MyRenderer))]//MainPage - navigation page
namespace MyProject.Droid
{
public class MyRenderer: MasterDetailPageRenderer
{
public MyRenderer(Context context) : base(context)
{
}
protected override void OnLayout(bool changed, int l, int t, int r, int b)
{
base.OnLayout(changed, l, t, r, b);
var toolbar = FindViewById<AndroidX.AppCompat.Widget.Toolbar>(Resource.Id.toolbar);
for (var i = 0; i < toolbar.ChildCount; i++)
{
var title = toolbar.GetChildAt(i) as TextView;
if (title != null && !string.IsNullOrEmpty(title.Text))
{
title.TextAlignment = Android.Views.TextAlignment.Center;
title.Gravity = GravityFlags.CenterHorizontal;
var layoutParams = (AndroidX.AppCompat.Widget.Toolbar.LayoutParams)title.LayoutParameters;
layoutParams.Width = ViewGroup.LayoutParams.MatchParent;
toolbar.RequestLayout();
}
}
}
}
}

How can I make a > in a cell with Xamarin.Forms?

I have an application where I can change the order and the way cards appear. For anyone who has iOS I need something very similar to the way the Settings > Contacts > Sort Order page works.
This shows two rows. One with First, Last and the other with Last, First. When a user clicks on a row it acts like a radio button and a tick mark appears at the end of the row.
I would like to try and implement this functionality but I am not sure where to start. Should I do this with a ViewCell or a TextCell and how does anyone have any ideas as to how it is implemented this
. 

EDIT 1: Simplified property changed logic in iOS renderer; now there are no references or handlers to cleanup.
In extension to #hankide's answer:
You can create a bindable property IsChecked while extending a TextCell or ViewCell and bind your VM state to it.
public class MyTextCell : TextCell
{
public static readonly BindableProperty IsCheckedProperty =
BindableProperty.Create(
"IsChecked", typeof(bool), typeof(MyTextCell),
defaultValue: false);
public bool IsChecked
{
get { return (bool)GetValue(IsCheckedProperty); }
set { SetValue(IsCheckedProperty, value); }
}
}
Next step would be to create renderer that listens to this property and shows a check-mark at iOS level.
[assembly: ExportRenderer(typeof(MyTextCell), typeof(SampleApp.iOS.MyTextCellRenderer))]
namespace SampleApp.iOS
{
public class MyTextCellRenderer : TextCellRenderer
{
public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell, UITableView tv)
{
var nativeCell = base.GetCell(item, reusableCell, tv);
var formsCell = item as MyTextCell;
SetCheckmark(nativeCell, formsCell);
return nativeCell;
}
protected override void HandlePropertyChanged(object sender, PropertyChangedEventArgs args)
{
base.HandlePropertyChanged(sender, args);
System.Diagnostics.Debug.WriteLine($"HandlePropertyChanged {args.PropertyName}");
if (args.PropertyName == MyTextCell.IsCheckedProperty.PropertyName)
{
var nativeCell = sender as CellTableViewCell;
if (nativeCell?.Element is MyTextCell formsCell)
SetCheckmark(nativeCell, formsCell);
}
}
void SetCheckmark(UITableViewCell nativeCell, MyTextCell formsCell)
{
if (formsCell.IsChecked)
nativeCell.Accessory = UITableViewCellAccessory.Checkmark;
else
nativeCell.Accessory = UITableViewCellAccessory.None;
}
}
}
Sample usage 1
And, sample usage would like:
<TableView Intent="Settings">
<TableSection Title="Sort Order">
<local:MyTextCell Text="First Last" IsChecked="false" />
<local:MyTextCell Text="Last, First" IsChecked="true" />
</TableSection>
</TableView>
Sample usage 2
You can also listen to Tapped event to ensure IsChecked property works as expected.
For example, you bind this property to ViewModel:
<TableView Intent="Settings">
<TableSection Title="Sort Order">
<local:MyTextCell Tapped="Handle_Tapped" Text="{Binding [0].Name}" IsChecked="{Binding [0].IsSelected}" />
<local:MyTextCell Tapped="Handle_Tapped" Text="{Binding [1].Name}" IsChecked="{Binding [1].IsSelected}" />
</TableSection>
</TableView>
and handle tap event:
public SettingViewModel[] Settings = new []{
new SettingViewModel { Name = "First Last", IsSelected = false },
new SettingViewModel { Name = "Last First", IsSelected = true },
};
void Handle_Tapped(object sender, System.EventArgs e)
{
var cell = sender as TextCell;
if (cell == null)
return;
var selected = cell.Text;
foreach(var setting in Settings)
{
if (setting.Name == selected)
setting.IsSelected = true;
else
setting.IsSelected = false;
}
}
The sort order settings page you described is implemented using the UIKit's UITableView. In Xamarin.Forms, you can utilize the TableView control to get the same result.
As you will quickly notice, there's no way to set the checkmark icon with Xamarin.Forms so you'll probably need to create a custom cell, that has the text on the left and the checkmark image on the right.
If you really want to do everything by the book, you should probably create a custom renderer that allows you to set the Accessory property of the current ViewCell. However, this will get a bit complex for such a small feature.

How to underline label with underline effect in Xamarin Forms?

I followed this tutorial to create underline effect. However, when my page starts it breaks without exception being caught. Has anyone managed to create underline effect? Here is a code:
UnderlineEffect.cs:
namespace XX.CustomForms
{
public class UnderlineEffect : RoutingEffect
{
public const string EffectNamespace = "XX.CustomForms";
public UnderlineEffect() : base($"{EffectNamespace}.{nameof(UnderlineEffect)}")
{
}
}
}
UnderlineLabel_Droid.cs:
[assembly: ResolutionGroupName(UnderlineEffect.EffectNamespace)]
[assembly: ExportEffect(typeof(UnderlineEffect), nameof(UnderlineEffect))]
namespace XX.Droid.Renderers
{
public class UnderlineEffect : PlatformEffect
{
protected override void OnAttached()
{
SetUnderline(true);
}
protected override void OnDetached()
{
SetUnderline(false);
}
protected override void OnElementPropertyChanged(System.ComponentModel.PropertyChangedEventArgs args)
{
base.OnElementPropertyChanged(args);
if (args.PropertyName == Label.TextProperty.PropertyName || args.PropertyName == Label.FormattedTextProperty.PropertyName)
{
SetUnderline(true);
}
}
private void SetUnderline(bool underlined)
{
try
{
var textView = (TextView)Control;
if (underlined)
{
textView.PaintFlags |= PaintFlags.UnderlineText;
}
else
{
textView.PaintFlags &= ~PaintFlags.UnderlineText;
}
}
catch (Exception ex)
{
Console.WriteLine("Cannot underline Label. Error: ", ex.Message);
}
}
}
}
And my xaml:
xmlns:custom="clr-namespace:XX.CustomForms;assembly=XX"
<Label Text="Privacy Notice" HorizontalTextAlignment="Center" >
<Label.Effects>
<custom:UnderlineEffect />
</Label.Effects>
</Label>
Xamarin Forms added a TextDecorations property to Labels. Update to Xamarin Forms 3.3.0+ and just set:
C#
Label label = new Label {
TextDecorations = TextDecorations.Underline
}
XAML
<Label TextDecorations="Underline"/>
Docs Link
Be aware that there was a bug on iOS when an underlined Label is in a ListView. Looks like it has been fixed and released in 3.5.0. I am still using a custom renderer on iOS for now until I am ready to update to the latest version.
GitHub issue
So continue using the iOS effect if you have not updated to XF 3.5.0 yet.
The lengths some people are going to to get underlined text in Xamarin is insane. Here's a way to do it without a thousand line custom renderer. The negative margin trick came from this guy.
<StackLayout HorizontalOptions="Start">
<Label Text="Underlined Text" />
<BoxView HeightRequest="1" BackgroundColor="Purple" HorizontalOptions="FillAndExpand" Margin="0,-7,0,0" />
</StackLayout>
Use TextDecorations property in Label class.
<Label Text="Underlined Text" TextDecorations="Underline"/>
To be able to add an underline to a label, we created custom renderers that inherits from Label.
public class CustomLabel : Label
{
public static readonly BindableProperty IsUnderlinedProperty = BindableProperty.Create("IsUnderlined", typeof(bool), typeof(CustomLabel), false);
public bool IsUnderlined
{
get { return (bool) GetValue(IsUnderlinedProperty); }
set { SetValue(IsUnderlinedProperty, value); }
}
}
In your xaml page you can use it as:
<s:CustomLabel IsUnderlined="True" Text="UnderlinedText" FontSize="18" HorizontalOptions="CenterAndExpand">
Note that s is the namespace declared in the root element of xaml page.
Now your renderer in Android would be something like that:
public class CustomLabelRenderer : LabelRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
if (Control != null && Element != null)
{
if (((CustomLabel)Element).IsUnderlined)
{
Control.PaintFlags = PaintFlags.UnderlineText;
}
}
}
}

Resources