UWP beginner: Адаптивный дизайн (VB.NET + C#)

в 10:27, , рубрики: C#, microsoft, uwp, vb.net, windows, XAML, адаптивный дизайн, Блог компании Microsoft, разработка под windows, универсальная платформа

Мы продолжаем историю по разработке под универсальную платформу Windows (UWP). Тема этой статьи родилась из-за большого количества вопросов по ней к автору от независимых разработчиков UWP-приложений. Для кого-то она может показаться вполне очевидной, но мы надеемся, что вы найдете в статье полезный лайфхак по адаптивному дизайну в UWP.

Статья подготовлена совместно с активным участником сообщества Microsoft Developer, Алексеем Плотниковым, и менеджером по работе с техническими аудиториями, Стасом Павловым.

UWP beginner: Адаптивный дизайн (VB.NET + C#) - 1

Первая истина, которую должен запомнить любой разработчик UWP-приложений – «мысли как пользователь». Вторая истина – «без адаптивного дизайна приложение обречено на медленную и мучительную смерть», так как количество устройств и расширений экранов просто огромно. Первое и самое частое решение для создания адаптивного дизайна, которое сразу приходит в голову – это адаптивные триггеры (AdaptiveTrigger).

Писать очередную статью о них конечно не стоит, но для понимания дальнейших рассуждений, небольшая справка не помешает. Возьмем самый распространенный пример использования адаптивных триггеров:

<Grid x:Name="LayoutRoot">
    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup>
            <VisualState x:Name="WideState">
                <VisualState.StateTriggers>
                    <AdaptiveTrigger MinWindowWidth="600" />
                </VisualState.StateTriggers>
                <VisualState.Setters>
                    <Setter Target="LayoutRoot.Background" Value="Green" />
                </VisualState.Setters>
            </VisualState>
            <VisualState x:Name="NarrowState">
                <VisualState.StateTriggers>
                    <AdaptiveTrigger MinWindowWidth="0" />
                </VisualState.StateTriggers>
                <VisualState.Setters>
                    <Setter Target="LayoutRoot.Background" Value="Red" />
                </VisualState.Setters>
            </VisualState>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
</Grid>

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

UWP beginner: Адаптивный дизайн (VB.NET + C#) - 2

Если вы не понимаете, что тут происходит, то для начала вам нужно познакомиться с понятием адаптивных триггеров. Хороший курс по адаптивному дизайну и адаптивным триггерами доступен в Microsoft Virtual Academy. К слову, не лишним будет пройти его весь – это существенно расширит ваши знания в вопросе разработки UWP-приложений.

Приятная особенность адаптивных триггеров заключается в том, что нам не нужно выполнять никаких действий в коде и это упрощает разделение труда между дизайнером и разработчиком, и в дополнении делает проект независимым от используемого языка программирования (C# или VB.Net).

С первого взгляда все может показаться крайне простым и очевидным, но начиная применять полученные выше знания, вы столкнетесь с целым рядом вытекающих вопросов и сложностей. Первое, что приходит в голову – «А какую ширину окна выбрать для, того, чтобы на экране телефона видеть подходящее построение макета?» Этот вопрос может показаться глупым, а ответ вполне простым – выберем наибольшее значение из минимальных. Например, в коде выше задана ширина 600 px, а в других примерах и подобных статьях часто встречается 720 px. Давайте зададим 720 px, и тогда все устройства ширина экрана которых будет либо меньше, либо равна 720 px, будут иметь нужное нам представление. И вы будете правы ровно до того момента, пока не повернете ваше устройство в горизонтальное положение (помните первую истину?). Для кого-то, возможно, это будет открытием, но существует устройства, в которых высота экрана меньше 720 px. Повернув устройство в горизонтальное положение, высота превратится в ширину, триггер выдаст нам представление для узкого по ширине окна, что полностью сломает наш макет.

Для полного понимания картины рассмотрим пример выше со следующей модификацией:

   <Grid x:Name="GlobalGrid">
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup>
                <VisualState x:Name="WideState">
                    <VisualState.StateTriggers>
                        <AdaptiveTrigger MinWindowWidth="720" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Target="MyStackPanel.Orientation" Value="Horizontal" />
                    </VisualState.Setters>
                </VisualState>
                <VisualState x:Name="NarrowState">
                    <VisualState.StateTriggers>
                        <AdaptiveTrigger MinWindowWidth="0" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Target="MyStackPanel.Orientation" Value="Vertical" />
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
        <StackPanel x:Name="MyStackPanel" HorizontalAlignment="Center" VerticalAlignment="Center">
            <TextBlock Text="77" FontSize="100"/>
            <TextBlock Text="77" FontSize="100"/>
            <TextBlock Text="77" FontSize="100"/>
        </StackPanel>
    </Grid>

Запустите проект на самом первом эмуляторе из списка отладки «Mobile Emulator 10.xxx. WVGA 4 inch 512MB» и переведите его в горизонтальный режим. Как видите StackPanel остался в вертикальной ориентации, хотя более логичным было бы видеть горизонтальную. Триггер не отработал, потому что и ширина, и высота устройства в нашем случае меньше 720 px.

Хорошо, тогда давайте выберем меньшее значение для триггера, например, 500. Но и тут мы получаем ту же проблему, но уже на современных устройствах, где ширина экрана в вертикальной ориентации больше 500. На самом деле, чтобы воспроизвести проблему, нам даже не нужен эмулятор устройства с маленьким разрешением. Достаточно запустить приложение на ПК и сделать размер окна минимальным по высоте, а затем ширину уменьшить до достижения значения из триггера. StackPanel снова выглядит не так как нам хотелось бы. Кстати, если эта проблема вам кажется не значительной, то вспомните, что в Windows 10 на ПК мы получили возможность закреплять сразу четыре окна на экране, что дает им как раз такие пропорции.

UWP beginner: Адаптивный дизайн (VB.NET + C#) - 3

Перейдем к решению. Показанный выше пример со StackPanel – это не просто пример ради примера, а главная часть рабочего проекта, в котором обозначенная проблема была краеугольным камнем. Для понимания, о чем идет речь, рассмотрим приложение «Рубль Life» от соавтора статьи, в котором отображаются курсы валют в реальном времени.

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

UWP beginner: Адаптивный дизайн (VB.NET + C#) - 4

Как такой поход реализуется на практике? Довольно просто. Все что нужно – это отреагировать на событие изменения размера в головном Grid и сохранить изменение в переменную/свойство, которые затем будем использовать при построении макета.

VB.NET

Private Sub GlobalGrid_SizeChanged(sender As Object, e As SizeChangedEventArgs)
        If e.NewSize.Height > e.NewSize.Width Then
            MyApplicationData.Orientation = Horizontal
        Else
            MyApplicationData.Orientation = Vertical
        End If
End Sub

C#

  private void GlobalGrid_SizeChanged(object sender, SizeChangedEventArgs e)
        {
      
            if (e.NewSize.Height > e.NewSize.Width)
                MyApplicationData.Orientation = Orientation.Horizontal;
            else
                MyApplicationData.Orientation = Orientation.Vertical;
        }

В данном примере соответствующее значение присваивается свойству собственного класса. Далее все что нужно, это осуществить привязку свойства Orientation у StackPanel к соответствующему свойству в нашем классе. Вы так же можете сохранить преимущества задания значений в XAML и вместо установки свойств класса, осуществить переход к нужному визуальному состоянию с помощью VisualStateManager.GoToState().

Стоит отметить еще одну хитрость, которая превращает данное решение в идеальное, для подобных проектов. Что бы не гадать с размерами шрифтов для разных устройств и разрешений экрана, достаточно обернуть StackPanel в Viewbox и тогда на любом устройстве от самых маленьких телефонов, до больших телевизоров, вы будете видеть приятную глазу картину.

Кстати при изучении приложения «Рубль Life», можно так же заметить, что при изменении соотношения сторон, происходит и другие изменения в интерфейсе. Например, если запустить приложение на ПК и развернуть на полный экран, то кнопки приложения будут располагаться сверху и снизу, занимая свободное пространство, а если сжать окно, то кнопки скроются и отобразится CommandBar с набором тех же действий. Напротив, если запустить приложение на телефоне, то мы сначала увидим представление с CommandBar, а повернув телефон, представление с кнопками. Такой подход является воистину универсальным и позволяет пользователю получить предпочтительную версию интерфейса, просто изменив размер окна или повернув устройство.

Что же со StackPanel это действительно работает, скажете вы, а как быть с более сложными интерфейсами? Вы удивитесь, но такой подход можно применять и на более сложных интерфейсах комбинированием реакции на изменение соотношения сторон и использованием адаптивных триггеров. Например, у вас есть группа объектов, которая формирует высоту в 600 px. Как бы вы не реагировали на изменения соотношения сторон, это не поможет вам сделать эту группу объектов, удобной для восприятия, если высота окна или высота экрана телефона в горизонтальном положении меньше 600 px. Как раз для таких ситуаций и нужны адаптивные триггеры. Вы можете перестроить группу так, чтобы объекты стали занимать меньшую высоту, либо скрыть/переместить менее значимые объекты.

В завершении стоит добавить, что главная идея построения интерфейса в ответ на изменение соотношения сторон основана на том, что нам не всегда нужно кардинально изменять интерфейс для разных типов устройств и порой достаточно создать два варианта представления для ситуаций, которые мы назвали «горизонтальным» и «вертикальным» расположением окна/устройства. Например, стандартное приложение для Windows 10 «Почта» имеет разные XAML-файлы под версию для ПК и телефонов, тогда как описанная выше концепция также смотрелась бы вполне логично. В частности, в версии для телефонов, повернув устройство в горизонтально положение, интерфейс не изменится, тогда как места вполне достаточно для отображения как списка писем, так и содержания письма по типу версии для ПК. В дальнейших статьях, мы будем постоянно прибегать к примерам использования такой стратегии, что позволит вам закрепить данный материал, либо напротив выработать собственную методику построения универсальных и адаптивных интерфейсов.

Первая статья из серии «UWP beginner»: Заголовок окна в приложениях.

Автор: Microsoft

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js