alexrainman / CarouselView

CarouselView control for Xamarin Forms
MIT License
437 stars 177 forks source link

Label/Button Height #1

Closed JoshuaNovak919 closed 8 years ago

JoshuaNovak919 commented 8 years ago

Why is the Label/Button HeightRequest required? It makes it very difficult to use the CarouselViews when you are required to manually set or calculate all the heights.

alexrainman commented 8 years ago

Hi Joshua. I still need to figure it out how to propagate request layout down to each children. Stay tuned. Thanks

alexrainman commented 8 years ago

By the way, you dont need to specify HeightRequest for everything. For example grid row * and Auto works. Layouts FillAndExpand works. Padding, spacing properties works. Only some elements like Label, Button, BoxView needs HeightRequest...

alexrainman commented 8 years ago

Label HeightRequest = to their font size :)

JoshuaNovak919 commented 8 years ago

@alexrainman Yeah I tried that, but depending on the font, it isn't exactly the right size. I got that working by manually setting it up. It seems that Orientation="Horizontal" on StackLayouts doesn't work and Positioning in an AbsoluteLayout doesn't work either.

alexrainman commented 8 years ago

Its possible to calculate the height using font size and the amount of text to place. I will do my testing based on your comments. Thanks

alexrainman commented 8 years ago

J, you are right, Horizontal StackLayout doesn't works and no idea why :) you can use a two column Grid instead.

alexrainman commented 8 years ago

I tested AbsoluteLayout and it works like a charm :)

alexrainman commented 8 years ago

And RelativeLayout works too.

JoshuaNovak919 commented 8 years ago

@alexrainman AbsoluteLayout works depending on what position you use. If you use AutoSize for height or width then it won't work. I'm using PositionProportional and I had to define height and width for all the elements to get it to work.

alexrainman commented 8 years ago

That's the problem, AutoSize doesn't works. As i said, you must request height 'til i find the way of requesting layout to all children. This the first version of the Control and it took me while to understand the obscure non-documented Xamarin.Forms RenderFactory API. I will share with you a way to calculate label height in a cross-platform way :)

JoshuaNovak919 commented 8 years ago

@alexrainman I figured out a way to calculate the height by just adding a few pixels to the font size, but the issue is calculating the width, it is quite difficult.

alexrainman commented 8 years ago

This is what i use to calculate the width:

var width = Navigation.NavigationStack.First().Width - x;

x = sum of padding from left and right sides. If you have columns, then just do the math :)

alexrainman commented 8 years ago

Then to calculate height use this:

public interface ITextMeter
{
    double MeasureTextSize(string text, double width, double fontSize, string fontName = null);
}
[assembly: Xamarin.Forms.Dependency(typeof(TextMeterImplementation))]
namespace YourNamespace.iOS
{
    public class TextMeterImplementation : ITextMeter
    {
        //public static Xamarin.Forms.Size MeasureTextSize(string text, double width, double fontSize, string fontName = null)
        public double MeasureTextSize(string text, double width, double fontSize, string fontName = null)
        {
            var nsText = new NSString(text);
            var boundSize = new SizeF((float)width, float.MaxValue);
            var options = NSStringDrawingOptions.UsesFontLeading | NSStringDrawingOptions.UsesLineFragmentOrigin;

            if (fontName == null)
            {
                fontName = "HelveticaNeue";
            }

            var attributes = new UIStringAttributes {
                Font = UIFont.FromName(fontName, (float)fontSize)
            };

            var sizeF = nsText.GetBoundingRect(boundSize, options, attributes, null).Size;

            //return new Xamarin.Forms.Size((double)sizeF.Width, (double)sizeF.Height);
            return (double)sizeF.Height;
        }
    }
}
[assembly: Xamarin.Forms.Dependency(typeof(TextMeterImplementation))]
namespace YourNamespace.Droid
{
    public class TextMeterImplementation : ITextMeter
    {
        private Typeface textTypeface;

        public double MeasureTextSize(string text, double width, double fontSize, string fontName = null) {

            var paint = new TextPaint(PaintFlags.AntiAlias | PaintFlags.SubpixelText);
            paint.TextSize = (float)fontSize;
            paint.SetTypeface(GetTypeface(fontName));

            int lineCount = 0;

            int index = 0;
            int length = text.Length;

            while(index < length - 1) {
                index += paint.BreakText(text, index, length, true, (float)width, null);
                lineCount++;
            }

            var bounds = new Rect();
            paint.GetTextBounds(text, 0, length, bounds);

            return (double)lineCount * bounds.Height() + 3;
        }

        private Typeface GetTypeface(string fontName)
        {
            if (fontName == null)
            {
                return Typeface.Default;
            }

            if (textTypeface == null)
            {
                textTypeface = Typeface.Create(fontName, TypefaceStyle.Normal);
            }

            return textTypeface;
        }
    }
}
alexrainman commented 8 years ago

I found a fix for this. Expect version 2 soon :)

lwinged commented 8 years ago

Hi, I tested your fix and I left a comment.

Can you help me ?

https://gist.github.com/alexrainman/82b00160ab32bef9e69dee6d460f44fa#comments

