Orientação do Dispositivo
É importante considerar como seu aplicativo será usado e como a orientação paisagem pode ser incorporada para melhorar a experiência do usuário. Layouts individuais podem ser projetados para acomodar várias orientações e usar melhor o espaço disponível. No nível do aplicativo, a rotação pode ser desabilitada ou habilitada.
Controlando a orientação
Ao usar Xamarin.Formso , o método suportado para controlar a orientação do dispositivo é usar as configurações de cada projeto individual.
iOS
No iOS, a orientação do dispositivo é configurada para aplicativos usando o arquivo Info.plist . Use as opções do IDE na parte superior deste documento para selecionar quais instruções você gostaria de ver:
No Visual Studio, abra o projeto do iOS e abra Info.plist. O arquivo será aberto em um painel de configuração, começando com a guia Informações de implantação do iPhone:
Android
Para controlar a orientação no Android, abra MainActivity.cs e defina a orientação usando o atributo que decora a MainActivity
classe:
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)
...
O Xamarin.Android dá suporte a várias opções para especificar a orientação:
- Paisagem – força a orientação do aplicativo a ser paisagem, independentemente dos dados do sensor.
- Retrato – força a orientação do aplicativo a ser retrato, independentemente dos dados do sensor.
- Usuário – faz com que o aplicativo seja apresentado usando a orientação preferida do usuário.
- Atrás – faz com que a orientação do aplicativo seja a mesma que a orientação da atividade por trás dele.
- Sensor – faz com que a orientação do aplicativo seja determinada pelo sensor, mesmo que o usuário tenha desabilitado a rotação automática.
- SensorLandscape – faz com que o aplicativo use a orientação paisagem enquanto usa dados do sensor para alterar a direção para a qual a tela está voltada (para que a tela não seja vista de cabeça para baixo).
- SensorPortrait – faz com que o aplicativo use a orientação retrato ao usar dados do sensor para alterar a direção para a qual a tela está voltada (para que a tela não seja vista de cabeça para baixo).
- ReverseLandscape – faz com que o aplicativo use a orientação paisagem, voltada para a direção oposta da usual, de modo a aparecer "de cabeça para baixo".
- ReversePortrait – faz com que o aplicativo use a orientação retrato, voltada para a direção oposta do normal, de modo a aparecer "de cabeça para baixo".
- FullSensor – faz com que o aplicativo dependa dos dados do sensor para selecionar a orientação correta (das 4 possíveis).
- FullUser – faz com que o aplicativo use as preferências de orientação do usuário. Se a rotação automática estiver ativada, todas as 4 orientações poderão ser usadas.
- UserLandscape – [Não suportado] faz com que o aplicativo use a orientação paisagem, a menos que o usuário tenha a rotação automática habilitada, caso em que ele usará o sensor para determinar a orientação. Esta opção interromperá a compilação.
- UserPortrait – [Não suportado] faz com que o aplicativo use a orientação retrato, a menos que o usuário tenha a rotação automática habilitada, caso em que ele usará o sensor para determinar a orientação. Esta opção interromperá a compilação.
- Bloqueado – [Não suportado] faz com que o aplicativo use a orientação da tela, seja ela qual for na inicialização, sem responder a alterações na orientação física do dispositivo. Esta opção interromperá a compilação.
Observe que as APIs nativas do Android fornecem muito controle sobre como a orientação é gerenciada, incluindo opções que contradizem explicitamente as preferências expressas do usuário.
Plataforma Universal do Windows
Na Plataforma Universal do Windows (UWP), as orientações com suporte são definidas no arquivo Package.appxmanifest . Abrir o manifesto revelará um painel de configuração onde as orientações suportadas podem ser selecionadas.
Reagindo a mudanças na orientação
Xamarin.Forms O não oferece nenhum evento nativo para notificar seu aplicativo sobre alterações de orientação no código compartilhado. No entanto,Xamarin.Essentials contém uma classe [DeviceDisplay
] que fornece notificações de alterações de orientação.
Para detectar orientações sem Xamarin.Essentials, monitore o SizeChanged
evento do Page
, que é acionado quando a largura ou a Page
altura das alterações são alteradas. Quando a largura do Page
é maior que a altura, o dispositivo está no modo paisagem. Para obter mais informações, consulte Exibir uma imagem com base na orientação da tela.
Como alternativa, é possível substituir o OnSizeAllocated
método em um Page
, inserindo qualquer lógica de alteração de layout lá. O OnSizeAllocated
método é chamado sempre que a Page
recebe um novo tamanho, o que acontece sempre que o dispositivo é girado. Observe que a implementação base de OnSizeAllocated
executa funções de layout importantes, portanto, é importante chamar a implementação base na substituição:
protected override void OnSizeAllocated(double width, double height)
{
base.OnSizeAllocated(width, height); //must be called
}
A falha em dar esse passo resultará em uma página que não funciona.
Observe que o OnSizeAllocated
método pode ser chamado muitas vezes quando um dispositivo é girado. Alterar seu layout a cada vez é um desperdício de recursos e pode levar à cintilação. Considere usar uma variável de instância em sua página para rastrear se a orientação está em paisagem ou retrato e redesenhar apenas quando houver uma alteração:
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
}
}
Depois que uma alteração na orientação do dispositivo for detectada, você poderá adicionar ou remover visualizações adicionais de/para sua interface do usuário para reagir à alteração no espaço disponível. Por exemplo, considere a calculadora integrada em cada plataforma em retrato:
e paisagem:
Observe que os aplicativos aproveitam o espaço disponível adicionando mais funcionalidades na paisagem.
Layout responsivo
É possível projetar interfaces usando os layouts internos para que eles façam a transição normalmente quando o dispositivo for girado. Ao projetar interfaces que continuarão a ser atraentes ao responder a alterações na orientação, considere as seguintes regras gerais:
- Preste atenção às proporções – mudanças na orientação podem causar problemas quando certas suposições são feitas em relação às proporções. Por exemplo, uma exibição que teria muito espaço em 1/3 do espaço vertical de uma tela em retrato pode não caber em 1/3 do espaço vertical em paisagem.
- Tenha cuidado com valores absolutos – valores absolutos (pixel) que fazem sentido em retrato podem não fazer sentido em paisagem. Quando valores absolutos forem necessários, use layouts aninhados para isolar seu impacto. Por exemplo, seria razoável usar valores absolutos em a
TableView
ItemTemplate
quando o modelo de item tem uma altura uniforme garantida.
As regras acima também se aplicam ao implementar interfaces para vários tamanhos de tela e geralmente são consideradas práticas recomendadas. O restante deste guia explicará exemplos específicos de layouts responsivos usando cada um dos layouts principais no Xamarin.Forms.
Observação
Para maior clareza, as seções a seguir demonstram como implementar layouts responsivos usando apenas um tipo de Layout
cada vez. Na prática, muitas vezes é mais simples misturar Layout
s para obter um layout desejado usando o mais simples ou mais intuitivo Layout
para cada componente.
StackLayout
Considere o seguinte aplicativo, exibido em retrato:
e paisagem:
Isso é feito com o seguinte 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>
Alguns C# são usados para alterar a orientação de outerStack
com base na orientação do dispositivo:
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;
}
}
}
Observe o seguinte:
outerStack
é ajustado para apresentar a imagem e os controles como uma pilha horizontal ou vertical, dependendo da orientação, para aproveitar melhor o espaço disponível.
AbsoluteLayout
Considere o seguinte aplicativo, exibido em retrato:
e paisagem:
Isso é feito com o seguinte 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>
Observe o seguinte:
- Devido à forma como a página foi organizada, não há necessidade de código procedural para introduzir capacidade de resposta.
- O
ScrollView
está sendo usado para permitir que o rótulo seja visível mesmo quando a altura da tela é menor que a soma das alturas fixas dos botões e da imagem.
RelativeLayout
Considere o seguinte aplicativo, exibido em retrato:
e paisagem:
Isso é feito com o seguinte 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>
Observe o seguinte:
- Devido à forma como a página foi organizada, não há necessidade de código procedural para introduzir capacidade de resposta.
- O
ScrollView
está sendo usado para permitir que o rótulo seja visível mesmo quando a altura da tela é menor que a soma das alturas fixas dos botões e da imagem.
Grade
Considere o seguinte aplicativo, exibido em retrato:
e paisagem:
Isso é feito com o seguinte 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>
Junto com o seguinte código de procedimento para lidar com alterações de rotação:
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);
}
}
}
Observe o seguinte:
- Devido à forma como a página foi disposta, há um método para alterar o posicionamento da grade dos controles.