Control de giro

En este tema se describe cómo controlar los cambios de orientación del dispositivo en Xamarin.Android. Trata cómo trabajar con el sistema de recursos de Android para cargar automáticamente los recursos de una orientación de dispositivo determinada, así como cómo controlar mediante programación los cambios de orientación.

Información general

Dado que los dispositivos móviles se giran fácilmente, la rotación integrada es una característica estándar en los sistemas operativos móviles. Android proporciona un marco sofisticado para tratar la rotación dentro de las aplicaciones, tanto si la interfaz de usuario se crea mediante declaración en XML como mediante programación en el código. Al controlar automáticamente los cambios de diseño declarativos en un dispositivo girado, una aplicación puede beneficiarse de la estrecha integración con el sistema de recursos de Android. Para el diseño mediante programación, los cambios se deben controlar manualmente. Esto permite un mayor control en tiempo de ejecución, pero a costa de más trabajo para el desarrollador. Una aplicación también puede optar por no participar en el reinicio de la actividad y tomar el control manual de los cambios de orientación.

En esta guía se examinan los siguientes temas de orientación:

  • Rotación de diseño declarativo: cómo usar el sistema de recursos de Android para crear aplicaciones compatibles con la orientación, incluido cómo cargar diseños y Drawables para orientaciones concretas.

  • Rotación de diseño mediante programación: cómo agregar controles mediante programación, así como cómo controlar manualmente los cambios de orientación.

Control de rotación declarativa con diseños

Al incluir archivos en carpetas que siguen las convenciones de nomenclatura, Android carga automáticamente los archivos adecuados cuando cambia la orientación. Esto incluye la compatibilidad para:

  • Recursos de diseño: especifica qué archivos de diseño se inflan para cada orientación.

  • Recursos Drawables: especifica qué Drawables se cargan para cada orientación.

Recursos de diseño

De forma predeterminada, los archivos XML de Android (AXML) incluidos en la carpeta Resources/layout se usan para representar vistas para una actividad. Los recursos de esta carpeta se usan para la orientación vertical y horizontal si no se proporcionan recursos de diseño adicionales específicamente para horizontal. Considere la estructura del proyecto creada por la plantilla de proyecto predeterminada:

Default project template structure

Este proyecto crea un único archivo Main.axml en la carpeta Resources/layout. Cuando se llama al método OnCreate de Activity, infla la vista definida en Main.axml, que declara un botón como se muestra en el XML siguiente:

<?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>

Si el dispositivo se gira a la orientación horizontal, se llama de nuevo al método OnCreate de Activity y se infla el mismo archivo Main.axml, como se muestra en la captura de pantalla siguiente:

Same screen but in landscape orientation

Diseños específicos de orientación

Además de la carpeta de diseño (que tiene como valor predeterminado vertical y también se puede denominar explícitamente layout-port mediante la inclusión de una carpeta denominada layout-land), una aplicación puede definir las vistas que necesita cuando está en horizontal sin cambios de código.

Supongamos que el archivo Main.axml contenía el siguiente 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>

Si se agrega al projecto un archivo denominado layout-land que contiene un archivo Main.axml adicional, al inflar el diseño cuando esté en horizontal, se cargará Android el archivo Main.axml recién agregado. Considere la versión horizontal del archivo Main.axml que contiene el código siguiente (por simplicidad, este XML es similar a la versión vertical predeterminada del código, pero usa una cadena diferente en 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>

La ejecución de este código y la rotación del dispositivo de vertical a horizontal muestra la nueva carga XML, como se muestra a continuación:

Portrait and landscape screenshots printing the portrait mode

Recursos Drawables

Durante la rotación, Android trata los recursos Drawables de forma similar a los recursos de diseño. En este caso, el sistema obtiene los elementos Drawables de las carpetas Resources/drawable y Resources/drawable-land, respectivamente.

Por ejemplo, supongamos que el proyecto incluye una imagen denominada Monkey.png en la carpeta Resources/drawable, donde se hace referencia al objeto Drawable desde un elemento ImageView en XML de la siguiente manera:

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

Supongamos además que se incluye una versión diferente de Monkey.png en Resources/drawable-land. Al igual que con los archivos de diseño, cuando se gira el dispositivo, los cambios Drawables para la orientación dada, como se muestra a continuación:

Different version of Monkey.png shown in portrait and landscape modes

Controlar la rotación mediante programación

A veces definimos diseños en el código. Esto puede ocurrir por diversos motivos, incluidas las limitaciones técnicas, las preferencias del desarrollador, etc. Cuando agregamos controles mediante programación, una aplicación debe tener en cuenta manualmente la orientación del dispositivo, que se controla automáticamente cuando se usan recursos XML.

Agregar controles en el código

Para agregar controles mediante programación, una aplicación debe realizar los pasos siguientes:

  • Crear un diseño.
  • Establecer parámetros de diseño.
  • Crear controles.
  • Establecer parámetros de diseño de control.
  • Agregar controles a la aplicación.
  • Establecer el diseño como la vista de contenido.

Por ejemplo, considere una interfaz de usuario que consta de un único control TextView agregado a RelativeLayout, como se muestra en el código siguiente.

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);
}

