i need to measure the string width and height depending on some style attributes like font, fontsize and fontattributes (bold/italic). How can i do this?
Thank you!
In Android, it provide the getTextBounds to retrieve the text boundary box and store to bounds. Return in bounds.
https://developer.android.com/reference/android/graphics/Paint#getTextBounds(java.lang.CharSequence,%20int,%20int,%20android.graphics.Rect)
In Xamarin.Forms, we could use Dependency Service to do that.
public interface CalculateTextWidth
{
double calculateWidth(string text);
}
Android Implementation:
[assembly: Dependency(typeof(CalculateTextWidth_Android))]
namespace App6.Droid
{
public class CalculateTextWidth_Android : CalculateTextWidth
{
public double calculateWidth(string text)
{
Rect bounds = new Rect();
TextView textView = new TextView(Forms.Context);
textView.Paint.GetTextBounds(text, 0, text.Length, bounds);
var length = bounds.Width();
return length / Resources.System.DisplayMetrics.ScaledDensity;
}
}
}
Usage:
private void Button_Clicked(object sender, EventArgs e)
{
var s = DependencyService.Get<CalculateTextWidth>().calculateWidth(label.Text);
}
I now use SkiaSharps MeasureText Method.
Related
I need to crop the image in elliptical shape, but I do not want to use Ellipse and fill with ImageBrush as mentioned in this link , instead I need the bitmap itself to be in rounded / elliptical instead of rectangular.
Sometimes I would like to crop in rectangular and sometimes in elliptical, so I cannot use Ellipse and fill.
Is there any alternative solution to this? It would also be better if I can clip the Image in elliptical format.
But the Clip in Image accepts only RectangleGeometry.
you can create a custom UserControl and in that use win2d CanvasControl to display image where you can draw image in any shape that you want, using "CreateLayer" funtion to mask the drawing image with shape. for example
at xaml:
<UserControl
x:Class="UWPClassLib.MyImageControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:UWPClassLib"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:win2d="using:Microsoft.Graphics.Canvas.UI.Xaml"
mc:Ignorable="d">
<win2d:CanvasControl x:Name="w2dCanvas"
Draw="w2dCanvas_Draw" />
</UserControl>
at code behind :
public enum MaskShape
{
rectangle,
circle
}
public sealed partial class MyImageControl : UserControl
{
public WriteableBitmap Bitmap
{
get { return (WriteableBitmap)GetValue(BitmapProperty); }
set { SetValue(BitmapProperty, value); }
}
public static readonly DependencyProperty BitmapProperty = DependencyProperty.Register(nameof(Bitmap), typeof(WriteableBitmap), typeof(MyImageControl), new PropertyMetadata(null, OnBitmapPropertyChanged));
private static void OnBitmapPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var myctrl = (MyImageControl)d;
myctrl.TryCreateResource();
}
public MaskShape Shape
{
get { return (MaskShape)GetValue(ShapeProperty); }
set { SetValue(ShapeProperty, value); }
}
public static readonly DependencyProperty ShapeProperty = DependencyProperty.Register(nameof(Shape), typeof(MaskShape), typeof(MyImageControl), new PropertyMetadata(MaskShape.circle));
private CanvasBitmap Source;
public MyImageControl()
{
this.InitializeComponent();
}
public void Invalidate()
{
w2dCanvas.Invalidate();
}
private void w2dCanvas_Draw(Microsoft.Graphics.Canvas.UI.Xaml.CanvasControl sender, Microsoft.Graphics.Canvas.UI.Xaml.CanvasDrawEventArgs args)
{
using (var session = args.DrawingSession)
{
session.Clear(Colors.Transparent);
if (CheckResourceCreated())
{
using (var mask = GetMaskShape())
using (var layer = session.CreateLayer(1.0f, mask))
{
session.DrawImage(Source);
}
// either you can do that or can use
// session.FillGeometry(mask, imagebrush); //and image brush can be made from source e.g.
// imagebrush = new CanvasImageBrush(w2dCanvas, Source);
}
}
}
private CanvasGeometry GetMaskShape()
{
switch (Shape)
{
default:
case MaskShape.circle:
var center = new System.Numerics.Vector2((float)this.ActualWidth / 2, (float)this.ActualHeight / 2);
var radiusX = (float)this.ActualWidth / 2;
var radiusY = (float)this.ActualHeight / 2;
return CanvasGeometry.CreateEllipse(w2dCanvas, center, radiusX, radiusY);
case MaskShape.rectangle:
return CanvasGeometry.CreateRectangle(w2dCanvas, new Rect(0, 0, this.ActualWidth, this.ActualHeight));
}
}
private bool CheckResourceCreated()
{
if (Source == null)
{
TryCreateResource();
}
return (Source != null);
}
private void TryCreateResource()
{
try
{
if (Bitmap == null)
return;
Source = CanvasBitmap.CreateFromBytes(w2dCanvas, Bitmap.PixelBuffer.ToArray(), Bitmap.PixelWidth, Bitmap.PixelHeight, DirectXPixelFormat.B8G8R8A8UIntNormalized);
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
}
and now you can use it any where to display image in rectangle or circle. you just have to change MaskShape and call invalidate
plese forgive any error since, i wrote it on the go and haven't test it.
its just for an idea
I saw this example:
Xamarin Forms - How to create custom render to give TableSection the default iOS Footer?
It does 75% of what I am looking for with this code:
using CoreGraphics;
using Foundation;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(TableView), typeof(Japanese.iOS.TableViewCustomRenderer))]
namespace Japanese.iOS
{
public class TableViewCustomRenderer : TableViewRenderer
{
UITableView tableView;
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);
}
void Current_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
}
private class CustomFooterTableViewModelRenderer : TableViewModelRenderer
{
public CustomFooterTableViewModelRenderer(TableView model) : base(model)
{
}
public override nfloat GetHeightForFooter(UITableView tableView, nint section)
{
return 10;
}
public override string TitleForFooter(UITableView tableView, nint section)
{
return "This is the title for this given section";
}
}
}
}
1. However what I would like is to be able to extend TableView so that I am able to put in the XAML some way to set or leave unset the footer text and height. Something like:
<ExtTableView FooterText="abc" FooterHeight="50". ..
2. From experiments with the code above I tried hardcoding in some text and realize that there is no spacing set. So I would also like to find out if there is a way to set the spacing and font so it appears just like in the iOS settings pages?
Could someone suggest how I could go about creating what I am looking for which is I guess something like an ExtTableView class that can accept additional arguments.
As hankide said , I just provide more details.
However what I would like is to be able to extend TableView so that I am able to put in the XAML some way to set or leave unset the footer text and height.
Create MyTableView that inherits from TableView
public class MyTableView : TableView
{
public static readonly BindableProperty FooterHeightProperty =
BindableProperty.Create("FooterHeight", typeof(string), typeof(MyTableView), "");
public string FooterHeight
{
get { return (string)GetValue(FooterHeightProperty); }
set { SetValue(FooterHeightProperty, value); }
}
public static readonly BindableProperty FooterTextProperty =
BindableProperty.Create("FooterText", typeof(string), typeof(MyTableView), "");
public string FooterText
{
get { return (string)GetValue(FooterTextProperty); }
set { SetValue(FooterTextProperty, value); }
}
}
Get the value that you set in XMAL and assign them to CustomFooterTableViewModelRenderer
protected override void OnElementChanged(ElementChangedEventArgs<TableView> e)
{
base.OnElementChanged(e);
if (Control == null)
return;
var tableView = Control as UITableView;
var formsTableView = Element as MyTableView;
CustomFooterTableViewModelRenderer render = new CustomFooterTableViewModelRenderer(formsTableView);
render.height = float.Parse(formsTableView.FooterHeight);
render.text = formsTableView.FooterText;
tableView.WeakDelegate = render;
}
private class CustomFooterTableViewModelRenderer : TableViewModelRenderer
{
public float height { get; set; }
public String text { get; set; }
public CustomFooterTableViewModelRenderer(TableView model) : base(model)
{
}
public override UIView GetViewForFooter(UITableView tableView, nint section)
{
UIView view = new UIView(new CGRect(0, 0, tableView.Frame.Width, 50));
view.BackgroundColor = UIColor.Gray;
UILabel label = new UILabel();
label.Frame = new CGRect(0, 0, tableView.Frame.Width, height);
label.BackgroundColor = UIColor.Red;
label.Text = text;
label.Font = UIFont.SystemFontOfSize(15);
view.Add(label);
return view;
}
public override nfloat GetHeightForFooter(UITableView tableView, nint section)
{
return 50;
}
}
Usage:
<local:MyTableView FooterHeight="20" FooterText="ABC">
<TableRoot>
<TableSection>
<TextCell Text="22222" ></TextCell>
</TableSection>
</TableRoot>
</local:MyTableView>
From experiments with the code above I tried hardcoding in some text and realize that there is no spacing set. So I would also like to find out if there is a way to set the spacing and font so it appears just like in the iOS settings pages?
You could override the method GetViewForFooter to change the defalut style of footer,find it in the code above .
My test :
You had the right idea about creating the custom control. Here's what to do:
Create ExtTableView class that inherits from TableView
public class ExtTableView : TableView { }
Create BindableProperties for both FooterText and FooterHeight, as outlined here.
After that you can set the properties in XAML
<ExtTableView FooterText="abc" FooterHeight="50" ...
Within the renderer, you can get the values from Element (which points to our Xamarin.Forms ExtTableView).
// Modify the native control with these values
var text = Element.FooterText;
var height = Element.FooterHeight;
Now before anyone ignores this as a duplicate please read till the end. What I want to achieve is this
I've been doing some googling and looking at objective c and swift responses on stackoverflow as well. And this response StackOverFlowPost seemed to point me in the right direction. The author even told me to use ClipsToBounds to clip the subview and ensure it's within the parents bounds. Now here's my problem, if I want to show an image on the right side of the entry(Gender field), I can't because I'm clipping the subview.
For clipping, I'm setting the property IsClippedToBounds="True" in the parent stacklayout for all textboxes.
This is the code I'm using to add the bottom border
Control.BorderStyle = UITextBorderStyle.None;
var myBox = new UIView(new CGRect(0, 40, 1000, 1))
{
BackgroundColor = view.BorderColor.ToUIColor(),
};
Control.AddSubview(myBox);
This is the code I'm using to add an image at the beginning or end of an entry
private void SetImage(ExtendedEntry view)
{
if (!string.IsNullOrEmpty(view.ImageWithin))
{
UIImageView icon = new UIImageView
{
Image = UIImage.FromFile(view.ImageWithin),
Frame = new CGRect(0, -12, view.ImageWidth, view.ImageHeight),
ClipsToBounds = true
};
switch (view.ImagePos)
{
case ImagePosition.Left:
Control.LeftView.AddSubview(icon);
Control.LeftViewMode = UITextFieldViewMode.Always;
break;
case ImagePosition.Right:
Control.RightView.AddSubview(icon);
Control.RightViewMode = UITextFieldViewMode.Always;
break;
}
}
}
After analysing and debugging, I figured out that when OnElementChanged function of the Custom Renderer is called, the control is still not drawn so it doesn't have a size. So I subclassed UITextField like this
public class ExtendedUITextField : UITextField
{
public UIColor BorderColor;
public bool HasBottomBorder;
public override void Draw(CGRect rect)
{
base.Draw(rect);
if (HasBottomBorder)
{
BorderStyle = UITextBorderStyle.None;
var myBox = new UIView(new CGRect(0, 40, Frame.Size.Width, 1))
{
BackgroundColor = BorderColor
};
AddSubview(myBox);
}
}
public void InitInhertedProperties(UITextField baseClassInstance)
{
TextColor = baseClassInstance.TextColor;
}
}
And passed the hasbottomborder and bordercolor parameters like this
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
var view = e.NewElement as ExtendedEntry;
if (view != null && Control != null)
{
if (view.HasBottomBorder)
{
var native = new ExtendedUITextField
{
BorderColor = view.BorderColor.ToUIColor(),
HasBottomBorder = view.HasBottomBorder
};
native.InitInhertedProperties(Control);
SetNativeControl(native);
}
}
But after doing this, now no events fire :(
Can someone please point me in the right direction. I've already built this for Android, but iOS seems to be giving me a problem.
I figured out that when OnElementChanged function of the Custom Renderer is called, the control is still not drawn so it doesn't have a size.
In older versions of Xamarin.Forms and iOS 9, obtaining the control's size within OnElementChanged worked....
You do not need the ExtendedUITextField, to obtain the size of the control, override the Frame in your original renderer:
public override CGRect Frame
{
get
{
return base.Frame;
}
set
{
if (value.Width > 0 && value.Height > 0)
{
// Use the frame size now to update any of your subview/layer sizes, etc...
}
base.Frame = value;
}
}
I have created a custom LabelRenderer in my Android app to apply a custom font in a Xamarin Android app (https://developer.xamarin.com/guides/xamarin-forms/user-interface/text/fonts/).
Everything works great for a normal label with the content added to the .Text property. However, if I create a label using .FormattedText property, the custom font is not applied.
Anyone have success doing this? An option, since I'm just stacking lines of different sized text, is to use separate label controls for each, but I'd prefer to use a formatted string if possible.
Here's the guts of my custom renderer:
[assembly: ExportRenderer (typeof (gbrLabel), typeof (gbrLabelRenderer))]
public class gbrLabelRenderer: LabelRenderer
{
protected override void OnElementChanged (ElementChangedEventArgs<Label> e)
{
base.OnElementChanged (e);
var label = (TextView)Control;
Typeface font = Typeface.CreateFromAsset (Forms.Context.Assets, "Lobster-Regular.ttf");
label.Typeface = font;
}
}
And here's my simple label control... all it does is apply the font to iOS, and leaves applying the font for Android up to the custom renderer.
public class gbrLabel: Label
{
public gbrLabel ()
{
Device.OnPlatform (
iOS: () => {
FontFamily = "Lobster-Regular";
FontSize = Device.GetNamedSize(NamedSize.Medium,this);
}
}
}
Works fine for labels with just the .Text property... but not for labels with the .FormattedText property.
Should I keep digging, or just stack my labels since that's an option in this case?
Here's an example of the various ways I've tried this in the Formatted text, since that was requested:
var fs = new FormattedString ();
fs.Spans.Add (new Span {
Text = string.Format("LINE 1\n",Title),
FontSize = Device.GetNamedSize(NamedSize.Large,typeof(Label))
});
fs.Spans.Add (new Span {
Text = string.Format ("LINE 2\n"),
FontSize = Device.GetNamedSize(NamedSize.Large,typeof(Label)) * 2,
FontAttributes = FontAttributes.Bold,
FontFamily = "Lobster-Regular"
});
fs.Spans.Add (new Span {
Text = string.Format ("LINE 3\n"),
FontSize = Device.GetNamedSize(NamedSize.Medium,typeof(Label)),
FontFamily = "Lobster-Regular.ttf"
});
gbrLabel lblContent = new gbrLabel {
FormattedText = fs
}
None of these (the first should be set by the default class / renderer, and the second 2 are variations of including the font in a span definition itself) work on Android.
Note: Android and iOS issues have been summarized on a blog post: smstuebe.de/2016/04/03/formattedtext.xamrin.forms/
The font is set as long as you do not set FontSize or FontAttributes. So I had the look at the implementation and found that the FormattedText is trying to load the font like the default renderer which doesn't work on Android.
The android formatting system works very similar to that one of Xamarin.Forms. It's using spans to define text attributes. The renderer is adding a FontSpan for every Span with a custom font, size or attribute. Unfortunately, the FontSpanclass is a private inner class of FormattedStringExtensions so we have to deal with reflections.
Our Renderer is updating the Control.TextFormatted on initialization and when the FormattedText property changes. In the update method, we get all FontSpans and replace them with our CustomTypefaceSpan.
Renderer
public class FormattedLabelRenderer : LabelRenderer
{
private static readonly Typeface Font = Typeface.CreateFromAsset(Forms.Context.Assets, "LobsterTwo-Regular.ttf");
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
Control.Typeface = Font;
UpdateFormattedText();
}
private void UpdateFormattedText()
{
if (Element.FormattedText != null)
{
var extensionType = typeof(FormattedStringExtensions);
var type = extensionType.GetNestedType("FontSpan", BindingFlags.NonPublic);
var ss = new SpannableString(Control.TextFormatted);
var spans = ss.GetSpans(0, ss.ToString().Length, Class.FromType(type));
foreach (var span in spans)
{
var start = ss.GetSpanStart(span);
var end = ss.GetSpanEnd(span);
var flags = ss.GetSpanFlags(span);
var font = (Font)type.GetProperty("Font").GetValue(span, null);
ss.RemoveSpan(span);
var newSpan = new CustomTypefaceSpan(Control, font);
ss.SetSpan(newSpan, start, end, flags);
}
Control.TextFormatted = ss;
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == Label.FormattedTextProperty.PropertyName)
{
UpdateFormattedText();
}
}
}
I'm not sure, why you introduced a new element type gbrLabel, but as long as you only wan't to change the renderer, you don't have to create a custom element. You can replace the renderer of the default element:
[assembly: ExportRenderer(typeof(Label), typeof(FormattedLabelRenderer))]
CustomTypefaceSpan
public class CustomTypefaceSpan : MetricAffectingSpan
{
private readonly Typeface _typeFace;
private readonly Typeface _typeFaceBold;
private readonly Typeface _typeFaceItalic;
private readonly Typeface _typeFaceBoldItalic;
private readonly TextView _textView;
private Font _font;
public CustomTypefaceSpan(TextView textView, Font font)
{
_textView = textView;
_font = font;
// Note: we are ignoring _font.FontFamily (but thats easy to change)
_typeFace = Typeface.CreateFromAsset(Forms.Context.Assets, "LobsterTwo-Regular.ttf");
_typeFaceBold = Typeface.CreateFromAsset(Forms.Context.Assets, "LobsterTwo-Bold.ttf");
_typeFaceItalic = Typeface.CreateFromAsset(Forms.Context.Assets, "LobsterTwo-Italic.ttf");
_typeFaceBoldItalic = Typeface.CreateFromAsset(Forms.Context.Assets, "LobsterTwo-BoldItalic.ttf");
}
public override void UpdateDrawState(TextPaint paint)
{
ApplyCustomTypeFace(paint);
}
public override void UpdateMeasureState(TextPaint paint)
{
ApplyCustomTypeFace(paint);
}
private void ApplyCustomTypeFace(Paint paint)
{
var tf = _typeFace;
if (_font.FontAttributes.HasFlag(FontAttributes.Bold) && _font.FontAttributes.HasFlag(FontAttributes.Italic))
{
tf = _typeFaceBoldItalic;
}
else if (_font.FontAttributes.HasFlag(FontAttributes.Bold))
{
tf = _typeFaceBold;
}
else if (_font.FontAttributes.HasFlag(FontAttributes.Italic))
{
tf = _typeFaceItalic;
}
paint.SetTypeface(tf);
paint.TextSize = TypedValue.ApplyDimension(ComplexUnitType.Sp, _font.ToScaledPixel(), _textView.Resources.DisplayMetrics);
}
}
Our Custom CustomTypefaceSpanis similar to the FontSpan of Xamarin.Forms, but is loading the custom fonts and can load different fonts for different FontAttributes.
The result is a nice colorful Text :)
Is there a way to add padding to a label using a custom renderer? I know you can cheat by adding a content view around the label and adding padding to the content view; but I want to keep the UI cleaner and not have to add an extra element.
Just to be clear, I don't want a margin - in other words, if I add a background color to the label, you should see padding between the text and the background of the label, like this:
Have you tried something like this:
namespace CustomFinder.iOS.Renderers
{
public class DataLabelRenderer : LabelRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
if (Control == null)
{
SetNativeControl(new TagUiLabel());
}
base.OnElementChanged(e);
}
}
public class TagUiLabel : UILabel
{
private UIEdgeInsets EdgeInsets { get; set; }
public TagUiLabel()
{
EdgeInsets = new UIEdgeInsets(0, 3, 0, 3);
}
public override void DrawText(CoreGraphics.CGRect rect)
{
base.DrawText(EdgeInsets.InsetRect(rect));
}
}
}
I have this from here
haven't tried it yet.