【问题标题】:Custom Font in Xamarin.Forms Label with FormattedString带有 FormattedString 的 Xamarin.Forms 标签中的自定义字体
【发布时间】:2017-02-21 02:15:43
【问题描述】:

我在我的 Android 应用程序中创建了一个自定义 LabelRenderer,以便在 Xamarin Android 应用程序 (https://developer.xamarin.com/guides/xamarin-forms/user-interface/text/fonts/) 中应用自定义字体。

一切都适用于将内容添加到 .Text 属性的普通标签。但是,如果我使用 .FormattedText 属性创建标签,则不会应用自定义字体。

有人成功过吗?一个选项,因为我只是堆叠不同大小的文本行,是为每个使用单独的标签控件,但如果可能的话,我更喜欢使用格式化的字符串。

这是我的自定义渲染器的内容:

[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;
    }
}

这是我的简单标签控件...它所做的只是将字体应用到 iOS,然后将 Android 的字体应用到自定义渲染器。

public class gbrLabel: Label
{
    public gbrLabel ()
    {
        Device.OnPlatform (
            iOS: () => {
                FontFamily = "Lobster-Regular";
                FontSize = Device.GetNamedSize(NamedSize.Medium,this);
            }
    }
}

适用于仅具有 .Text 属性的标签...但不适用于具有 .FormattedText 属性的标签。

我应该继续挖掘,还是只是堆叠我的标签,因为在这种情况下这是一种选择?

这是我在格式化文本中尝试过的各种方法的示例,因为这是要求的:

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
}

这些(第一个应该由默认类/渲染器设置,第二个是在跨度定义本身中包含字体的变体)都不适用于 Android。

【问题讨论】:

  • 没错,它非常相似......不过,没有人对这个问题给出一个好的答案。我以前从来没有问过骗子——我可以把我的问题移到那里来更新一下吗?
  • 你从那个答案中遗漏了什么?您可以为每个跨度设置字体。或者您的目标是在后台为所有标签设置字体,这样您就不必手动设置?
  • 这适用于 iOS,但您不能在 Android 的跨度(或 Xamarin.Forms 中的任何位置)中设置字体。您需要自定义渲染器,但它似乎仅在您使用 .Text 属性时才有效,而不是 .FormattedText 属性,即使您希望所有文本都是相同的字体,只是大小不同。作为旁注,我确实想要所有标签的基本字体,但这很容易实现。这里的问题完全集中在 .FormattedText 属性和为 Android 平台设置字体。
  • 能否请您展示一下您是如何设置 FormattedText 以及格式化文本是什么?

标签: xamarin xamarin.ios xamarin.android


【解决方案1】:

注意: Android 和 iOS 问题已在一篇博文中进行了总结:smstuebe.de/2016/04/03/formattedtext.xamrin.forms/


只要不设置FontSizeFontAttributes,就会设置字体。所以我查看了实现,发现FormattedText 正在尝试加载字体,就像在 Android 上不起作用的默认渲染器一样。

android 格式化系统的工作方式与 Xamarin.Forms 非常相似。它使用跨度来定义文本属性。渲染器使用自定义字体、大小或属性为每个 Span 添加一个 FontSpan。不幸的是,FontSpanclass 是 FormattedStringExtensions 的私有内部类,所以我们必须处理反射。

我们的渲染器在初始化和FormattedText 属性更改时更新Control.TextFormatted。在更新方法中,我们获取所有FontSpans 并将它们替换为我们的CustomTypefaceSpan

渲染器

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

我不确定,为什么你引入了一个新的元素类型gbrLabel,但只要你不想改变渲染器,你就不必创建自定义元素。你可以替换默认元素的渲染器:

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

我们的自定义CustomTypefaceSpan类似于Xamarin.Forms的FontSpan,但是是加载自定义字体,可以为不同的FontAttributes加载不同的字体。

结果是一个漂亮的彩色文本:)

【讨论】:

  • 行:var spans = ss.GetSpans(0, ss.ToString().Length, Class.FromType(type)); 导致我在编译时出错:[...]/FormattedLabelRenderer.cs(60,60): Error CS0176: Static member Java.Lang.Class.FromType(System.Type) cannot be accessed with an instance reference, qualify it with a type name instead (CS0176) (GbrEvents.Droid)。我试图找出这到底意味着什么......也许我错过了 using 语句?
  • Class 也是 Renderer 的一个属性,因为它继承自 Java.Lang.Object。您可以尝试使用完整的限定名Java.Lang.Class.FromType 吗?或使用using Java.Lang;
  • 我已经尝试过使用 Java.Lang; ...没有帮助。但是,添加完全限定的参考可以解决问题……完美!我可能在课堂上有另一个 using 语句,导致它感到困惑。我们很高兴,是时候开始使用字体、大小和颜色了。
  • 我目前正在一篇博文中进行总结……在 iOS 上也发现了一些问题。所以不要超过你的演奏^^
  • 如果你有兴趣,现在发表博文:smstuebe.de/2016/04/03/formattedtext.xamrin.forms/
最近更新 更多