Xamarin Forms Android Autosize Label TextCompat pre android 8 不自动调整文本大小

Xamarin Forms Android Autosize Label TextCompat pre android 8 doesn't autosize text

我想在我的 xamarin 表单解决方案中利用 android textviews 的自动调整大小功能,以便随着文本长度的增长,字体大小会缩小以永远不会溢出标签的边界,并且不会'不会被截断。为此,我创建了一个自定义 Label 控件并添加了一个 android 自定义渲染器。它在 Android 7 及以下版本中不起作用。它在 Android 8 及更高版本中工作。

根据 the docs autosize support was introduced in android 8, but can be supported back to Android 4 with AppCompat.v4. However, my custom rendered label just renders the default font size in Android pre 8. It works fine in 8+ devices, the label text resizes as needed to not overflow the bounds. 在原生 android 上有类似问题说这可能与不设置宽度和高度有关,我已经尝试明确设置 widthrequest 和 heightrequest 但它没有'不要改变任何东西。同样设置 maxlines=1 不会改变任何东西。另一个线程表明自定义字体是罪魁祸首。我使用默认设备字体创建了一个香草形式的解决方案,并获得了相同的效果。

我的代码:

internal class AutosizeLabelRenderer : LabelRenderer
{
    #region constructor

    public AutosizeLabelRenderer(Context context) : base(context)
    {
    }

    #endregion

    #region overridable

    protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
    {
        base.OnElementChanged(e);

        if (e.NewElement == null || !(e.NewElement is AutoSizeLabel autoLabel) || Control == null) { return; }

        TextViewCompat.SetAutoSizeTextTypeUniformWithConfiguration(Control, autoLabel.AutoSizeMinTextSize,
                autoLabel.AutoSizeMaxTextSize, autoLabel.AutoSizeStepGranularity, (int)ComplexUnitType.Sp);
        
    }

    #endregion
}

public class AutoSizeLabel : Label
{
    public int AutoSizeMaxTextSize
    {
        get => (int)GetValue(AutoSizeMaxTextSizeProperty);
        set => SetValue(AutoSizeMaxTextSizeProperty, value);
    }

    public static readonly BindableProperty AutoSizeMaxTextSizeProperty = BindableProperty.Create(
        nameof(AutoSizeMaxTextSize),        // the name of the bindable property
        typeof(int),     // the bindable property type
        typeof(AutoSizeLabel));      // the default value for the property

    public int AutoSizeMinTextSize
    {
        get => (int)GetValue(AutoSizeMinTextSizeProperty);
        set => SetValue(AutoSizeMinTextSizeProperty, value);
    }

    public static readonly BindableProperty AutoSizeMinTextSizeProperty = BindableProperty.Create(
        nameof(AutoSizeMinTextSize),        // the name of the bindable property
        typeof(int),     // the bindable property type
        typeof(AutoSizeLabel));      // the default value for the property


    public int AutoSizeStepGranularity
    {
        get => (int)GetValue(AutoSizeStepGranularityProperty);
        set => SetValue(AutoSizeStepGranularityProperty, value);
    }

    public static readonly BindableProperty AutoSizeStepGranularityProperty = BindableProperty.Create(
        nameof(AutoSizeStepGranularity),        // the name of the bindable property
        typeof(int),     // the bindable property type
        typeof(AutoSizeLabel));      // the default value for the property

    //
}

不工作:Android 7 - 文本不收缩

按预期工作:Android 8 及以上

Xaml 以上图片:

        <StackLayout HeightRequest="200" WidthRequest="100">
            <Label Text="Fixed width and height, sentences get longer, text should shrink" />
            <controls:AutoSizeLabel
                AutoSizeMaxTextSize="50"
                AutoSizeMinTextSize="8"
                AutoSizeStepGranularity="1"
                BackgroundColor="{StaticResource Shamrock}"
                HeightRequest="40"
                HorizontalOptions="Start"
                MaxLines="1"
                Text="A small sentence"
                WidthRequest="200" />
            <controls:AutoSizeLabel
                AutoSizeMaxTextSize="50"
                AutoSizeMinTextSize="8"
                AutoSizeStepGranularity="1"
                BackgroundColor="{StaticResource Shamrock}"
                HeightRequest="40"
                HorizontalOptions="Start"
                MaxLines="1"
                Text="A larger sentence that shrinks"
                WidthRequest="200" />
            <controls:AutoSizeLabel
                AutoSizeMaxTextSize="50"
                AutoSizeMinTextSize="8"
                AutoSizeStepGranularity="1"
                BackgroundColor="{StaticResource Shamrock}"
                HeightRequest="40"
                HorizontalOptions="Start"
                MaxLines="1"
                Text="An even larger sentence that shrinks more."
                WidthRequest="200" />
        </StackLayout>

