響應方向變化
應用程式頁面的布局通常與特定的外形和寬高比緊密相關。有時,應用程式将要求僅在縱向或橫向模式下使用它。但是,當手機改變方向時,應用程式通常會嘗試在螢幕上移動。
網格可以幫助應用程式适應方向更改。可以在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架構的主要目标。