После того, как на Google IO 2019 я увидел доклад про Android Jetpack Compose, захотелось сразу же его попробовать. Тем более, что подход, реализованный в нём, очень напомнил Flutter, которым я интересовался ранее.



Сама библиотека Compose находится в пре-альфа стадии, поэтому доступно не так много документации и статей про нее. Далее я буду полагаться на несколько ресурсов, которые мне удалось найти, плюс открытые исходники библиотеки.


Вот эти ресурсы:



Что такое Android Jetpack Compose?


Раньше весь UI в Android был основан на классе View. Так повелось с первых дней Android. И в связи с этим накопилось множество легаси и архитектурных недостатков, которые могли бы быть улучшены. Но сделать это достаточно сложно, не сломав весь код, написанный на их основе.


За последние годы появилось множество новых концептов в мире клиентских приложений (включая веяния Frontend-а), поэтому команда Google пошла радикальным путём и переписала весь UI-уровень в Android с нуля. Так и появилась библиотека Android Jetpack Compose, включающая в себя концептуальные приёмы из React, Litho, Vue, Flutter и многих других.


Давайте пройдемся по некоторым особенностям существующего UI и сравним его с Compose.


1. Независимость от релизов Android


Существующий UI тесно связан с платформой. Когда появились первые компоненты Material Design, они работали только с Android 5 (API21) и выше. Для работы на старых версиях системы необходимо использовать Support Library.


Compose же входит в состав Jetpack, что делает его независимым от версий системы и возможным для использования даже в старых версиях Android (как минимум с API21).


2. Весь API на Kotlin


Раньше приходилось иметь дело с разными файлами, чтобы сделать UI. Мы описывали разметку в xml, а затем использовали Java/Kotlin код, чтобы заставить ее работать. Затем мы снова возвращались в другие xml-файлы для того чтобы задать темы, анимацию, навигацию,… И даже пытались писать код в xml (Data Binding).


Использование Kotlin позволяет писать UI в декларативном стиле прямо в коде вместо xml.


3. Composable = Композитный: использование композиции вместо наследования


Создание кастомных элементов UI может быть довольно громоздким. Нам необходимо унаследоваться от View или его потомка и позаботиться о многих важных свойствах перед тем, как он правильно заведется. Например, класс TextView содержит около 30 тысяч строк Java-кода. Это связано с тем, что он содержит множество лишней логики внутри себя, которую наследуют элементы-потомки.


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


Padding как нельзя лучше подойдет для иллюстрации того, о чем речь:


В существующем UI для того, чтобы отрисовать TextView c отступами в 30dp:


image

нам нужно написать следующий код:


<TextView android:id="@+id/simpleTextView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@color/cyan"
    android:padding="30dp" <------------------------ NOTE THIS
    android:text="Drag or tap on the seek bar" />

Это означает, что где-то внутри TextView.java или его суперклассов содержится логика, которая знает, как посчитать и отрисовать отступы.


Давайте посмотрим, как можно сделать то же самое в Compose:


// note: the cyan background color is omitted for now to keep it simple
Padding(30.dp) {
    Text("Drag or tap on the seek bar")
}

Изменения
TextView стал просто Text(). Свойство android:padding превратилось в Padding, который оборачивает Text.


Преимущества
Таким образом, Text отвечает только за отрисовку непосредственно текста. Он не знает про то, как считать отступы. С другой стороны, Padding отвечает только за отступы и ничего больше. Он может быть использован вокруг любого другого элемента.


4. Однонаправленный поток данных


Однонаправленный поток данных является важным концептом, если мы говорим, например, про управление состоянием CheckBox в существующей системе UI. Когда пользователь тапает на CheckBox, его состояние становится checked = true: класс обновляет состояние View и вызывает callback из кода, который следит за изменением состояния.


Затем в самом коде, например, во ViewModel, вам нужно обновить соответствующую переменную state. Теперь у вас есть две копии нажатого состояния, которые могут создать проблемы. Например, изменение значения переменной state внутри ViewModel вызовет обновление CheckBox, что может закончиться бесконечным циклом. Чтобы избежать этого, нам придется придумывать какой-то костыль.


