Ориентация устройства
Важно учитывать, как будет использоваться приложение и как можно включить альбомную ориентацию для улучшения взаимодействия с пользователем. Отдельные макеты можно разрабатывать для размещения нескольких ориентаций и лучше всего использовать доступное пространство. На уровне приложения поворот можно отключить или включить.
Управление ориентацией
При использовании Xamarin.Formsподдерживаемый метод управления ориентацией устройства — использовать параметры для каждого отдельного проекта.
iOS
В iOS ориентация устройства настраивается для приложений с помощью файла Info.plist . Используйте параметры интегрированной среды разработки в верхней части этого документа, чтобы выбрать нужные инструкции:
В Visual Studio откройте проект iOS и откройте info.plist. Файл откроется на панели конфигурации, начиная с вкладки сведений о развертывании iPhone:
Android
Чтобы управлять ориентацией в Android, откройте MainActivity.cs и задайте ориентацию с помощью атрибута, украшающего MainActivity
класс:
namespace MyRotatingApp.Droid
{
[Activity (Label = "MyRotatingApp.Droid", Icon = "@drawable/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation, ScreenOrientation = ScreenOrientation.Landscape)] //This is what controls orientation
public class MainActivity : FormsAppCompatActivity
{
protected override void OnCreate (Bundle bundle)
...
Xamarin.Android поддерживает несколько вариантов указания ориентации:
- Ландшафт — заставляет ориентацию приложения быть альбомной, независимо от данных датчика.
- Книжная — заставляет ориентацию приложения быть книжной, независимо от данных датчика.
- Пользователь — приводит к тому, что приложение будет представлено с помощью предпочтительной ориентации пользователя.
- Позади — приводит к тому, что ориентация приложения совпадает с ориентацией действия за ним.
- Датчик — приводит к тому, что ориентация приложения определяется датчиком, даже если пользователь отключил автоматическую смену.
- SensorLandscape — позволяет приложению использовать альбомную ориентацию при использовании данных датчика для изменения направления экрана (так что экран не отображается как перевернутый).
- SensorPortrait — позволяет приложению использовать книжную ориентацию при использовании данных датчика для изменения направления экрана (так что экран не отображается как перевернутый).
- ReverseLandscape — заставляет приложение использовать альбомную ориентацию, сталкиваясь с противоположной стороной от обычной, чтобы показаться "вверх с ног".
- ReversePortrait — приводит к тому, что приложение использует книжную ориентацию, сталкиваясь с противоположной стороной от обычного, чтобы появиться "вверх с ног".
- FullSensor — позволяет приложению полагаться на данные датчика, чтобы выбрать правильную ориентацию (из возможного 4).
- FullUser — приводит к тому, что приложение будет использовать настройки ориентации пользователя. Если включена автоматическая смена, можно использовать все 4 ориентации.
- UserLandscape — [Не поддерживается] заставляет приложение использовать альбомную ориентацию, если пользователь не включил автоматическую смену, в этом случае датчик будет использоваться для определения ориентации. Этот параметр приведет к прерыванию компиляции.
- UserPortrait — [Не поддерживается] приводит к тому, что приложение будет использовать книжную ориентацию, если пользователь не включил автоматическую смену, в этом случае датчик будет использовать датчик для определения ориентации. Этот параметр приведет к прерыванию компиляции.
- Заблокировано . [Не поддерживается] приложение будет использовать ориентацию экрана, независимо от того, что она запущена, без реагирования на изменения физической ориентации устройства. Этот параметр приведет к прерыванию компиляции.
Обратите внимание, что собственные API Android обеспечивают много контроля над управлением ориентацией, включая параметры, которые явно противоречат выраженным предпочтениям пользователя.
Универсальная платформа Windows
В универсальная платформа Windows (UWP) поддерживаемые ориентации задаются в файле Package.appxmanifest. Открытие манифеста покажет панель конфигурации, в которой можно выбрать поддерживаемые ориентации.
Реагирование на изменения в ориентации
Xamarin.Forms не предлагает собственные события для уведомления приложения об изменениях ориентации в общем коде. Однако содержит класс [DeviceDisplay
],Xamarin.Essentials предоставляющий уведомления об изменениях ориентации.
Чтобы обнаружить ориентацию без Xamarin.Essentials, отслеживайте SizeChanged
событие Page
, которое срабатывает при изменении ширины или высоты Page
. Если ширина превышает Page
высоту, устройство находится в альбомном режиме. Дополнительные сведения см. в разделе "Отображение изображения на основе ориентации экрана".
Кроме того, можно переопределить OnSizeAllocated
метод на объекте, вставляя в нее Page
логику изменения макета. Метод OnSizeAllocated
вызывается всякий раз Page
, когда выделяется новый размер, который происходит при смене устройства. Обратите внимание, что базовая реализация выполняет важные функции макета OnSizeAllocated
, поэтому важно вызвать базовую реализацию в переопределении:
protected override void OnSizeAllocated(double width, double height)
{
base.OnSizeAllocated(width, height); //must be called
}
Сбой этого шага приведет к неработоспособности страницы.
Обратите внимание, что OnSizeAllocated
метод может вызываться много раз при повороте устройства. Изменение макета каждый раз является расточительным из-за ресурсов и может привести к мерцаниям. Попробуйте использовать переменную экземпляра на странице для отслеживания того, находится ли ориентация в альбомной или книжной, и перерисовка только при изменении:
private double width = 0;
private double height = 0;
protected override void OnSizeAllocated(double width, double height)
{
base.OnSizeAllocated(width, height); //must be called
if (this.width != width || this.height != height)
{
this.width = width;
this.height = height;
//reconfigure layout
}
}
После обнаружения изменения ориентации устройства может потребоваться добавить или удалить дополнительные представления в пользовательский интерфейс для реагирования на изменение доступного пространства. Например, рассмотрим встроенный калькулятор на каждой платформе в книжном режиме:
и ландшафт:
Обратите внимание, что приложения используют доступное пространство, добавив дополнительные функциональные возможности в альбомной среде.
Адаптивный макет
Интерфейсы можно создавать с помощью встроенных макетов, чтобы они плавно переходили при повороте устройства. При проектировании интерфейсов, которые будут оставаться привлекательными при реагировании на изменения в ориентации, учитывайте следующие общие правила:
- Обратите внимание на коэффициенты — изменения в ориентации могут вызвать проблемы, когда некоторые предположения делаются в отношении соотношений. Например, представление, которое будет иметь много места в 1/3 вертикального пространства экрана в портрете может не помещаться в 1/3 вертикального пространства в ландшафте.
- Будьте осторожны с абсолютными значениями — абсолютными (пиксель) значениями , которые имеет смысл в портрете, могут не иметь смысла в ландшафте. Если необходимы абсолютные значения, используйте вложенные макеты для изоляции их влияния. Например, было бы разумно использовать абсолютные значения в
TableView
ItemTemplate
шаблоне элемента с гарантированной равномерной высотой.
Приведенные выше правила также применяются при реализации интерфейсов для нескольких размеров экрана и обычно считаются рекомендациями. В остальной части этого руководства приводятся конкретные примеры адаптивных макетов с помощью каждого из основных макетов.Xamarin.Forms
Примечание.
Для ясности в следующих разделах показано, как реализовать адаптивные макеты с помощью одного типа Layout
одновременно. На практике часто проще смешивать Layout
s для достижения требуемого макета с помощью более простого или наиболее интуитивно понятного Layout
для каждого компонента.
StackLayout
Рассмотрим следующее приложение, отображаемое в книжном режиме:
и ландшафт:
Это достигается с помощью следующего XAML:
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ResponsiveLayout.StackLayoutPageXaml"
Title="Stack Photo Editor - XAML">
<ContentPage.Content>
<StackLayout Spacing="10" Padding="5" Orientation="Vertical"
x:Name="outerStack"> <!-- can change orientation to make responsive -->
<ScrollView>
<StackLayout Spacing="5" HorizontalOptions="FillAndExpand"
WidthRequest="1000">
<StackLayout Orientation="Horizontal">
<Label Text="Name: " WidthRequest="75"
HorizontalOptions="Start" />
<Entry Text="deer.jpg"
HorizontalOptions="FillAndExpand" />
</StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="Date: " WidthRequest="75"
HorizontalOptions="Start" />
<Entry Text="07/05/2015"
HorizontalOptions="FillAndExpand" />
</StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="Tags:" WidthRequest="75"
HorizontalOptions="Start" />
<Entry Text="deer, tiger"
HorizontalOptions="FillAndExpand" />
</StackLayout>
<StackLayout Orientation="Horizontal">
<Button Text="Save" HorizontalOptions="FillAndExpand" />
</StackLayout>
</StackLayout>
</ScrollView>
<Image Source="deer.jpg" />
</StackLayout>
</ContentPage.Content>
</ContentPage>
Некоторые C# используются для изменения ориентации outerStack
на основе ориентации устройства:
protected override void OnSizeAllocated (double width, double height){
base.OnSizeAllocated (width, height);
if (width != this.width || height != this.height) {
this.width = width;
this.height = height;
if (width > height) {
outerStack.Orientation = StackOrientation.Horizontal;
} else {
outerStack.Orientation = StackOrientation.Vertical;
}
}
}
Обратите внимание на следующее:
outerStack
настраивается для представления изображения и элементов управления в виде горизонтального или вертикального стека в зависимости от ориентации, чтобы лучше всего воспользоваться доступным пространством.
AbsoluteLayout
Рассмотрим следующее приложение, отображаемое в книжном режиме:
и ландшафт:
Это достигается с помощью следующего XAML:
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ResponsiveLayout.AbsoluteLayoutPageXaml"
Title="AbsoluteLayout - XAML" BackgroundImageSource="deer.jpg">
<ContentPage.Content>
<AbsoluteLayout>
<ScrollView AbsoluteLayout.LayoutBounds="0,0,1,1"
AbsoluteLayout.LayoutFlags="PositionProportional,SizeProportional">
<AbsoluteLayout>
<Image Source="deer.jpg"
AbsoluteLayout.LayoutBounds=".5,0,300,300"
AbsoluteLayout.LayoutFlags="PositionProportional" />
<BoxView Color="#CC1A7019" AbsoluteLayout.LayoutBounds=".5
300,.7,50" AbsoluteLayout.LayoutFlags="XProportional
WidthProportional" />
<Label Text="deer.jpg" AbsoluteLayout.LayoutBounds = ".5
310,1, 50" AbsoluteLayout.LayoutFlags="XProportional
WidthProportional" HorizontalTextAlignment="Center" TextColor="White" />
</AbsoluteLayout>
</ScrollView>
<Button Text="Previous" AbsoluteLayout.LayoutBounds="0,1,.5,60"
AbsoluteLayout.LayoutFlags="PositionProportional
WidthProportional"
BackgroundColor="White" TextColor="Green" BorderRadius="0" />
<Button Text="Next" AbsoluteLayout.LayoutBounds="1,1,.5,60"
AbsoluteLayout.LayoutFlags="PositionProportional
WidthProportional" BackgroundColor="White"
TextColor="Green" BorderRadius="0" />
</AbsoluteLayout>
</ContentPage.Content>
</ContentPage>
Обратите внимание на следующее:
- Из-за того, как страница была размещена, нет необходимости в процедурных кодах для внедрения скорости реагирования.
- Используется
ScrollView
для отображения метки даже в том случае, если высота экрана меньше суммы фиксированных высот кнопок и изображения.
RelativeLayout
Рассмотрим следующее приложение, отображаемое в книжном режиме:
и ландшафт:
Это достигается с помощью следующего XAML:
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ResponsiveLayout.RelativeLayoutPageXaml"
Title="RelativeLayout - XAML"
BackgroundImageSource="deer.jpg">
<ContentPage.Content>
<RelativeLayout x:Name="outerLayout">
<BoxView BackgroundColor="#AA1A7019"
RelativeLayout.WidthConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Width,Factor=1}"
RelativeLayout.HeightConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Height,Factor=1}"
RelativeLayout.XConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Width,Factor=0,Constant=0}"
RelativeLayout.YConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Height,Factor=0,Constant=0}" />
<ScrollView
RelativeLayout.WidthConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Width,Factor=1}"
RelativeLayout.HeightConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Height,Factor=1,Constant=-60}"
RelativeLayout.XConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Width,Factor=0,Constant=0}"
RelativeLayout.YConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Height,Factor=0,Constant=0}">
<RelativeLayout>
<Image Source="deer.jpg" x:Name="imageDeer"
RelativeLayout.WidthConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Width,Factor=.8}"
RelativeLayout.XConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Width,Factor=.1}"
RelativeLayout.YConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Height,Factor=0,Constant=10}" />
<Label Text="deer.jpg" HorizontalTextAlignment="Center"
RelativeLayout.WidthConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Width,Factor=1}"
RelativeLayout.HeightConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Height,Factor=0,Constant=75}"
RelativeLayout.XConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Width,Factor=0,Constant=0}"
RelativeLayout.YConstraint="{ConstraintExpression
Type=RelativeToView,ElementName=imageDeer,Property=Height,Factor=1,Constant=20}" />
</RelativeLayout>
</ScrollView>
<Button Text="Previous" BackgroundColor="White" TextColor="Green" BorderRadius="0"
RelativeLayout.YConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Height,Factor=1,Constant=-60}"
RelativeLayout.XConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Width,Factor=0,Constant=0}"
RelativeLayout.HeightConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Width,Factor=0,Constant=60}"
RelativeLayout.WidthConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Width,Factor=.5}"
/>
<Button Text="Next" BackgroundColor="White" TextColor="Green" BorderRadius="0"
RelativeLayout.XConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Width,Factor=.5}"
RelativeLayout.YConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Height,Factor=1,Constant=-60}"
RelativeLayout.HeightConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Width,Factor=0,Constant=60}"
RelativeLayout.WidthConstraint="{ConstraintExpression
Type=RelativeToParent,Property=Width,Factor=.5}"
/>
</RelativeLayout>
</ContentPage.Content>
</ContentPage>
Обратите внимание на следующее:
- Из-за того, как страница была размещена, нет необходимости в процедурных кодах для внедрения скорости реагирования.
- Используется
ScrollView
для отображения метки даже в том случае, если высота экрана меньше суммы фиксированных высот кнопок и изображения.
Сетка
Рассмотрим следующее приложение, отображаемое в книжном режиме:
и ландшафт:
Это достигается с помощью следующего XAML:
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ResponsiveLayout.GridPageXaml"
Title="Grid - XAML">
<ContentPage.Content>
<Grid x:Name="outerGrid">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="60" />
</Grid.RowDefinitions>
<Grid x:Name="innerGrid" Grid.Row="0" Padding="10">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image Source="deer.jpg" Grid.Row="0" Grid.Column="0" HeightRequest="300" WidthRequest="300" />
<Grid x:Name="controlsGrid" Grid.Row="0" Grid.Column="1" >
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Text="Name:" Grid.Row="0" Grid.Column="0" />
<Label Text="Date:" Grid.Row="1" Grid.Column="0" />
<Label Text="Tags:" Grid.Row="2" Grid.Column="0" />
<Entry Grid.Row="0" Grid.Column="1" />
<Entry Grid.Row="1" Grid.Column="1" />
<Entry Grid.Row="2" Grid.Column="1" />
</Grid>
</Grid>
<Grid x:Name="buttonsGrid" Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Text="Previous" Grid.Column="0" />
<Button Text="Save" Grid.Column="1" />
<Button Text="Next" Grid.Column="2" />
</Grid>
</Grid>
</ContentPage.Content>
</ContentPage>
Наряду со следующим процедурным кодом для обработки изменений поворота:
private double width;
private double height;
protected override void OnSizeAllocated (double width, double height){
base.OnSizeAllocated (width, height);
if (width != this.width || height != this.height) {
this.width = width;
this.height = height;
if (width > height) {
innerGrid.RowDefinitions.Clear();
innerGrid.ColumnDefinitions.Clear ();
innerGrid.RowDefinitions.Add (new RowDefinition{ Height = new GridLength (1, GridUnitType.Star) });
innerGrid.ColumnDefinitions.Add (new ColumnDefinition { Width = new GridLength (1, GridUnitType.Star) });
innerGrid.ColumnDefinitions.Add (new ColumnDefinition { Width = new GridLength (1, GridUnitType.Star) });
innerGrid.Children.Remove (controlsGrid);
innerGrid.Children.Add (controlsGrid, 1, 0);
} else {
innerGrid.RowDefinitions.Clear();
innerGrid.ColumnDefinitions.Clear ();
innerGrid.ColumnDefinitions.Add (new ColumnDefinition{ Width = new GridLength (1, GridUnitType.Star) });
innerGrid.RowDefinitions.Add (new RowDefinition { Height = new GridLength (1, GridUnitType.Auto) });
innerGrid.RowDefinitions.Add (new RowDefinition { Height = new GridLength (1, GridUnitType.Star) });
innerGrid.Children.Remove (controlsGrid);
innerGrid.Children.Add (controlsGrid, 0, 1);
}
}
}
Обратите внимание на следующее:
- Из-за способа размещения страницы существует метод изменения размещения сетки элементов управления.