Обработка поворота

В этом разделе описывается обработка изменений ориентации устройства в Xamarin.Android. В нем описывается, как работать с системой ресурсов Android для автоматической загрузки ресурсов для определенной ориентации устройства, а также как программно обрабатывать изменения ориентации.

Обзор

Так как мобильные устройства легко поворачиваются, встроенная смена является стандартной функцией в мобильных операционных устройствах. Android предоставляет сложную платформу для работы с поворотом в приложениях, независимо от того, создается ли пользовательский интерфейс декларативно в XML или программно в коде. При автоматической обработке декларативных изменений макета на поворачиваемом устройстве приложение может воспользоваться тесной интеграцией с системой ресурсов Android. Для программного макета изменения должны обрабатываться вручную. Это позволяет более точно управлять средой выполнения, но за счет большей работы для разработчика. Приложение также может отказаться от перезапуска действия и управлять изменениями ориентации вручную.

В этом руководстве рассматриваются следующие разделы ориентации:

  • Декларативная смена макета — как использовать систему ресурсов Android для создания приложений с поддержкой ориентации, включая загрузку макетов и рисуемых элементов для определенных ориентаций.

  • Смена программного макета— как добавлять элементы управления программным способом, а также как обрабатывать изменения ориентации вручную.

Обработка поворота декларативно с помощью макетов

Включив файлы в папки, которые соответствуют соглашениям об именовании, Android автоматически загружает соответствующие файлы при изменении ориентации. Добавлена поддержка:

  • Ресурсы макета— указание файлов макета для каждой ориентации.

  • Ресурсы, доступные для рисования, — указание, какие рисуемые модули загружаются для каждой ориентации.

Ресурсы макета

По умолчанию файлы ANDROID XML (AXML), включенные в папку Resources/layout , используются для просмотра представлений для действия. Ресурсы этой папки используются для книжной и альбомной ориентации, если дополнительные ресурсы макета не предоставляются специально для альбомной ориентации. Рассмотрим структуру проекта, созданную по умолчанию шаблоном проекта:

Default project template structure

Этот проект создает один файл Main.axml в папке Resources/layout . При вызове метода Activity OnCreate он увеличивает представление, определенное в Main.axml, которое объявляет кнопку, как показано в XML ниже:

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

Если устройство поворачивается на альбомную ориентацию, метод действия OnCreate вызывается снова и тот же файл Main.axml раздувается, как показано на снимке экрана ниже:

Same screen but in landscape orientation

Макеты, относящиеся к ориентации

Помимо папки макета (которая по умолчанию используется для портрета и может быть явно именованным портом макета, включая именованную layout-landпапку), приложение может определить представления, необходимые в альбомной среде без каких-либо изменений кода.

Предположим, что файл Main.axml содержит следующий 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>

