天天看點

第十七章:掌握網格(七)

響應方向變化

應用程式頁面的布局通常與特定的外形和寬高比緊密相關。有時,應用程式将要求僅在縱向或橫向模式下使用它。但是,當手機改變方向時,應用程式通常會嘗試在螢幕上移動。

網格可以幫助應用程式适應方向更改。可以在XAML中定義網格,對縱向和橫向模式都有一定的限制,然後一些代碼可以在頁面的SizeChanged處理程式中進行适當的調整。

如果您可以将應用程式的整個布局劃分為兩個大區域,當手機以縱向模式定向或水準定向為橫向模式時,此作業最簡單。将這些區域中的每一個放在網格的單獨單元格中。當手機處于縱向模式時,網格有兩行,當它處于橫向模式時,它有兩列。在下圖中,第一個區域始終位于頂部或左側。第二個區域可以是縱向模式的第二行,也可以是橫向模式的第二列:

第十七章:掌握網格(七)

為了使事情變得相當簡單,您需要在XAML中定義具有兩行和兩列的網格,但在縱向模式下,第二列的寬度為零,而在橫向模式下,第二行的高度為零。

GridRgbSliders程式示範了這種技術。它類似于第15章“互動式界面”中的RgbSliders程式,除了布局使用Grid和StackLayout的組合,而Label元素通過使用帶有值的資料綁定來顯示Slider元素的目前值轉換器和值轉換器參數。 (稍後會詳細介紹。)基于三個Slider元素設定BoxView的Color屬性仍然需要代碼,因為Color結構的R,G和B屬性不受可綁定屬性的支援,并且這些屬性不能單獨更改無論如何,因為他們沒有公共集通路器。 (但是,在下一章中,在MVVM上,您将看到一種在代碼隐藏檔案中消除此邏輯的方法。)

正如您在下面的清單中所看到的,名為mainGrid的Grid确實有兩行和兩列。但是,它已初始化為縱向模式,是以第二列的寬度為零。 Grid的頂行包含BoxView,使用“*”(星号)設定盡可能大,而底行包含StackLayout和所有互動式控件。這是自動高度:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:toolkit=
                 "clr-namespace:Xamarin.FormsBook.Toolkit;assembly=Xamarin.FormsBook.Toolkit"
             x:Class="GridRgbSliders.GridRgbSlidersPage"
             SizeChanged="OnPageSizeChanged">
 
    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness"
                    iOS="0, 20, 0, 0" />
    </ContentPage.Padding>
    <ContentPage.Resources>
        <ResourceDictionary>
            <toolkit:DoubleToIntConverter x:Key="doubleToInt" />
            <Style TargetType="Label">
                <Setter Property="HorizontalTextAlignment" Value="Center" />
            </Style>
        </ResourceDictionary>
    </ContentPage.Resources>
    <Grid x:Name="mainGrid">
        <!-- Initialized for portrait mode. -->
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="0" />
        </Grid.ColumnDefinitions>
        <BoxView x:Name="boxView"
                 Grid.Row="0" Grid.Column="0" />
        <StackLayout x:Name="controlPanelStack"
                     Grid.Row="1" Grid.Column="0"
                     Padding="10, 5">
 
            <StackLayout VerticalOptions="CenterAndExpand">
                <Slider x:Name="redSlider"
                        ValueChanged="OnSliderValueChanged" />
                <Label Text="{Binding Source={x:Reference redSlider},
                                      Path=Value,
                                      Converter={StaticResource doubleToInt},
                                      ConverterParameter=255,
                                      StringFormat='Red = {0:X2}'}" />
            </StackLayout>
            <StackLayout VerticalOptions="CenterAndExpand">
                <Slider x:Name="greenSlider"
                        ValueChanged="OnSliderValueChanged" />
                <Label Text="{Binding Source={x:Reference greenSlider},
                                      Path=Value,
                                      Converter={StaticResource doubleToInt},
                                      ConverterParameter=255,
                                      StringFormat='Green = {0:X2}'}" />
            </StackLayout>
 
            <StackLayout VerticalOptions="CenterAndExpand">
                <Slider x:Name="blueSlider"
                        ValueChanged="OnSliderValueChanged" />
                <Label Text="{Binding Source={x:Reference blueSlider},
                                      Path=Value,
                                      Converter={StaticResource doubleToInt},
                                      ConverterParameter=255,
                                      StringFormat='Blue = {0:X2}'}" />
            </StackLayout>
        </StackLayout>
    </Grid>
</ContentPage>           

這是縱向視圖:

第十七章:掌握網格(七)

XAML檔案中的布局以兩種方式為橫向模式準備。首先,Grid已經有了第二列。這意味着要切換到橫向模式,代碼隐藏檔案需要将第二行的高度更改為零,将第二列的寬度更改為非零值。

其次,包含所有Slider和Label元素的StackLayout可以從代碼通路,因為它有一個名稱,特别是controlPanelStack。然後,代碼隐藏檔案可以對此StackLayout進行Grid.SetRow和Grid.SetColumn調用,以将其從第1行和第0列移動到第0行和第1列。