TextView字体大小随着控件的大小而变化,这是Android8.0(API26)新增的,因此使用之前的version.You可能会改变TextViewAppCompatTextView

改变你的

protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
  {
     base.OnElementChanged(e);
     if (e.NewElement == null || !(e.NewElement is AutoSizeLabel autoLabel) || Control == null) { return; }
        AppCompatTextView appCompatTextView = new AppCompatTextView(_context);
        appCompatTextView.Text = Element.Text;
        appCompatTextView.SetMaxLines(1);
        SetNativeControl(appCompatTextView);                       
        TextViewCompat.SetAutoSizeTextTypeUniformWithConfiguration(Control,autoLabel.AutoSizeMinTextSize,autoLabel.AutoSizeMaxTextSize, autoLabel.AutoSizeStepGranularity, (int)ComplexUnitType.Sp);
  }

让我完成了大部分工作。我需要采取一些额外的步骤才能使其完全正常工作,因此我将代码作为单独的答案发布在此处。

我的回答与 Leo 的回答不同:

  1. 像 Leo 建议的那样在范围内创建一个新的本机控件意味着它可以工作一段时间但被垃圾收集器处理掉并在导航后 return 转到页面时导致异常离开。要解决此问题,我需要将名为 ManageNativeControlLifetime 的 属性 覆盖为 return false,然后通过覆盖 dispose 方法并调用 Control.RemoveFromParent(); 手动管理对象的处置。此建议来自 this thread.

    的 xamarin 工作人员
  2. 格式化和绑定上下文在创建新的原生控件时不会自动继承,需要手动设置。我需要使用 android 特定的绑定语法根据我的需要添加这些。您可能需要根据需要添加其他格式和绑定代码,我这里只是做字体颜色、引力和绑定上下文。

我设置了绑定上下文 appCompatTextView.SetBindingContext(autoLabel.BindingContext);

  1. 设置绑定上下文后,我需要向我的 XF AutoSizeLabel class 添加一个新字符串 属性 以通过 XAML 传入,然后使用它为相关 属性 设置绑定路径(在我的例子中是文本 属性)。如果需要多个绑定,则需要为每个必需的 属性 添加多个新的绑定路径属性。我设置了这样的特定绑定:

    appCompatTextView.SetBinding("Text", new Binding(autoLabel.TextBindingPath));

    为了在我的 Xamarin Forms Xaml 中实现这一点,我的 Xaml 从 <Label Text="{Binding MyViewModelPropertyName}" /> 变成了 <controls:AutoSizeLabel TextBindingPath="MyViewModelPropertyName" />

渲染器的完整代码如下:

    protected override bool ManageNativeControlLifetime => false;

    protected override void Dispose(bool disposing)
    {
        Control.RemoveFromParent();
        base.Dispose(disposing);
    }

    private AppCompatTextView appCompatTextView;

    protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
    {
        base.OnElementChanged(e);

        if (e.NewElement == null || !(e.NewElement is AutoSizeLabel autoLabel) || Control == null) { return; }

        //v8 and above supported natively, no need for the extra stuff below.
        if (DeviceInfo.Version.Major >= 8)
        {
            Control?.SetAutoSizeTextTypeUniformWithConfiguration(
                autoLabel.AutoSizeMinTextSize,
                autoLabel.AutoSizeMaxTextSize, autoLabel.AutoSizeStepGranularity,
                (int)ComplexUnitType.Sp);
            return;
        }

            appCompatTextView = new AppCompatTextView(Context);
            appCompatTextView.SetTextColor(Element.TextColor.ToAndroid());
            appCompatTextView.SetMaxLines(1);
            appCompatTextView.Gravity = GravityFlags.Center;
            appCompatTextView.SetBindingContext(autoLabel.BindingContext);
            appCompatTextView.SetBinding("Text", new Binding(autoLabel.TextBindingPath));
            SetNativeControl(appCompatTextView);
        

        TextViewCompat.SetAutoSizeTextTypeUniformWithConfiguration(Control, autoLabel.AutoSizeMinTextSize, autoLabel.AutoSizeMaxTextSize, autoLabel.AutoSizeStepGranularity, (int)ComplexUnitType.Sp);
    }