Датчик ориентации экрана в WinRT

124

За последние годы компьютеры в ходе своей эволюции обзавелись новыми «органами чувств». Нет, это не сюжет нового фантастического фильма! Многие компьютеры - особенно планшетные и другие мобильные устройства — оснащены оборудованием, которое позволяет машине определить свою ориентацию в трехмерном пространстве, свое местонахождение на поверхности Земли, интенсивность внешнего освещения и даже скорость поворота компьютера в руках пользователя.

Все эти устройства объединяются под общим названием датчиков (sensors), а программный интерфейс для работы с ними находится в основном в пространстве имен Windows.Devices.Sensors, тогда как классы, при помощи которых программа определяет свое географическое местонахождение, находятся в пространстве имен Windows.Devices.Geolocation. Оборудование для позиционирования часто неформально называют GPS (по названию спутниковой системы GPS), хотя компьютер также часто может определить свое географическое положение по сетевому подключению.

В этой и следующих статьях основное внимание уделяется информации, получаемой от классов SimpleOrientationSensor, Accelerometer, Compass, Inclinometer, OrientationSensor и Geolocator. К сожалению, мне придется пропустить реже используемые классы LightSensor и Gyrometer для получения информации об освещенности и угловой скорости компьютера.

Чтобы получить максимум пользы от этого материала, следует взять компьютер, на котором запущены программы-примеры, и переместить его в пространстве. Если ваша машина разработки с системой Windows 8 прикована к столу (как и моя), возьмите планшет — например, Microsoft Surface — и разверните программы на нем в удаленном режиме.

Ориентация

Простейшим из всех классов датчиков является класс SimpleOrientationSensor; он дает программе примерное представление об ориентации в трехмерном пространстве, но без особых подробностей. Для получения экземпляра SimpleOrientationSensor вызывается статический метод:

SimpleOrientationSensor sensor = SimpleOrientationSensor.GetDefault();

Эта операция выполняется в приложении только один раз, так что код может выполняться в определении поля для предоставления доступа к этому объекту в классе.

Если метод GetDefault() возвращает null, компьютер не располагает средствами для определения своей ориентации.

Значение, описывающее текущую ориентацию, может быть в любой момент времени получено от объекта SimpleOrientationSensor:

SimpleOrientation orientation = sensor.GetCurrentOrientation();

Перечисление SimpleOrientation состоит из шести значений:

NotRotated
Rotated90DegreesCounterclockwise
Rotated180DegreesCounterclockwise
Rotated270DegreesCounterclockwise
Faceup
Facedown

Именно из-за ограниченности этой информации класс SimpleOrientationSensor называется «простым» (simple).

Также программа может получать оповещения о событиях при изменении ориентации. Назначьте обработчик для события OrientationChanged:

sensor.OrientationChanged += OnSimpleOrientationChanged;

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

Обработчик события выполняется в собственном потоке, поэтому для взаимодействия с потоком пользовательского интерфейса необходимо использовать объект CoreDispatcher:

private async void OnSimpleOrientationChanged(SimpleOrientationSensor sender,
            SimpleOrientationSensorOrientationChangedEventArgs e)
{
    await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
    {

    });
}

Аргумент события с очень длинным именем содержит свойство Orientation перечислимого типа SimpleOrientation и свойство Timestamp типа DateTimeOffset. Резонно спросить: а разве мы не располагаем информацией об ориентации устройства? Разве она не предоставляется пространством имен Windows.Graphics.Display? Разве класс DisplayProperties и его статические свойства NativeOrientation и CurrentOrientation, а также событие OrientationChanged не предоставляют информацию об ориентации? Напомню, что эти два статических свойства возвращают значения перечислимого типа DisplayOrientations: Landscape, Portrait, LandscapeFlipped, PortraitFlipped.

Классы SimpleOrientationSensor и DisplayProperties безусловно связаны, но важно понять суть этой связи: класс SimpleOrientationSensor описывает ориентацию компьютера в трехмерном пространстве. Свойство DisplayProperties.CurrentOrientation описывает, каким образом Windows компенсирует ориентацию компьютера посредством автоматического изменения ориентации окна вашей программы. Иначе говоря, SimpleOrientationSensor сообщает информацию об ориентации оборудования, a DisplayProperties.CurrentOrientation — информацию об изменении ориентации программы, которая произошла в ответ на изменение ориентации оборудования.

Проект OrientationAndOrientation поможет вам различать два вида ориентации. Файл XAML определяет несколько элементов TextBlock для вывода меток и данных:

<Page ...>

    <Grid Background="#FF1D1D1D">
        <Grid HorizontalAlignment="Center" VerticalAlignment="Center">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>

            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition Width="Auto" />
            </Grid.ColumnDefinitions>

            <TextBlock Text="SimpleOrientationSensor: " />

            <TextBlock Name="orientationSensorTextBlock"
                       Grid.Column="1"
                       TextAlignment="Right" />

            <TextBlock Text="DisplayProperties.CurrentOrientation: "
                       Grid.Row="1" />

            <TextBlock Name="displayOrientationTextBlock"
                       Grid.Row="1"
                       Grid.Column="1"
                       TextAlignment="Right" />
        </Grid>
    </Grid>
</Page>