Thank you

alexrainman commented 8 years ago

Can you use CVLabel custom control provided by the plugin and let me know if height is correct? Thats what i advice, use CVLabel and for the rest of controls provide HeightRequest with OnPlatform using the default ones.

lwinged commented 8 years ago

I've just use your TextMeterImplementation with Xamarin Label. It works on iOS but not on Android.

Now It works on Android for me. I fixed my issue with this :

public float GetDensity()
{
    return global::Android.App.Application.Context.Resources.DisplayMetrics.Density;
}

public double MeasureTextSize(string text, double width, double fontSize, string fontName = null)
{
            var textView = new TextView(global::Android.App.Application.Context);
            textView.Typeface = GetTypeface(fontName);
            textView.SetText(text, TextView.BufferType.Normal);
            textView.SetTextSize(ComplexUnitType.Dip, (float)fontSize);

            int widthMeasureSpec = Android.Views.View.MeasureSpec.MakeMeasureSpec(
                (int)width, MeasureSpecMode.AtMost);
            int heightMeasureSpec = Android.Views.View.MeasureSpec.MakeMeasureSpec(
                0, MeasureSpecMode.Unspecified);

            textView.Measure(widthMeasureSpec, heightMeasureSpec);

            var density = global::Android.App.Application.Context.Resources.DisplayMetrics.Density;

            return ((double)textView.MeasuredHeight / density) + 5;
 }

Example :

var density = textMeter.getDensity() -> 1 for iOS and global::Android.App.Application.Context.Resources.DisplayMetrics.Density for Android

Height = (int)textMeter.MeasureTextSize(alert.CurrentText, (App.ScreenWidth / nbColumns) - 30 * density, 15.0, "Roboto-Regular");

EDIT: 09/01/2016

I didn't work when I changed textsize in android settings (display section) because I used Dip instead of Sp (recommended for text size)

but this work for me now :

public float GetDensity()
{
    return global::Android.App.Application.Context.Resources.DisplayMetrics.Density;
}

public double MeasureTextSize(string text, double width, double fontSize, string fontName = null)
{
            var textView = new TextView(global::Android.App.Application.Context);
            textView.Typeface = GetTypeface(fontName);
            textView.SetText(text, TextView.BufferType.Normal);
            textView.SetTextSize(ComplexUnitType.Sp, (float)fontSize);

            int widthMeasureSpec = Android.Views.View.MeasureSpec.MakeMeasureSpec(
                (int)width, MeasureSpecMode.AtMost);
            int heightMeasureSpec = Android.Views.View.MeasureSpec.MakeMeasureSpec(
                0, MeasureSpecMode.Unspecified);

            textView.Measure(widthMeasureSpec, heightMeasureSpec);

            var density = global::Android.App.Application.Context.Resources.DisplayMetrics.Density;

            return ((double)textView.MeasuredHeight / density) + 5;
 }
alexrainman commented 8 years ago

It doesn't works for me :(

This is what i use internally in TextMeter Android:

        private Typeface textTypeface;

        public double MeasureTextSize(string text, double width, double fontSize, string fontName = null)
        {
            var textView = new TextView(global::Android.App.Application.Context);
            textView.Typeface = GetTypeface(fontName);
            textView.SetText(text, TextView.BufferType.Normal);
            textView.SetTextSize(ComplexUnitType.Px, (float)fontSize);

            int widthMeasureSpec = AViews.View.MeasureSpec.MakeMeasureSpec(
                (int)width, AViews.MeasureSpecMode.AtMost);
            int heightMeasureSpec = AViews.View.MeasureSpec.MakeMeasureSpec(
                0, AViews.MeasureSpecMode.Unspecified);

            textView.Measure(widthMeasureSpec, heightMeasureSpec);

            return (double)textView.MeasuredHeight;
        }

        private Typeface GetTypeface(string fontName)
        {
            if (fontName == null)
            {
                return Typeface.Default;
            }

            if (textTypeface == null)
            {
                textTypeface = Typeface.Create(fontName, TypefaceStyle.Normal);
            }

            return textTypeface;
        }

CVLabel.cs

    public class CVLabel : Label
    {
        public CVLabel()
        {
            SetBinding(Label.HeightRequestProperty, new Binding("WidthRequest", BindingMode.Default, new LabelHeightConverter(), this, null, this));
        }

        protected override void OnSizeAllocated(double width, double height)
        {
            base.OnSizeAllocated(width, height);

            WidthRequest = width;

            this.LayoutTo(new Rectangle(this.X, this.Y, width, height));

            this.InvalidateMeasure();
        }
    }

And it works with your text or any other.

fgiacomelli commented 7 years ago

Hi, I'm using your implementation because I want to see how much big are my labels (I need to know the measure of my text in pixels). I see that when I set a fontSize of 20 I have i.e. 268 as measured size. What's the measurement unit? Thank you

alexrainman commented 7 years ago

If my work is helping you, please help me back: https://xamarinhq.wufoo.com/forms/nominate-a-xamarin-mvp/

alexrainman commented 7 years ago

This is what i have done that is community visible: