Compartilhar via


Tratamento de rotação

Este tópico descreve como lidar com alterações de orientação do dispositivo no Xamarin.Android. Ele aborda como trabalhar com o sistema de recursos Android para carregar automaticamente recursos para uma orientação de dispositivo específica, bem como como lidar programaticamente com alterações de orientação.

Visão geral

Como os dispositivos móveis são facilmente rotacionados, a rotação integrada é um recurso padrão em sistemas operacionais móveis. O Android fornece uma estrutura sofisticada para lidar com a rotação dentro de aplicativos, quer a interface do usuário seja criada declarativamente em XML ou programaticamente em código. Ao lidar automaticamente com alterações de layout declarativas em um dispositivo rotacionado, um aplicativo pode se beneficiar da forte integração com o sistema de recursos Android. Para o layout programático, as alterações devem ser tratadas manualmente. Isso permite um controle mais fino em tempo de execução, mas às custas de mais trabalho para o desenvolvedor. Um aplicativo também pode optar por desativar a reinicialização da atividade e assumir o controle manual das alterações de orientação.

Este guia examina os seguintes tópicos de orientação:

  • Rotação de layout declarativa – Como usar o sistema de recursos do Android para criar aplicativos com reconhecimento de orientação, incluindo como carregar layouts e desenhos para orientações específicas.

  • Rotação de layout programático – Como adicionar controles programaticamente, bem como lidar com alterações de orientação manualmente.

Manipulando a rotação declarativamente com layouts

Ao incluir arquivos em pastas que seguem convenções de nomenclatura, o Android carrega automaticamente os arquivos apropriados quando a orientação muda. Isso inclui suporte para:

  • Recursos de layout – Especificando quais arquivos de layout são inflados para cada orientação.

  • Recursos Desenháveis – Especificando quais sacáveis são carregados para cada orientação.

Recursos de layout

Por padrão, os arquivos XML (AXML) do Android incluídos na pasta Recursos/layout são usados para renderizar exibições de uma Atividade. Os recursos dessa pasta são usados para orientação retrato e paisagem se nenhum recurso de layout adicional for fornecido especificamente para paisagem. Considere a estrutura do projeto criada pelo modelo de projeto padrão:

Estrutura de modelo de projeto padrão

Este projeto cria um único arquivo Main.axml na pasta Resources/layout . Quando o método Activity OnCreate é chamado, ele infla a exibição definida em Main.axml, que declara um botão conforme mostrado no XML abaixo:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">
<Button  
  android:id="@+id/myButton"
  android:layout_width="fill_parent" 
  android:layout_height="wrap_content" 
  android:text="@string/hello"/>
</LinearLayout>

Se o dispositivo for girado para a orientação paisagem, o método Activity OnCreate será chamado novamente e o mesmo arquivo Main.axml será inflado, conforme mostrado na captura de tela abaixo:

Mesma tela, mas na orientação paisagem

Layouts específicos de orientação

Além da pasta de layout (que tem como padrão retrato e também pode ser explicitamente nomeada como layout-port incluindo uma pasta chamada layout-land), um aplicativo pode definir as exibições necessárias quando estiver em paisagem sem alterações de código.

Suponha que o arquivo Main.axml contivesse o seguinte XML:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">
  <TextView
    android:text="This is portrait"
    android:layout_height="wrap_content"
    android:layout_width="fill_parent" />
</RelativeLayout>

Se uma pasta chamada layout-land que contém um arquivo Main.axml adicional for adicionada ao projeto, inflar o layout quando estiver em paisagem agora resultará no carregamento do Main.axml recém-adicionado pelo Android. Considere a versão paisagem do arquivo Main.axml que contém o código a seguir (para simplificar, esse XML é semelhante à versão retrato padrão do código, mas usa uma cadeia de caracteres diferente no TextView):

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">
  <TextView
    android:text="This is landscape"
    android:layout_height="wrap_content"
    android:layout_width="fill_parent" />
</RelativeLayout>

Executar esse código e girar o dispositivo de retrato para paisagem demonstra o novo carregamento XML, conforme mostrado abaixo:

Capturas de tela retrato e paisagem imprimindo o modo retrato

Recursos Sacáveis

Durante a rotação, o Android trata os recursos desenháveis de forma semelhante aos recursos de layout. Nesse caso, o sistema obtém os sacáveis das pastas Resources/drawable e Resources/drawable-land , respectivamente.

Por exemplo, digamos que o projeto inclua uma imagem chamada Monkey.png na pasta Resources/drawable , onde o desenho é referenciado a partir de um ImageView XML como este:

<ImageView
  android:layout_height="wrap_content"
  android:layout_width="wrap_content"
  android:src="@drawable/monkey"
  android:layout_centerVertical="true"
  android:layout_centerHorizontal="true" />

Vamos supor ainda que uma versão diferente do Monkey.png esteja incluída em Recursos/drawable-land. Assim como nos arquivos de layout, quando o dispositivo é girado, o desenho muda para a orientação dada, como mostrado abaixo:

Versão diferente do Monkey.png mostrada nos modos retrato e paisagem

Manipulando a rotação programaticamente

Às vezes, definimos layouts em código. Isso pode acontecer por uma variedade de razões, incluindo limitações técnicas, preferência do desenvolvedor, etc. Quando adicionamos controles programaticamente, um aplicativo deve contabilizar manualmente a orientação do dispositivo, que é manipulada automaticamente quando usamos recursos XML.

Adicionando controles no código

Para adicionar controles programaticamente, um aplicativo precisa executar as seguintes etapas:

  • Crie um layout.
  • Definir parâmetros de layout.
  • Criar controles.
  • Definir parâmetros de layout de controle.
  • Adicione controles ao layout.
  • Defina o layout como o modo de exibição de conteúdo.

Por exemplo, considere uma interface do usuário que consiste em um único TextView controle adicionado a um RelativeLayout, conforme mostrado no código a seguir.

protected override void OnCreate (Bundle bundle)
{
  base.OnCreate (bundle);
                        
  // create a layout
  var rl = new RelativeLayout (this);

  // set layout parameters
  var layoutParams = new RelativeLayout.LayoutParams (ViewGroup.LayoutParams.FillParent, ViewGroup.LayoutParams.FillParent);
  rl.LayoutParameters = layoutParams;
        
  // create TextView control
  var tv = new TextView (this);

  // set TextView's LayoutParameters
  tv.LayoutParameters = layoutParams;
  tv.Text = "Programmatic layout";

  // add TextView to the layout
  rl.AddView (tv);
        
  // set the layout as the content view
  SetContentView (rl);
}

Esse código cria uma instância de uma RelativeLayout classe e define sua LayoutParameters propriedade. A LayoutParams classe é a maneira do Android encapsular como os controles são posicionados de forma reutilizável. Depois que uma instância de um layout é criada, os controles podem ser criados e adicionados a ele. Os controles também têm LayoutParameters, como o TextView neste exemplo. Depois que o TextView for criado, adicioná-lo ao RelativeLayout e definir o RelativeLayout como o modo de exibição de conteúdo resulta no aplicativo exibindo o TextView como mostrado:

Botão do contador de incremento mostrado nos modos retrato e paisagem

Detectando orientação no código

Se um aplicativo tentar carregar uma interface de usuário diferente para cada orientação quando OnCreate for chamada (isso acontecerá sempre que um dispositivo for girado), ele deverá detectar a orientação e, em seguida, carregar o código de interface do usuário desejado. O Android tem uma classe chamada WindowManager, que pode ser usada para determinar a rotação atual do dispositivo através da WindowManager.DefaultDisplay.Rotation propriedade, como mostrado abaixo:

protected override void OnCreate (Bundle bundle)
{
  base.OnCreate (bundle);
                        
  // create a layout
  var rl = new RelativeLayout (this);

  // set layout parameters
  var layoutParams = new RelativeLayout.LayoutParams (ViewGroup.LayoutParams.FillParent, ViewGroup.LayoutParams.FillParent);
  rl.LayoutParameters = layoutParams;
                        
  // get the initial orientation
  var surfaceOrientation = WindowManager.DefaultDisplay.Rotation;
  // create layout based upon orientation
  RelativeLayout.LayoutParams tvLayoutParams;
                
  if (surfaceOrientation == SurfaceOrientation.Rotation0 || surfaceOrientation == SurfaceOrientation.Rotation180) {
    tvLayoutParams = new RelativeLayout.LayoutParams (ViewGroup.LayoutParams.FillParent, ViewGroup.LayoutParams.WrapContent);
  } else {
    tvLayoutParams = new RelativeLayout.LayoutParams (ViewGroup.LayoutParams.FillParent, ViewGroup.LayoutParams.WrapContent);
    tvLayoutParams.LeftMargin = 100;
    tvLayoutParams.TopMargin = 100;
  }
                        
  // create TextView control
  var tv = new TextView (this);
  tv.LayoutParameters = tvLayoutParams;
  tv.Text = "Programmatic layout";
        
  // add TextView to the layout
  rl.AddView (tv);
        
  // set the layout as the content view
  SetContentView (rl);
}

Este código define o para ser posicionado TextView 100 pixels a partir do canto superior esquerdo da tela, animando automaticamente para o novo layout, quando girado para paisagem, como mostrado aqui:

O estado de exibição é preservado nos modos retrato e paisagem

Impedindo a reinicialização da atividade

Além de lidar com tudo no OnCreate, um aplicativo também pode impedir que uma Atividade seja reiniciada quando a orientação for alterada, definindo ConfigurationChanges o ActivityAttribute seguinte:

[Activity (Label = "CodeLayoutActivity", ConfigurationChanges=Android.Content.PM.ConfigChanges.Orientation | Android.Content.PM.ConfigChanges.ScreenSize)]

Agora, quando o dispositivo é girado, a atividade não é reiniciada. Para manipular manualmente a mudança de orientação nesse caso, uma Atividade pode substituir o OnConfigurationChanged método e determinar a Configuration orientação do objeto que é passado, como na nova implementação da Atividade abaixo:

[Activity (Label = "CodeLayoutActivity", ConfigurationChanges=Android.Content.PM.ConfigChanges.Orientation | Android.Content.PM.ConfigChanges.ScreenSize)]
public class CodeLayoutActivity : Activity
{
  TextView _tv;
  RelativeLayout.LayoutParams _layoutParamsPortrait;
  RelativeLayout.LayoutParams _layoutParamsLandscape;
                
  protected override void OnCreate (Bundle bundle)
  {
    // create a layout
    // set layout parameters
    // get the initial orientation

    // create portrait and landscape layout for the TextView
    _layoutParamsPortrait = new RelativeLayout.LayoutParams (ViewGroup.LayoutParams.FillParent, ViewGroup.LayoutParams.WrapContent);
                
    _layoutParamsLandscape = new RelativeLayout.LayoutParams (ViewGroup.LayoutParams.FillParent, ViewGroup.LayoutParams.WrapContent);
    _layoutParamsLandscape.LeftMargin = 100;
    _layoutParamsLandscape.TopMargin = 100;
                        
    _tv = new TextView (this);
                        
    if (surfaceOrientation == SurfaceOrientation.Rotation0 || surfaceOrientation == SurfaceOrientation.Rotation180) {
      _tv.LayoutParameters = _layoutParamsPortrait;
    } else {
      _tv.LayoutParameters = _layoutParamsLandscape;
    }
                        
    _tv.Text = "Programmatic layout";
    rl.AddView (_tv);
    SetContentView (rl);
  }
                
  public override void OnConfigurationChanged (Android.Content.Res.Configuration newConfig)
  {
    base.OnConfigurationChanged (newConfig);
                        
    if (newConfig.Orientation == Android.Content.Res.Orientation.Portrait) {
      _tv.LayoutParameters = _layoutParamsPortrait;
      _tv.Text = "Changed to portrait";
    } else if (newConfig.Orientation == Android.Content.Res.Orientation.Landscape) {
      _tv.LayoutParameters = _layoutParamsLandscape;
      _tv.Text = "Changed to landscape";
    }
  }
}

Aqui, os parâmetros de TextView's layout são inicializados para paisagem e retrato. As variáveis de classe mantêm os parâmetros, juntamente com o TextView próprio, uma vez que a Atividade não será recriada quando a orientação for alterada. O código ainda usa o surfaceOrientartion in OnCreate para definir o layout inicial para o TextView. Depois disso, OnConfigurationChanged lida com todas as alterações de layout subsequentes.

Quando executamos o aplicativo, o Android carrega as alterações da interface do usuário conforme a rotação do dispositivo ocorre e não reinicia a atividade.

Impedindo a reinicialização da atividade para layouts declarativos

As reinicializações de atividades causadas pela rotação do dispositivo também podem ser evitadas se definirmos o layout em XML. Por exemplo, podemos usar essa abordagem se quisermos impedir uma reinicialização de atividade (por motivos de desempenho, talvez) e não precisarmos carregar novos recursos para orientações diferentes.

Para fazer isso, seguimos o mesmo procedimento que usamos com um layout programático. Simplesmente definido ConfigurationChanges no ActivityAttribute, como fizemos no CodeLayoutActivity anterior. Qualquer código que precise ser executado para a mudança de orientação pode ser implementado novamente no OnConfigurationChanged método.

Mantendo o estado durante as alterações de orientação

Seja manipulando a rotação declarativa ou programaticamente, todos os aplicativos Android devem implementar as mesmas técnicas para gerenciar o estado quando a orientação do dispositivo muda. O gerenciamento de estado é importante porque o sistema reinicia uma atividade em execução quando um dispositivo Android é girado. O Android faz isso para facilitar o carregamento de recursos alternativos, como layouts e desenhos, projetados especificamente para uma orientação específica. Quando é reiniciada, a Atividade perde qualquer estado transitório que possa ter armazenado em variáveis de classe locais. Portanto, se uma atividade for dependente do estado, ela deverá manter seu estado no nível do aplicativo. Um aplicativo precisa lidar com o salvamento e a restauração de qualquer estado de aplicativo que ele deseja preservar em todas as alterações de orientação.

Para obter mais informações sobre o estado persistente no Android, consulte o guia Ciclo de Vida da atividade .

Resumo

Este artigo abordou como usar os recursos internos do Android para trabalhar com rotação. Primeiro, explicou como usar o sistema de recursos do Android para criar aplicativos com reconhecimento de orientação. Em seguida, ele apresentou como adicionar controles no código, bem como lidar com alterações de orientação manualmente.