天天看點

第五章:尺寸處理(6)

經驗拟合文本

在具有特定尺寸的矩形内拟合文本的另一種方法涉及基于特定字型大小憑經驗确定呈現文本的大小,然後向上或向下調整該字型大小。 無論可通路性設定如何,此方法都具有在所有裝置上工作的優勢。

但是這個過程可能很棘手:第一個問題是字型大小和渲染文本的高度之間沒有幹淨的線性關系。 随着文本相對于其容器的寬度變大,會導緻更多的換行符,并帶來更多的空間浪費。 尋找最佳字型大小的計算通常涉及縮小該值的循環。

第二個問題涉及擷取以特定字型大小呈現的标簽大小的實際機制。 您可以在Label上設定SizeChanged處理程式,但在該處理程式中,您不希望進行任何更改(如設定新的FontSize屬性),這會導緻對該處理程式的遞歸調用。

更好的方法是調用由VisualElement定義的GetSizeRequest方法,并由Label和所有其他視圖繼承。 GetSizeRequest需要兩個參數 - 寬度限制和高度限制。 這些值表示要在其中拟合元素的矩形的大小,其中一個或另一個可以是無限大。 通過标簽使用GetSizeRequest時,通常将寬度限制參數設定為容器的寬度,将高度限制設定為Double.PositiveInfinity。

GetSizeRequest方法傳回一個SizeRequest類型的值,這是一個帶有兩個适當的結構的結構,名為Request和Minimum,都是Size類型。 Request屬性表示呈現文本的大小。 (關于這個和相關方法的更多資訊可以在第26章中找到。)

EmpiricalFontSize項目示範了這種技術。 為了友善起見,它定義了一個名為FontCalc的小型結構,其構造函數針對特定Label(已用文本初始化)調用GetSizeRequest,試用字型大小和文本寬度:

struct FontCalc
{
    public FontCalc(Label label, double fontSize, double containerWidth)
         : this()
    {
        // Save the font size.
        FontSize = fontSize;
        // Recalculate the Label height.
        label.FontSize = fontSize;
        SizeRequest sizeRequest =
                 label.GetSizeRequest(containerWidth, Double.PositiveInfinity);
        // Save that height.
        TextHeight = sizeRequest.Request.Height;
    }
    public double FontSize { private set; get; }
    public double TextHeight { private set; get; }
}           

渲染标簽的合成高度儲存在TextHeight屬性中。

當您在頁面或布局上調用GetSizeRequest時,頁面或布局需要通過可視化樹來檢視所有子項的大小。 這當然會帶來性能上的損失,是以除非必要,否則應該避免撥打電話。 但是一個Label沒有孩子,是以在一個Label上調用GetSizeRequest并不是那麼糟糕。 但是,您仍然應該嘗試優化呼叫。 避免循環通路一系列字型大小值,以确定不會導緻文本超過容器高度的最大值。 在算法上縮小最佳值的過程更好。

GetSizeRequest要求該元素是可視化樹的一部分,并且布局過程至少部分開始。 不要在頁面類的構造函數中調用GetSizeRequest。 你不會從中獲得資訊。 第一個合理的機會是覆寫頁面的OnAppearing方法。 當然,您目前可能沒有足夠的資訊将參數傳遞給GetSizeRequest方法。

但是,調用GetSizeRequest并沒有任何副作用。 它不會在元素上設定新的大小,這意味着它不會引發SizeChanged事件,這意味着調用SizeChanged處理程式是安全的。

EmpiricalFontSizePage類執行個體化承載标簽的ContentView的SizeChanged處理程式中的FontCalc值。 每個FontCalc值的構造函數在Label上調用GetSize?Request調用并儲存結果TextHeight。 SizeChanged處理程式的初始字型大小為10和100,假定最佳值位于這兩者之間,并且它們代表下限和上限。 是以變量名稱更低?FontCalc和upperFontCalc:

public class EmpiricalFontSizePage : ContentPage
{
    Label label;
    public EmpiricalFontSizePage()
    {
        label = new Label();
        Padding = new Thickness(0, Device.OnPlatform(20, 0, 0), 0, 0);
        ContentView contentView = new ContentView
        {
            Content = label
        };
        contentView.SizeChanged += OnContentViewSizeChanged;
        Content = contentView;
    }
    void OnContentViewSizeChanged(object sender, EventArgs args)
    {
        // Get View whose size is changing.
        View view = (View)sender;
        if (view.Width <= 0 || view.Height <= 0)
            return;
        label.Text =
                 "This is a paragraph of text displayed with " +
                 "a FontSize value of ?? that is empirically " +
                 "calculated in a loop within the SizeChanged " +
                 "handler of the Label's container. This technique " +
                 "can be tricky: You don't want to get into " +
                 "an infinite loop by triggering a layout pass " +
                 "with every calculation. Does it work?";
        // Calculate the height of the rendered text.
        FontCalc lowerFontCalc = new FontCalc(label, 10, view.Width);
        FontCalc upperFontCalc = new FontCalc(label, 100, view.Width);
        while (upperFontCalc.FontSize - lowerFontCalc.FontSize > 1)
        {
            // Get the average font size of the upper and lower bounds.
            double fontSize = (lowerFontCalc.FontSize + upperFontCalc.FontSize) / 2;
             // Check the new text height against the container height.
            FontCalc newFontCalc = new FontCalc(label, fontSize, view.Width);
            if (newFontCalc.TextHeight > view.Height)
            {
                upperFontCalc = newFontCalc;
            }
            else
            {
                lowerFontCalc = newFontCalc;
            }
        }
        // Set the final font size and the text with the embedded value.
        label.FontSize = lowerFontCalc.FontSize;
        label.Text = label.Text.Replace("??", label.FontSize.ToString("F0"));
    }
}           

在while循環的每次疊代中,這兩個FontCalc值的FontSize屬性都會儲存并獲得新的FontCalc。 這成為新的lowerFontCalc或upperFontCalc值,具體取決于渲染文本的高度。 當計算的字型大小在最佳值的一個機關内時,循環結束。

大約七次疊代的循環就足以得到一個明顯好于早期程式中計算的估計值的值:

第五章:尺寸處理(6)

将手機橫向移動觸發另一次重新計算,導緻類似的(盡管不一定相同)字型大小:

第五章:尺寸處理(6)

看起來,除了簡單地将FontSize屬性與FontCalc的較低和較高值平均以外,算法可以得到改進。 但是字型大小和文本高度之間的關系相當複雜,有時候最簡單的方法也是一樣的好。

繼續閱讀