Использование Compose поможет решить эти проблемы, так как в его основе заложен принцип однонаправленности. Изменение состояния будет обрабатываться внутри фреймворка: мы просто отдаем модель данных внутрь. Кроме того, компонент в Compose теперь не меняет свое состояние самостоятельно. Вместо этого он только вызывает callback, и теперь это задача приложения изменить UI.


5. Улучшение отладки


Так как теперь весь UI написан на Kotlin, теперь можно дебажить UI. Я не попробовал это сам, но в подкасте говорили, что в Compose работают дебаггер и точки остановки.


Хватит слов, покажите код


Я знаю, хочется поскорее увидеть, как выглядит UI в коде (спойлер: очень похоже на Flutter, если вы пробовали писать на нем).


Мы начнем с создания нескольких простых View, затем сравним как они выглядят в существующем UI и в Compose.


1. FrameLayout vs Wrap + Padding + Background


Переиспользуем наш пример выше и попробуем сделать этот TextView с отступами в 30dp и бирюзовым фоном:


`TextView` с отступами в `30dp` и бирюзовым фоном

Существующий UI:


<TextView android:id="@+id/simpleTextView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@color/cyan" <-------------- NOTE THIS
    android:padding="30dp" <------------------------ AND THIS
    android:text="Drag or tap on the seek bar" />

Теперь посмотрим на код, который делает то же самое в Compose:


@Composable
fun MyText() {
    Wrap {
        Padding(30.dp) {
            DrawRectangle(color = Color.Cyan)
            Text("Drag or tap on the seek bar")
        }
    }
}

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


  • DrawRectangle отрисовывает фон
  • Padding отрисовывает отступы
  • Wrap — функция, которая накладывает параметры друг на друга, как FrameLayout.

Легко. Но немного отличается от существующей UI-системы, к который мы все привыкли.


2. Вертикальный LinearLayout vs Column


Теперь попробуем сделать что-то эквивалентное нашему старому доброму LinearLayout.
Чтобы поместить два элемента один под другим, как на картинке ниже, мы можем использовать Column:


Два элемента один под другим

Код будет выглядеть так:


@Composable
fun FormDemo() {
   Column(crossAxisAlignment = CrossAxisAlignment.Start) {
      Text("Click the button below: ")
      Button(text = "Next")
   }
}

Вложенные в Column элемент будут расположены вертикально друг под другом.


2a. Отступы


Вероятно, вы заметили, что текст и кнопка расположены слишком близко к краю. Поэтому добавим Padding.


@Composable
fun FormDemo() {
    Padding(10.dp) { // Новый отступ
        Column(crossAxisAlignment = CrossAxisAlignment.Start) {
            Text("Click the button below: ")
            Button(text = "Next")
        }
    }
}

Выглядит лучше:


Два элемента один под другим с отступом

2b. Интервалы


Мы можем также добавить немного отступов между Text и Button:


@Composable
fun FormDemo() {
    Padding(10.dp) {
        Column(crossAxisAlignment = CrossAxisAlignment.Start) {
            Text("Click the button below: ")
            HeightSpacer(10.dp) // Новый вертикальный интервал
            Button(text = "Next")
        }
    }
}

Как выглядит наш экран теперь:


Два элемента один под другим с отступом и интервалом

2c. Горизонтальный LinearLayout vs Row


Поместим вторую кнопку рядом с первой:


Добавили вторую кнопку

Код для этого:


@Composable
fun FormDemo() {
    Padding(10.dp) {
        Column(crossAxisAlignment = CrossAxisAlignment.Start) {
            Text("Click the button below: ")
            HeightSpacer(10.dp)
            Row { // Новый ряд
                Button(text = "Back") // Новая кнопка
                WidthSpacer(10.dp) // Новый горизонтальный интервал
                Button(text = "Next")
            }
        }
    }
}

Внутри Row две кнопки будут расположены горизонтально. WidthSpacer добавляет расстояние между ними.