Este código crea una instancia de una clase RelativeLayout y establece su propiedad LayoutParameters. La clase LayoutParams es la forma de Android de encapsular cómo se colocan los controles de forma reutilizable. Una vez creada una instancia de un diseño, se pueden crear y agregar controles. Los controles también tienen LayoutParameters, como TextView en este ejemplo. Después de crear el objeto TextView, agregarlo a RelativeLayout y configurar RelativeLayout como la vista de contenido, da como resultado la aplicación que muestra TextView como a continuación:

Increment counter button shown in both portrait and landscape modes

Detección de orientación en código

Si una aplicación intenta cargar una interfaz de usuario diferente para cada orientación cuando se llama a OnCreate (esto ocurrirá cada vez que se gira un dispositivo), debe detectar la orientación y, a continuación, cargar el código de interfaz de usuario deseado. Android tiene una clase denominada WindowManager, que se puede usar para determinar la rotación de dispositivos actual a través de la propiedad WindowManager.DefaultDisplay.Rotation, como se muestra a continuación:

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 establece TextView en la posición de 100 píxeles desde la parte superior izquierda de la pantalla, animando automáticamente al nuevo diseño, cuando se gira a horizontal, como se muestra aquí:

View state is preserved across portrait and landscape modes

Impedir el reinicio de la actividad

Además de controlar todo en OnCreate, una aplicación también puede impedir que se reinicie una actividad cuando cambia la orientación estableciendo ConfigurationChanges en ActivityAttribute como aquí:

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

Ahora, cuando se gira el dispositivo, no se reinicia la actividad. Para controlar manualmente el cambio de orientación en este caso, una actividad puede invalidar el método OnConfigurationChanged y determinar la orientación del objeto Configuration que se pasa, como en la nueva implementación de la actividad siguiente:

[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";
    }
  }
}

Aquí se inicializan los parámetros de diseño TextView's para horizontal y vertical. Las variables de clase contienen los parámetros, junto con el propio TextView, ya que la actividad no se volverá a crear cuando cambie la orientación. El código sigue usando surfaceOrientartion en OnCreate para establecer el diseño inicial de TextView. Después, OnConfigurationChanged controla todos los cambios de diseño posteriores.

Cuando ejecutamos la aplicación, Android carga los cambios en la interfaz de usuario a medida que se produce la rotación de dispositivos y no reinicia la actividad.

Impedir el reinicio de actividad para diseños declarativos

También se pueden evitar los reinicios de actividad causados por la rotación de dispositivos si definimos el diseño en XML. Por ejemplo, podemos usar este enfoque si queremos evitar un reinicio de actividad (por motivos de rendimiento, quizás) y no es necesario cargar nuevos recursos para distintas orientaciones.

Para ello, seguimos el mismo procedimiento que usamos con un diseño mediante programación. Simplemente se establece ConfigurationChanges en ActivityAttribute, como hicimos antes en CodeLayoutActivity. Cualquier código que necesite ejecutarse para el cambio de orientación se puede implementar de nuevo en el método OnConfigurationChanged.

Mantener el estado durante los cambios de orientación

Tanto si controla la rotación mediante declaración como mediante programación, todas las aplicaciones Android deben implementar las mismas técnicas para administrar el estado cuando cambia la orientación del dispositivo. La administración del estado es importante porque el sistema reinicia una actividad en ejecución cuando se gira un dispositivo Android. Android hace esto para facilitar la carga de recursos alternativos, como diseños y Drawables diseñados específicamente para una orientación determinada. Cuando se reinicia, la actividad pierde cualquier estado transitorio que pueda haber almacenado en variables de clase local. Por lo tanto, si una actividad depende del estado, debe conservar su estado en el nivel de aplicación. Una aplicación debe controlar el guardado y la restauración de cualquier estado de aplicación que quiera conservar en los cambios de orientación.

Para obtener más información sobre el estado persistente en Android, consulte la guía Ciclo de vida de la actividad.

Resumen

En este artículo se explica cómo usar las funcionalidades integradas de Android para trabajar con la rotación. En primer lugar, se explica cómo usar el sistema de recursos de Android para crear aplicaciones compatibles con la orientación. A continuación, se presenta cómo agregar controles en el código, así como cómo controlar los cambios de orientación manualmente.