Если папка с именем layout-land, содержащая дополнительный файл Main.axml , добавляется в проект, то при добавлении макета в альбомном режиме будет добавлен новый файл Main.axml в Android. Рассмотрим альбомную версию файла Main.axml , содержащего следующий код (для простоты этот XML аналогичен стандартной книжной версии кода, но использует другую строку в 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>

Выполнение этого кода и поворот устройства с книжной на альбомную демонстрирует новую загрузку XML, как показано ниже:

Portrait and landscape screenshots printing the portrait mode

Ресурсы, доступные для рисования

Во время смены Android обрабатывает доступные для рисования ресурсы аналогично макету ресурсов. В этом случае система получает рисуемые элементы из папок "Ресурсы", "Рисуемые " и "Ресурсы"/ "Рисуемые " соответственно.

Например, предположим, что проект содержит изображение с именем Monkey.png в папке Resources/drawable , где на рисуемый объект ссылается из ImageView XML, как показано ниже:

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

Предположим, что другая версия Monkey.png включена в раздел "Ресурсы/рисуемые земли". Как и в файлах макета, когда устройство поворачивается, меняется нарисованное изменение заданной ориентации, как показано ниже:

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

Обработка поворота программным способом

Иногда мы определяем макеты в коде. Это может произойти по различным причинам, включая технические ограничения, предпочтения разработчика и т. д. При программном добавлении элементов управления приложение должно вручную учитывать ориентацию устройства, которая обрабатывается автоматически при использовании XML-ресурсов.

Добавление элементов управления в код

Чтобы добавить элементы управления программным способом, приложению необходимо выполнить следующие действия:

  • Создание макета.
  • Задайте параметры макета.
  • Создание элементов управления.
  • Задайте параметры макета элемента управления.
  • Добавьте элементы управления в макет.
  • Задайте макет в качестве представления содержимого.

Например, рассмотрим пользовательский интерфейс, состоящий из одного TextView элемента управления, добавленного в объект RelativeLayout, как показано в следующем коде.

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

Этот код создает экземпляр RelativeLayout класса и задает его LayoutParameters свойство. Класс LayoutParams — это способ инкапсулирования того, как элементы управления размещаются повторно. После создания экземпляра макета элементы управления можно создавать и добавлять в него. Элементы управления также имеют такие LayoutParametersэлементы управления, как TextView в этом примере. TextView После создания добавьте его в RelativeLayout представление содержимого и задав RelativeLayout его в качестве результата в приложении, отображая TextView его, как показано ниже:

Increment counter button shown in both portrait and landscape modes

Обнаружение ориентации в коде

Если приложение пытается загрузить другой пользовательский интерфейс для каждой ориентации при OnCreate вызове (это происходит при каждом повороте устройства), оно должно обнаружить ориентацию, а затем загрузить нужный код пользовательского интерфейса. Android имеет класс с именем WindowManager, который можно использовать для определения текущего поворота устройства с помощью WindowManager.DefaultDisplay.Rotation свойства, как показано ниже:

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

Этот код задает TextView расположение 100 пикселей в левом верхнем углу экрана, автоматически анимируя новый макет, при повороте в альбом, как показано ниже:

View state is preserved across portrait and landscape modes

Предотвращение перезапуска действия

Помимо обработки всего в OnCreateприложении, приложение также может предотвратить перезапуск действия при изменении ориентации, установив ConfigurationChanges в ActivityAttribute следующем виде:

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

Теперь, когда устройство поворачивается, действие не перезапускается. Чтобы вручную обработать изменение ориентации в этом случае, действие может переопределить OnConfigurationChanged метод и определить ориентацию от Configuration объекта, переданного, как в новой реализации действия ниже:

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

TextView's Здесь параметры макета инициализированы как для альбомной, так и книжной. Переменные класса содержат параметры вместе с TextView самим собой, так как действие не будет повторно создано при изменении ориентации. Код по-прежнему используется surfaceOrientartion для OnCreate задания начального макета для TextView. После этого OnConfigurationChanged обрабатывает все последующие изменения макета.

При запуске приложения Android загружает изменения пользовательского интерфейса при смене устройства и не перезапускает действие.

Предотвращение перезапуска действия для декларативных макетов

Перезапуски действий, вызванные поворотом устройства, также могут быть запрещены, если мы определим макет в XML. Например, мы можем использовать этот подход, если мы хотим предотвратить перезапуск действия (по соображениям производительности, возможно), и нам не нужно загружать новые ресурсы для разных ориентаций.

Для этого мы следуйте той же процедуре, которую мы используем с программным макетом. Просто установить ConfigurationChanges в ActivityAttribute, как мы сделали в предыдущих CodeLayoutActivity . Любой код, который требуется запустить для изменения ориентации, можно снова реализовать в методе OnConfigurationChanged .

Сохранение состояния во время изменений ориентации

Независимо от того, следует ли обрабатывать поворот декларативно или программно, все приложения Android должны реализовать те же методы управления состоянием при изменении ориентации устройства. Управление состоянием важно, так как система перезапускает запущенное действие при смене устройства Android. Android делает это, чтобы упростить загрузку альтернативных ресурсов, таких как макеты и рисуемые элементы, разработанные специально для определенной ориентации. При перезапуске действие теряет любое временное состояние, которое может храниться в переменных локального класса. Таким образом, если действие зависит от состояния, оно должно сохранить свое состояние на уровне приложения. Приложение должно обрабатывать сохранение и восстановление любого состояния приложения, которое он хочет сохранить во время изменений ориентации.

Дополнительные сведения о сохранении состояния в Android см. в руководстве по жизненному циклу действий.

Итоги

В этой статье описано, как использовать встроенные возможности Android для работы с поворотом. Во-первых, он объяснил, как использовать систему ресурсов Android для создания приложений с учетом ориентации. Затем он представил, как добавить элементы управления в код, а также как обрабатывать изменения ориентации вручную.