2d. Gravity vs Alignment


Выровняем наши элементы по центру, как это делает gravity в текущем UI. Чтобы показать diff, я закомментирую старые строки и заменю их новыми:


@Composable
fun FormDemo() {
    Padding(10.dp) {
//      Column(crossAxisAlignment = CrossAxisAlignment.Start) {
        Column(crossAxisAlignment = CrossAxisAlignment.Center) { // центрирование
            Text("Click the button below: ")
            HeightSpacer(10.dp)
//          Row {
            Row(mainAxisSize = FlexSize.Min) { // ограничиваем размер элемента
                Button(text = "Back")
                WidthSpacer(10.dp)
                Button(text = "Next")
            }
        }
    }
}

У нас получится:


Центральное выравнивание

С crossAxisAlignment = CrossAxisAlignment.Center вложенные элементы будут выравнены по горизонтали по центру. Мы должны также выставить Row параметр mainAxisSize = FlexSize.Min, похожий по поведению на layout_width = wrap_content, чтобы он не растягивался по всему экрану из-за дефолтного mainAxisSize = FlexSize.Max, который ведет себя как layout_width = match_parent.


2d. Замечание


Из того, что мы видели в примерах выше, можно заметить, что все элементы строятся композитно из отдельных функций: padding — отдельная функция, spacer — отдельная функция, вместо того, чтобы быть свойствами внутри Text, Button или Column.


Более сложные элементы, такие как RecyclerView или ConstraintLayout находятся в разработке: поэтому я не смог найти пример с ними в демонстрационных исходниках.


3.Стили и темы


Вы, вероятно, заметили, что кнопки выше по умолчанию фиолетовые. Это происходит потому, что они используют стили по умолчанию. Рассмотрим, как работают стили в Compose.


В примерах выше FormDemo помечена аннотацией @Composable. Теперь я покажу, как этот элемент используется в Activity:


override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
        CraneWrapper{
            MaterialTheme {
                FormDemo()
            }
        }
    }
}

Вместо функции setContentView() мы используем setContent() — функция-расширение из библиотеки Compose.kt.


CraneWrapper содержит дерево Compose и предоставляет доступ к Context, Density, FocusManager и TextInputService.


MaterialTheme позволяет кастомизировать тему для элементов.


Например, я могу изменить основной цвет темы (primary color) на каштановый следующим образом:


override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
        CraneWrapper{
//          MaterialTheme {
            MaterialTheme(colors = MaterialColors(primary = Color.Maroon)) {
                FormDemo()
            }
        }
    }
}

Теперь наш экран будет выглядеть так:


Maroon as Primary Color

Другие цвета и шрифты, которы можно поменять: MaterialTheme.kt#57


Rally Activity содержит хороший пример, как можно кастомизировать тему: source code to RallyTheme.kt


Что посмотреть/почитать


Если вы хотите большего, вы можете собрать проект-образец по инструкции тут.


Как пишут пользователи Windows, сейчас не существует официального способа запустить Compose, но есть неофициальный гайд из kotlinlang Slack.


Вопросы про Compose можно задать разработчикам в канале #compose kotlinlang Slack.


Оставляйте другие ссылки в комментариях — самые полезные добавлю сюда.


Выводы


Разработка этой библиотеки идет полным ходом, поэтому любые интерфейсы, показанные здесь могут быть изменены. Остается еще множество вещей, про которые можно узнать в исходном коде, как например @Model и Unidirectional data flow (однонаправленный поток данных). Возможно, это тема для будущих статей.