Файл фонового кода определяет два метода с единственной целью — заданием двух элементов TextBlock во втором столбце Grid. Эти два метода вызываются как из конструктора для задания исходных значений, так и из двух обработчиков событий:

using System;
using Windows.UI.Xaml.Controls;
using Windows.Devices.Sensors;
using Windows.UI.Core;
using Windows.Graphics.Display;

namespace WinRTTestApp
{
    public sealed partial class MainPage : Page
    {
        SimpleOrientationSensor simpleOrientationSensor = SimpleOrientationSensor.GetDefault();

        public MainPage()
        {
            this.InitializeComponent();

            // Инициализация SimpleOrientationSensor
            if (simpleOrientationSensor != null)
            {
                SetOrientationSensorText(simpleOrientationSensor.GetCurrentOrientation());
                simpleOrientationSensor.OrientationChanged += OnSimpleOrientationChanged;
            }

            // Инициализация DisplayProperties
            SetDisplayOrientationText(DisplayProperties.CurrentOrientation);
            DisplayProperties.OrientationChanged += OnDisplayPropertiesOrientationChanged;
        }

        // SimpleOrientationSensor handler
        private async void OnSimpleOrientationChanged(SimpleOrientationSensor sender,
            SimpleOrientationSensorOrientationChangedEventArgs e)
        {
            await this.Dispatcher.RunAsync(
                CoreDispatcherPriority.Normal, () =>
            {
                SetOrientationSensorText(e.Orientation);
            });
        }

        private void SetOrientationSensorText(SimpleOrientation simpleOrientation)
        {
            orientationSensorTextBlock.Text = simpleOrientation.ToString();
        }

        // Обработчик DisplayProperties
        private void OnDisplayPropertiesOrientationChanged(object sender)
        {
            SetDisplayOrientationText(DisplayProperties.CurrentOrientation);
        }

        private void SetDisplayOrientationText(DisplayOrientations displayOrientation)
        {
            displayOrientationTextBlock.Text = displayOrientation.ToString();
        }
    }
}

Обратите внимание: экземпляр SimpleOrientationSensor создается как поле, но конструктор проверяет возможное значение null перед обращением к объекту.

Если запустить эту программу на планшете с основной альбомной ориентацией (то есть у которого свойство DisplayProperties.NativeOrientation возвращает DisplayOrientations.Landscape) и если вы ничего не сделали для того, чтобы помешать Windows 8 изменить ориентацию (например, поместив планшет и стыковочный узел - док-станцию), то при последовательном повороте планшета по часовой стрелке два индикатора ориентации будут связаны следующим образом:

Значения датчика ориентации (SimpleOrientationSensor) и устройства (DisplayProperties.CurrentOrientation) при альбомной ориентации
SimpleOrientationSensor DisplayProperties.CurrentOrientation
NotRotated Landscape
Rotated270DegreesCounterclockwise Portrait
Rotated180DegreesCounterclockwise LandscapeFlipped
Rotated90DegreesCounterclockwise PortraitFlipped

SimpleOrientationSensor также возвращает значения Faceup и Facedown, не имеющие аналогов в перечислении DisplayOrientations.

Хотя приведенная таблица приблизительно верна для планшета с основной альбомной ориентацией, у мобильного устройства с основной книжной ориентацией соответствия будут выглядеть так:

Значения датчика ориентации (SimpleOrientationSensor) и устройства (DisplayProperties.CurrentOrientation) при книжной ориентации
SimpleOrientationSensor DisplayProperties.CurrentOrientation
NotRotated Portrait
Rotated270DegreesCounterclockwise LandscapeFlipped
Rotated180DegreesCounterclockwise PortraitFlipped
Rotated90DegreesCounterclockwise Landscape

Кроме того, приложение может потребовать, чтобы система Windows не пыталась компенсировать изменения ориентации компьютера — либо в файле Package.appxmanifest, либо на программном уровне посредством установки свойства DisplayProperties.AutoRotationPreferences. В этом случае свойство DisplayProperties.CurrentOrientation с большой вероятностью будет оставаться неизменным во время работы приложения. На некоторых приложениях также имеется аппаратный переключатель, который может использоваться для отключения автоматического поворота. В таком случае таблица выглядит примерно так:

Значения датчика ориентации (SimpleOrientationSensor) и устройства (DisplayProperties.CurrentOrientation) при отключении автоматического поворота
SimpleOrientationSensor DisplayProperties.CurrentOrientation
NotRotated PortraitFlipped
Rotated270DegreesCounterclockwise PortraitFlipped
Rotated180DegreesCounterclockwise PortraitFlipped
Rotated90DegreesCounterclockwise PortraitFlipped

Также вы можете самостоятельно компенсировать изменение ориентации. Запретите Windows выполнять изменения ориентации, а затем используйте SimpleOrientationSensor для определения реальной ориентации компьютера. Однако следует помнить, что содержимое файла Package.appxmanifest и свойство DisplayProperties.AutoRotationPreferences — всего лишь ваши пожелания, которые Windows может проигнорировать. Если Windows изменит ориентацию экрана вопреки вашим пожеланиям, возможно, потребуется дополнительная компенсация.

Пожалуй, самым безопасным способом запрета автоматического поворота является задание свойству DisplayProperties.AutoRotationPreferences значения DisplayProperties.NativeOrientation, как мы поступим позднее.

Пройди тесты
Лучший чат для C# программистов