在縱向模式下,BoxView的高度為“”(星号),StackLayout的高度為“自動”。這是否意味着StackLayout的寬度在橫向模式下應該是Auto?這不是明智之舉,因為它會縮小Slider元素的寬度。橫向模式的一個更好的解決方案是給BoxView和StackLayout寬度為“”(星号),将螢幕分成兩半。

這是代碼隐藏檔案,顯示負責在縱向和橫向模式之間切換的頁面上的SizeChanged處理程式,以及設定BoxView顔色的Slider元素的ValueChanged處理程式:

public partial class GridRgbSlidersPage : ContentPage
{
    public GridRgbSlidersPage()
    {
        // Ensure link to Toolkit library.
        new Xamarin.FormsBook.Toolkit.DoubleToIntConverter();
        InitializeComponent();
    }
    void OnPageSizeChanged(object sender, EventArgs args)
    {
        // Portrait mode.
        if (Width < Height)
        {
            mainGrid.RowDefinitions[1].Height = GridLength.Auto;
            mainGrid.ColumnDefinitions[1].Width = new GridLength(0, GridUnitType.Absolute);
            Grid.SetRow(controlPanelStack, 1);
            Grid.SetColumn(controlPanelStack, 0);
        }
        // Landscape mode.
        else
        {
            mainGrid.RowDefinitions[1].Height = new GridLength(0, GridUnitType.Absolute);
            mainGrid.ColumnDefinitions[1].Width = new GridLength(1, GridUnitType.Star);
            Grid.SetRow(controlPanelStack, 0);
            Grid.SetColumn(controlPanelStack, 1);
        }
    }
    void OnSliderValueChanged(object sender, ValueChangedEventArgs args)
    {
        boxView.Color = new Color(redSlider.Value, greenSlider.Value, blueSlider.Value);
    }
}           

這是橫向展示的橫向布局:

第十七章:掌握網格(七)

請注意,特别是在iOS和Android顯示器上,每對Slider和Label元素如何組合在一起。這是第三種方式,即XAML檔案準備好适應橫向模式。每對Slider和Label元素都嵌套在一個嵌套的StackLayout中。這将給出CenterAndExpand的VerticalOptions設定以執行此間距。

稍微考慮安排BoxView和控制台:在縱向模式下,操縱Slider元素的手指不會遮擋BoxView中的結果,而在橫向模式下,慣用右手的使用者的手指不會模糊BoxView也是。 (當然,左撇子使用者可能會堅持使用程式選項來交換位置!)

螢幕截圖顯示了以十六進制顯示的Slider值。這是通過資料綁定完成的,這通常是個問題。 Slider的Value屬性是double類型,如果您嘗試使用“X2”格式化十六進制的double,則會引發異常。類型轉換器(例如,名為DoubleToIntConverter)必須将源double轉換為int以進行字元串格式化。但是,Slider元素的設定範圍為0到1,而格式為十六進制的整數值的範圍必須介于0到255之間。

解決方案是使用Binding的ConverterParameter屬性。設定為此屬性的任何内容都作為第三個參數傳遞給值轉換器中的Convert和ConvertBack方法。這是Xamarin.FormsBook.Toolkit庫中的DoubleToIntConverter類:

namespace Xamarin.FormsBook.Toolkit
{
    public class DoubleToIntConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, 
 object parameter, CultureInfo culture)
        {
            string strParam = parameter as string;
            double multiplier = 1;
            if (!String.IsNullOrEmpty(strParam))
            {
                Double.TryParse(strParam, out multiplier);
            }
            return (int)Math.Round((double)value * multiplier);
        }
        public object ConvertBack(object value, Type targetType, 
 object parameter, CultureInfo culture)
        {
            string strParam = parameter as string;
            double divider = 1;
            if (!String.IsNullOrEmpty(strParam))
            {
                Double.TryParse(strParam, out divider);
            }
            return (int)value / divider;
        }
    }
}            

Convert和ConvertBack方法假定參數參數是一個字元串,如果是,則嘗試将其轉換為double。 然後将該值乘以轉換的double值,然後将産品轉換為int。

值轉換器,轉換器參數和字元串格式的組合将從Slider到0到1的值轉換為0到255範圍内的整數,然後将這些值格式化為兩個十六進制數字:

<Label Text="{Binding Source={x:Reference redSlider},
                      Path=Value,
                      Converter={StaticResource doubleToInt},
                      ConverterParameter=255,
                      StringFormat='Red = {0:X2}'}" />           

當然,如果您在代碼中定義Binding,則可能将ConverterParameter屬性設定為255的數值而不是字元串“255”,并且DoubleToIntConverter中的邏輯将失敗。 簡單的數值轉換器通常比完全防彈更簡單。

如果沒有代碼隐藏檔案中的Slider事件處理程式,可以完全實作像GridRgbSliders這樣的程式嗎? 代碼肯定仍然是必需的,但其中一些将被移離使用者界面邏輯。 這是下一章探讨的Model-View-ViewModel架構的主要目标。

繼續閱讀