Комментарии (27)


  1. QeGle
    24.07.2019 13:19

    Первое впечатление — синтаксис практически как во Flatter'е. Интересно, будет ли в будущем связка с view model вместо state? И как будет перерисовываться весь ui при изменении state, по частям или целиком?


    1. AndreySBer Автор
      24.07.2019 20:40

      Как минимум сейчас есть аннотация @Model, которая превращает класс в observable источник данных для Compose. Похоже на scoped model в Flutter. Посмотреть пример использования можно в исходниках


    1. sputnic
      27.07.2019 10:22

      Я смотрел исходники после io — там в некоторых кусках кода даже комментарии флаттеровские остались — просто дарт в котлин переписали


      1. AndreySBer Автор
        27.07.2019 10:23

        Слышал об этом тоже, но не стал тут писать, не проверив


  1. saag
    24.07.2019 18:11

    "10.dp" — это влияние php?:-)


    1. AndreySBer Автор
      24.07.2019 20:36

      Просто extension-функция над Int, которая преобразовывает его в инстанс дата класса Dp. Исходники Dp.kt


  1. Wagok
    25.07.2019 11:16

    А как будет происходить визуальное редактирование и вообще возможно ли оно при таком подходе?


    1. denonlink
      25.07.2019 21:46

      Не пользуюсь визуальным редактором 7 лет из 7.5, что я в Android-разработке. Намного быстрей писать все в XML руками. А еще, часто приходится фиксить UI-баги после тех, кто все делает в редакторе, так как редактор не дает полного понимания ПОЧЕМУ оно именно так отображается и как поведет себя в случае изменений контента вьюх. Ненавижу, когда случайно что-то дернешь в эдиторе и оно покосячит выстроенную XML-разметку. А еще терпеть не могу, когда сложный UI целиком делают в одном ConstraintLayout. Это долго, медленно и тяжело заводится и не дает никаких преимуществ, кроме ЧСВ писавшего.

      Декларативный стиль написания, описанный в статье, просто крут, скорей бы его довели до продакшена.


      1. Wagok
        25.07.2019 22:42

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


        1. denonlink
          26.07.2019 17:24

          Согласен, только это не редактор, а превьюха.


      1. evseev
        25.07.2019 23:17

        Лично я не вижу ничего страшного в том, что-бы что-то бросить в редакторе, а потом подправить руками. Я не все наизусть помню. Но по большому счеты вы правы. Чем сложнее UI, тем проще сделать руками.


        Но вот чем плохо все делать внутри одного констрейна я не очень понимаю. Не могли бы вы развить эту мысль?


        1. denonlink
          26.07.2019 17:38

          Если есть возможность написать все приложение в одном классе — почему так не делают? Юзают же зачем-то разбивку на классы и компоненты.

          ConstraintLayout продают как универсальное решение, но любое универсальное решение всегда работает хуже, чем специфичное.

          1. В нем нужно писать больше кода. Намного больше.
          2. Я до сих пор не понимаю как он обсчитывает Chains и каждый раз скрещиваю пальцы (точней скрещивал, т. к. на том проекте, где меня все заставляли в констрейнте делать, я уже не работаю). Я не отношусь к категории тех, кому тяжело дается изучение нового, но если даже меня это все выбивает из равновестя — явно что-то перемудрили со сложностью.
          3. При добавлении элемента в середину часто приходтся перелопачивать весь лейаут, так как он просто ломается. Особенно весело это, когда его писал кто-то другой и ты сидишь и высчитываешь «а как же это все было сделано».
          4. Изменение свойств групп вьюх. Когда какой-то элемент на экране — вьюгруппа — с ней легко работать: видимость, положение и т. п. Когда это просто некое множество связанных элементов внутри констрейнта — с такой группой невозможно работать нормально как с однми целым.
          Вообщем простые вещи, которые делаются с помощью вложенных лейаутов, превращаются в танцы с бубном в ConstraintLayout. Про производительность — экономить миллисекунды при просчете ценой кучи человеко-часов отладки и багами — так себе идея.

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


          1. evseev
            26.07.2019 18:36

            Если есть возможность написать все приложение в одном классе — почему так не делают? Юзают же зачем-то разбивку на классы и компоненты.

            Contstraint как раз и разбивает на экраны. Дробление ведь то же имеет свой разумный предел. Скажем мы ведь можем для каждой переменной создавать свой класс. Но не думаю, что кто-то так делает.

            1. Намного больше это 2-3 лишних параметра? Как по мне не сильно большая плата за стабильность и управляемость.

            2. Даже не знаю что сказать. Полно статей с анимацией как это все работает. Как это можно не понять? Вы точно доки читали? Или каждый раз угадывали?

            3. Так сложилось исторически, что в нашей команде сложной разметкой занимаюсь именно я. И у меня еще ни разу не было такого, что бы при добавлении в любое место элемента все-все сломалось. Конечно правки для соседних или связанных элементов делать приходится. Но ради предсказуемости результата я на это готов.

            4. Вполне логично, что разные элементы это разные элементы. Но сказать, что «невозможно работать нормально как с однми целым» это вы сильно перегибаете. Конечно когда в нашем распоряжении не было MVVM, LiveData, DataBinding это было несколько муторно. И то не было ничего невозможного. Но сейчас просто привязав видимость элементов к LiveData мы может управлять ими как группой. Мы можем даже пойти дальше. Мы можем создать любые кастомные параметры, которые на основе той-же LiveData могут реализовывать любую сколь угодно сложную логику. Например пропадать по очереди и перекрашиваться при этом в розовый. И все это будет делаться присвоением всего-лишь одного значения.

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


            1. denonlink
              26.07.2019 21:15

              Видимо, мы с вами думаем по разному. Все что угодно можно сделать в констрейнте, но в 80% случаев он не нужен и все проще сделать без него.

              Дробление ведь то же имеет свой разумный предел.


              Конечно имеет. Но с ConstraintLayout нам объявили, что теперь дробления в пределах экрана нет вообще.

              Скажем мы ведь можем для каждой переменной создавать свой класс. Но не думаю, что кто-то так делает.


              Делают. Но не я.

              1. Намного больше это 2-3 лишних параметра? Как по мне не сильно большая плата за стабильность и управляемость.


              2-3 лишних парамента в каждом элементе. А стабильность и управляемость не добавляется, а убавляется из-за чрезмерной сложности.

              2. Даже не знаю что сказать. Полно статей с анимацией как это все работает. Как это можно не понять? Вы точно доки читали? Или каждый раз угадывали?


              Читал доки. Читал статьи. Так и не понял в чем смысл этим всем мучаться если есть LinearLayout.

              3. Так сложилось исторически, что в нашей команде сложной разметкой занимаюсь именно я. И у меня еще ни разу не было такого, что бы при добавлении в любое место элемента все-все сломалось. Конечно правки для соседних или связанных элементов делать приходится. Но ради предсказуемости результата я на это готов.


              При добавлении элемента ничего не ломается, а вот когда вы начинаете менять constraints чтобы вписать этот элемент где-то посередине — все летит в трубу. Да, оно потом чинится, но зачем мучаться, если есть LinearLayout?

              4. Вполне логично, что разные элементы это разные элементы. Но сказать, что «невозможно работать нормально как с однми целым» это вы сильно перегибаете. Конечно когда в нашем распоряжении не было MVVM, LiveData, DataBinding ...


              Конечно, биднить видимость к 10 вьюхам намного лучше чем к одному лейауту, который хранит все связанные элементы. А потом, когда вы добавите в группу внутри ConstraintLayout еще 1 элемент и забудете прибиндить видимость — вам тестеры зыведут баг. Сколько человека-часов тратится на такую вот фигню только потому что «ConstraintLayout это круто и его надо юзать везде» я не знаю. А ведь юнит-тест то проходит, потому что он не проверяет правильность биндинга.

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


              1. evseev
                26.07.2019 23:50

                Видимо, мы с вами думаем по разному. Все что угодно можно сделать в констрейнте, но в 80% случаев он не нужен и все проще сделать без него.

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

                Конечно имеет. Но с ConstraintLayout нам объявили, что теперь дробления в пределах экрана нет вообще.

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

                2-3 лишних парамента в каждом элементе. А стабильность и управляемость не добавляется, а убавляется из-за чрезмерной сложности.

                Опять сложность. Вы бусики видели? Тут ведь то же самое. Дикари справляются с этой сложностью, а вам тяжело? Серьезно?

                Читал доки. Читал статьи. Так и не понял в чем смысл этим всем мучаться если есть LinearLayout.

                Да что уж там. Зачем вообще это все если есть ASM.

                При добавлении элемента ничего не ломается, а вот когда вы начинаете менять constraints чтобы вписать этот элемент где-то посередине — все летит в трубу. Да, оно потом чинится, но зачем мучаться, если есть LinearLayout?

                Нет. Ничего не ломается. Именно потому LL и не нужен.

                Конечно, биднить видимость к 10 вьюхам намного лучше чем к одному лейауту, который хранит все связанные элементы.

                Хм. А почему вы выкинули из обсуждения фразу :«пропадать по очереди и перекрашиваться при этом в розовый»? Или это то же слишком сложно? Не знаю как у вас, а у нас обычно реализуют то, что придумал дизайнер и согласовали с заказчиком. И если гибкость уже заложена, то поддерживать проект будет значительно проще. Допустим вы решили все положить в LL. На начальном этапе все работает. А потом вдруг кому-то пришло в светлую голову, что нужно что бы пропадали не все элементы, а 1,2,4 и 7. А во втором ряду при этом сонхронно со 2 пропадал и его второй. И это только в первом случае. Во втором, третьем и четвертом все должно быть совсем по-другому. Вот теперь вам нужно будет все переделать. И если вдруг решат, что все не так, то нужно будет опять все переделать. Вот тогда все и поплывет. Да еще и все попробуй отладить.

                А потом, когда вы добавите в группу внутри ConstraintLayout еще 1 элемент и забудете прибиндить видимость — вам тестеры зыведут баг.

                А почему только это забуду? Давайте я еще и забуду LiveData проинициализировать. Ну что бы оно еще и рухнуло. Ну и в базу данных данные забуду положить. А еще когда параметр прописывал вписал не от видимости, а от чего-то другого. Да, когда констрейн подключал я еще библиотеку перепутал и старючую альфу вкатил. Я понимаю, что программисты почти всегда идут по положительному сценарию. Но если ты дописал сокрытие элементов, то ведь жмакнешь кнопочку что бы хоть одним глазиком посмотреть как работает. Ну вот не верю, что кто-то вот так неглядя свою работу тестерам передает. И ладно-бы LL был непогрешимым. А так добавили вы элемент и проверили на устройстве с высокой плотностью пикселей. А тестер взял да и проверил на каком-то стареньком или крайне дешевом телефоне. А влазит-то не все. И ведь то же баг заведет.

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

                Судя по всему ConstraintLayout применять для разметки уместнее всего. Он сочетает в себе гибкость и быстроту. Осталось дело за малым. Вдумчиво прочитать документацию и понять, что проще уже некуда. И во время создания интерфейса немного подумать и не метаться с привязками. И тогда все изменения будут вносится быстро и просто. Ну или продолжать ворчать и обливаться холодным потом когда возникает надобность подправить что-то с существующем констрейне.


      1. AndreySBer Автор
        26.07.2019 11:37

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


        • накидывания элементов с обязательными атрибутами,
        • выравнивания в Constraint (хотя для меня это было не очевидно до просмотра курса от Google),
        • просмотра того, что получилось (не работает со сложными экранами и кастомными view).

        При этом, безусловно, нужно всегда проверять, что получилось в xml (это кстати так же рекомендуют в курсе выше). Да и сложные моменты иногда проще сделать в xml.


      1. AndreySBer Автор
        26.07.2019 11:41

        А еще терпеть не могу, когда сложный UI целиком делают в одном ConstraintLayout.

        Если есть такая возможность, почему бы нет? Constraint Layout как раз и придумали для уменьшения вложенности элементов и повышения скорости их обсчёта.



    1. AndreySBer Автор
      26.07.2019 11:28

      Скорее всего примерно так, как сейчас это происходи в плагине для Android Studio для Flutter.
      Можно в коде выделить элемент и нажать кнопочку Обернуть в Padding или Обернуть в Column, и так далее.
      Вопрос с визульным редактором для просмотра (хотя бы приблизительно) того, что получилось, довольно интересный. В Flutter говорят — ребята, вам он не нужен, потому что у нас изменения в экране пересобираются за пару секунд, при этом вы остаетесь на том же экране, не нужно каждый раз доходить до него вглубь.
      В нативном Android с этим сложнее, потому что и Instant Run намного дольше, и не всегда он работает правильно.


      В то же время визуальный просмотр в Android уже достаточно близок к реальности (вкладка Preview). Использовал несколько раз для показа граф.дизайнеру и быстрых правок, прежде чем собрать сборку и показать финальный скриншот.


  1. Nevis
    25.07.2019 15:53

    Во втором примере выглядит странно, что DrawRectangle находится внутри Padding. Не должен ли он быть снаружи?

        Wrap {
            Padding(30.dp) {
                DrawRectangle(color = Color.Cyan)
                Text("Drag or tap on the seek bar")
            }
        }
    


    1. AndreySBer Автор
      26.07.2019 11:42

      Возможно. Нужно перепроверить


    1. QeGle
      26.07.2019 12:05

      Если вынести DrawRectangle наружу то такая реализация не будет соответствовать xml варианту — не будет отступов относительно других вьюх/виджетов.


      1. AndreySBer Автор
        26.07.2019 12:16

        Если посмотреть на Padding во втором примере, то Padding в Compose то же самое, что Margin в XML, а Padding в XML реализован в Compose через HeightSpacer и WidthSpacer.
        Так что, скорее всего, нам нужно обернуть в Padding только Text, чтобы между ним и Rectangle было пространство. Но нужно проверить.


  1. evseev
    25.07.2019 23:31

    С одной стороны давно понятно, что что-то нужно менять и XML сам по себе не так уж хорош. Но вот что лично мне не нравится, так это разметка UI в коде. Хотя может быть я сам себя накручиваю. В конце-концов сейчас многие раскладывают по папочкам фрагмент, вьюмодел. Ну будет еще один файл там-же лежать с разметкой. Сейчас мы указываем XML, а будем указывать вершину описанной иерархии. В общем нужно сначала дождаться, а потом уже паниковать.


    1. AndreySBer Автор
      26.07.2019 11:47

      Если взять классический MVP, MVVM, или что-то другое с буквой V внутри, то в качестве этого View мы обычно считаем XML (возможно, с Databinding внутри) + "глупый" фрагмент/активити, которые служат как прослойка между Android OS, XML и платформонезависимым кодом на Java/Kotlin (Presenter, ViewModel, ...).


      С декларативным UI у нас будет в слое View тот же фрагмент/активити + классы с @Composableкомпонентами, которые мы кладем внутрь. Кажется, что так даже удобнее.


      Ну и положить этот View слой в отдельную папочку (если еще не) для структурированности.


  1. a_subscriber
    26.07.2019 11:47

    Это все конечно интересно, то вы тут расматриваете простые экраны. 3 элемента. А вот когда у вас на экране минимум 5-7 элементов, интересно как в этом случае поможет Compose подход. Я в таких случаях использую ConstraintLayout. Он очень удобный и позволяет строить сложные скрины. Как Compose сможет заменить ConstraintLayout. Это первый вопрос.
    Второе: Когда вы используйте xml для рисовалова экранов, то Android Studio показывает как выглядить конечный экран. И это очень удобно.
    А в случае Compose подхода, такое возможно будет гляда просто на Kotlin код?
    Нужно что будет, себе представлять как выглядит результат?
    Или я ошибаюсь?


    1. AndreySBer Автор
      26.07.2019 11:50

      Про сложные экраны и замену для Constraint самому очень интересно, как реализуют. Пока её нет, предсказать сложно. Возможно, что-то похожее на Flutter подход. Может, разрешат использовать @Composable внутри Constraint.
      Если как в Flutter, то он предоставляет некий конструктор из Material-элементов. И для типичного приложения по Material-гайдлайнам вроде бы достаточно.
      Но вот если хочется уникальный дизайн, тут согласен, что как-то сложнее, чем с XML.