Всем привет! В преддверии старта специализации Android-разработчик делимся с вами текстовой версией демо-занятия «Делаем мобильную версию Poplt». Занятие провёл преподаватель – Антон Мачихин, Android-разработчик в АО Альфа-Банк, с опытом работы с Android почти 6 лет. На занятии мы написали простенькое приложение/программку для тех, кто вообще ещё не знаком с Android-разработкой.  ВНИМАНИЕ! Под катом очень много скринов. Для тех, кому неудобно воспринимать скрины в конце статьи будет прикреплена видеоверсия урока. С помощью Android Studio создаём новый проект. Будет два варианта вёрстки, чтобы игрок мог выбрать как ему удобнее играть. Мы создаём новый проект. Через файл New project. Используем уже готовый шаблон с минимальными исходными, чтобы Fragment-ViewModel сразу же был в шаблоне. И нажимаем Next. Для проекта мы придумываем то название, которое хотим дать своему приложению. Сегодня назовём нашу игру «Pop It». Вы можете назвать как угодно, это не важно. Package Name - уникальное имя, для того чтобы при выкладывании на Market, приложение не конфликтовало с другими. Обычно делается это задом на перёд, домен имени, то есть в интернете вы вводите в одну сторону, а здесь вы вводите в обратную сторону. Мы назвали ru.otus.popit. Вы можете назвать свой проект, как угодно. Например, ru.свой никнейм или фамилия, имя и тому подобное. Далее выбираем куда сохранять будем. Язык выбираем Kotlin. Минимальность SDK можем оставить на 5. На самом деле программа предлагает и ниже до 4.1, 4.2, 4.4. Пятёрки в принципе достаточно. Хотя программа пишет 94%, но многие приложения уже не работают ниже 5. Разницы особо никакой не будет для этого приложения, но для других приложений у вас просто добавится больше проверок, что вы должны проверять на версию SDK, то есть на версию Android, чтобы какие-то элементы у вас работали, какие-то не работали. Потому что в зависимости от SDK разные вещи могут работать или не работать. Например, в пушках, которые в Storе приходят, добавляются различные дополнительные возможности типо канала, который надо настраивать по-другому, то есть в 4 бы не было, пушка приходит и всё. После 5 уже можно настроить для каждой пушки свою вибрацию, свою мелодии и так далее. Поэтому можем оставить смело самое минимальное 5 и этого будет достаточно. Нажимаем Finish. Программа начинает собирать. Необходимо подождать, когда проект соберётся, он скачивает в определённой зависимости из Gradle. Также собирается первый необходимый Bile, всё компилирует, поэтому надо какое-то время подождать, когда он всё это вытянет и соберёт. И после этого у нас появится слева дерево нашего проекта. Теперь мы можем работать. В зависимости от мощности компьютера, это будет занимать определённое время, то есть если компьютер не очень мощный - дольше, если мощный - быстро. Также у нас есть Main Activity. Здесь мы ничего трогать не будем и поэтому можете его не открывать. Main Activity - это основной компонент Android, грубо говоря сам экран, который мы с вами видим при запуске приложения, это Activity. А Fragment - кусочек Activity. На одном экране можно размещать несколько фрагментов, а Activity на одном экране нет. Правда в последних Android есть возможность использовать 2 приложения, то есть как 2 Activity, но фактически для приложения у вас одна единственная. Google в своей работе отдаёт предпочтение Single Activity, то есть одна единственная Activity и множество Fragment. Большинство новых приложений также создаётся на единственной Activity и множестве Fragment. Fragment более гибкие, чем Activity. Поэтому ничего не будем менять. Здесь есть единственный фрагмент, который встраивается в какой-то контейнер, а этот контейнер находится на вёрстке. То есть будет вставляться фрагмент и он будет на весь экран. Но к верстке мы вернёмся позже. Перейдём к Main Fragment и здесь мы будем немножко менять. Для этого необходимо в Gradle зависимости тоже немножко изменить, чтобы мы добавили определённые extension, которые нам нужны. Gradle – это сборщик нашего приложения, он вытягивает все зависимости, собирает, компилирует, фрустрирует и так далее. Все эти вызовы делает для того, чтобы собралось наше приложение. И поэтому всё, что нам нужно, будем добавлять именно здесь. Необходимо немножко поправить. Плагины, с какими вещами он будет работать. Тут обычно не много добавляется, так, что всё остаётся. Компилируем 30 SDK, этого сейчас просит Play Market (29-30, не ниже). Выбирается минимальность SDK, target SDK, на котором у нас сейчас не работает. Version - это нумерационное, чтобы, когда вы заливаете в Market, они друг за дружкой шли, уникальный порядковый номер. VersionName - номер версии, видимый для человека. Мы будем работать через View Binding, поэтому добавим его. Нам понадобится и build.gradle, который Modules, не Project. Project используется на весь проект, а Modules именно как app модуль. Возможно сделать многомодульность, то есть на множества. Но сейчас мы не будем рассматривать эту историю - это тема отдельного урока. Поэтому берём Modules. Здесь нам надо написать buildFeatures, в фигурных скобках. Далее viewBinding = true. Будем использовать именно viewBinding для Bind между вёрсткой и кодом. Нам также нужно добавить зависимости. Зависимости лучше скопируем, потому что их несколько. Тестовые нам не нужны, потому что тесты мы писать сегодня не будем. Здесь мы добавляем именно ядро для androidx, Activity и так далее, базовые классы. Есть material, оттуда возьмем только одну тему, чтобы цветовой тон. Constraintlayout для верстания view групп. В него можно вкладывать другие выдержки, то есть у нас все элементы, это view. А вот сonstraintlayout позволяет их вкладывать в друг дружку в определённом положении, то есть как они друг за дружкой будут идти, относительно кого и относительно чего они будут располагаться. Lifecycle будем использовать патерно. Fragment - кусочки, о которых мы говорили ренее. Gridlayout уже есть в коробке SDK, криво работает на 5-ой версии - немного не так, как нам необходимо. Поэтому надо лучше его подтянуть, и он будет работать так, как нам нужно. Просто он не всё умеет, что нам нужно. Всё это для того, чтобы мы смогли сделать следующие ячейки. Здесь наверху есть слоник или можно нажать sing и начинается синхронизация, то есть будет скачиваться то, чего не хватало, потому что здесь были добавлены какие-то зависимости и как раз их добавляет для того, чтобы у нас они появились в проекте и мы могли ими пользоваться. Теперь мы собрали проект. Мы можем запустить его, но видим только всего лишь фрагмент. Можно нажать кнопку play. Убедитесь, что у вас app без крестика. Также здесь, если у вас нет, бывает такое, когда первый раз запускаете Studio, что нет эмулятора. Если подключите телефон, он может появиться. Если нет эмулятора, можно здесь нажать и через AVD Manager, здесь есть Create Virtual Device. Вы можете запустить и у вас появится эмулятор. ●      Добавили зависимости в Gradle. ●      Fragment + ViewModel. ●      Верстка. Для этого мы можем зажать кнопку Ctrl и main fragment, который есть layout. Можем к нему перейти, чтобы долго его не искать. Ресурсы, это как раз все те ресурсы, которые необходимы для нашего приложения. Там есть и строки и также сами вёрстки, картинки и тому подобное. Сверху есть возможность определять дизайн. Можно сделать сплит для того, чтобы видеть и код, и саму вёрстку. Если перейти в Design, как раз будет атрибут и возможность добавлять атрибуты. ConstraintLayout мы так и оставляем. Всё будет писаться через теги и здесь мы можем найти открывающийся тег, ImageView, выбираем. Нужно задать ширину. Мы будем выбирать именно wrap_content, то есть по ширине, по содержимому. Можно перетащить ImageView. Появляются атрибуты, которые мы можем и добавлять, и убавлять. Нужно добавить имя через id, чтобы мы могли сблизить, то есть объединить нашу вёрстку с кодом. Назовём её update_btn, то есть кнопка для обновления. TextView назовем timer и сделаем 0.0. У нас высвечивается Hardcoded, то есть так писать не рекомендуется и нельзя. Но ресурсы, для того чтобы была возможность поддержки мультиязычности. Если вбить текст, то он будет на одном языке. Если вы хотите поддерживать мультиязычность и пользователь сможет менять язык, то у вас не будет возможности менять название, только через код. Поэтому Studio сразу же подсвечивает, что так нельзя. Обычно пишет Hello World и именно на эти слова не ругается. Для того, чтобы Studio не подсвечивало подобные проблемы, у нас будет динамическое название. Можно использовать tools. Он будет именно здесь высвечивать. Но на самом деле, когда вы запустите приложение, ничего не появится. То есть это только для того случая, когда вы верстаете его здесь.  Выбираем TextSize. Сделаем его 48. Почему именно sp, если есть ещё dp. Для текста рекомендуется именно sp. Если человек плохо видящий, то в настройках телефона он всегда может увеличить шрифт. Если мы делаем sp, то текст будет масштабироваться в зависимости от того, какой шрифт поставил пользователь у себя в настройках телефона (большой, маленький) и приложение будет адаптироваться. Если сделать dp, то он не масштабируется. Обычно именно для TextSize берётся sp, а для всего остального делается dp. Слева можете нажать в любом месте правой кнопкой мыши, сделать New и появится Vector Asset. Векторную графику мы добавляем. Нажимаем Clip Art, чтобы поменять на иконку. Она у нас появляется. Мы переименовываем иконку. ic принято оставлять, как icon. Назовём её update. И мы можем задать какой-нибудь цвет. Размеры делаем 64 и нажимаем Next Finish. У нас теперь отображаются иконки, а у вас не отображаются, потому что у нас стоит плагин, поэтому не пугайтесь. Можно через Resource Manager увидеть Если нажать Ctrl + Alt + L, то у нас отформатируется вид, чтобы было удобно. Здесь также есть Split и мы можем видеть, что у нас отображается. Здесь используется tint, здесь fillColor белый. На самом деле, лучше всегда поправлять, потому что на некоторых устройствах, особенно китайских, он вылезет как будто пикселизированный какой-то, плохого качества, ужасно. Именно tint векторной графики там плохо работает. Цвет, который задался, просто скопируйте и вставьте в fillColor, а остальное можно удалить. Ещё всё это мы можем вынести в ресурсы для того, чтобы цвет можно было переиспользовать. Если нажать Alt + Enter, у нас как раз предлагается вынести в цветовые ресурсы. Здесь мы как раз можем задать название. Оставляем всё как есть и нажимаем OK. Если мы через Ctrl перейдём, то здесь мы как раз можем и увидеть наши цвета. Давайте поправим под тот цвет, который у нас. Цвет можно менять на тот цвет, который вам хочется и удобно. В этот ImageView мы как раз можем добавить цвет, чтобы у нас он отображался. У нас как src. И теперь мы можем зацепить за этот бок. Добавим gridlayout. Открываем тег: нам нужно из android, именно тот, который мы подтянули из зависимости. И здесь мы сделаем его на всю ширь, поэтому здесь будет 0dp. Петельку тянем вниз, вправо, потом тянем влево. Будем растягивать по ширине. Петельку потянем к TextView. Пишем bottop. Надо его назвать, дать id/field. Здесь если нажать Ctrl + Пробел, появляется подсказка. Мы теперь его объединили. Вот этого нам не нужно, через Ctrl + Y мы удаляем. Теперь он привязан к верхушке и к иконке. Иконка привязана к боку, её тоже нужно привязать к полю. Мы можем скопировать и вставить. К верхушке она ещё тоже должна быть. Мы тоже можем скопировать и сюда же вставить. Нам нужно сделать так, чтобы был квадратик. Здесь ratio сделаем именно constraintDimensionRatio. Мы сделаем 1:1, у нас он получается квадратиком. Ещё нам нужно задать количество колонок. Мы можем задать именно columnCount 5 и также rowCount тоже 5. Лучше 6 сделать. У нас должно быть 5 на 5, но мы делаем 6 на 6. Давайте сделаем окантовку, рамочку. На Drawable щёлкаем правой кнопкой и здесь нужно сделать New, Drawable Resource File. Он будет называться background_mair. Всё остальное можно оставить как есть и нажимаем OK. Split можем нажать, чтобы мы могли видеть, что приблизительно у нас будет получаться. Здесь вместо selector нам надо будет layer-list. Между этими двумя тегами открываем новый тег, это будет item. Объединим несколько item. Закрываем скобочку и у нас подставляется закрывающийся тег. Добавим shape, это какая-то форма. Здесь как раз есть линия, квадрат, кольцо и овал. Из овала мы можем сделать и круг. Мы можем не писать и просто закрыть скобочку, чтобы у нас появился закрывающийся тег. Добавляем padding, чтобы была именно окантовка. Сделаем пункт 4dp. Мы делаем всё, что у вас высвечивается: bottom, left, right и top. Закрываем тег через Slesh. Рисуем линию - stroke. Возьмём ширину 8dp. Цвет мы сделаем наш color_main. Его будем использовать и у нас он здесь есть. Этот background нам нужно добавить. Пишем background. Появляется окантовка для нашего Pop It. Нам нужно сделать этот пузырёк. Мы будем делать в 2-х состояниях, то есть, когда он не нажатый, то есть выпуклый и когда он нажатый, то есть вогнутый. Опять по той же схеме, в Drawable делаем New, Drawable Resource File. Назовём shape_not_clicked. Опять по той же схеме, Split. Вместо selector мы возьмём layer-list, здесь мы сразу же item сделаем. Первый item - окантовока. Делаем shape и делаем oval, но можем сделать и круг. Давайте создадим ему размер, то есть ширину. Делаем 24dp. Вытащим его в ресурсы через Alt + Enter, Extract dimension resource и назовём его pop_size и он будет перемещаться в dimens, то есть это именно ресурсы для чисел, для размера. Появился файл dimens, это как раз именно размер. Возвращаемся обратно, ширину мы сделали, нужно также высоту. Можем уже и использовать как pop_size, закрываем тег. Теперь нам надо нарисовать черту, здесь мы тоже делаем width 2dp. Размеры этой черты тоже можем выносить в ресурсы для того, чтобы мы могли его менять. Давайте поменяем, pop_circle. Давайте pop_circle_width. Теперь надо сделать внутреннюю. Добавим ещё один item. Опять также shape и там тоже мы делаем ещё один oval. Мы добавим у него gradient, startColor и endColor endColor мы сделаем main. startColor должен быть как бы белый, но не белый. Место перелива тоже можно двигать, centerY 0.9, centerX 0.9. Также gradientRadius нужно поменять. Мы подобрали 12dp. И градиент мы сделаем радиальным. Отступы нужно сделать, чтобы у нас он был внутри. Снова bottom, left, right и так далее, их тоже нужно добавить. Давайте через ресурс добавим, чтобы каждый раз не писать. Сделаем его inner_padding. Добавляем, что у нас есть и появляется симпатичный пузырёк, это не лопнутый. Теперь делаем лопнувший пузырёк. Чтобы опять всё не печатать и не переписывать, мы просто копируем. Ctrl + C и Ctrl + V, убираете not, нажимаете ОК. Нажимаем Ctrl + D, он дублирует. Называем color_clicked и задать цвет. endColor делаем на clicked. CenterX надо сместить на -0.1, centerY на -0.1 и я ещё изменил радиус на 18. Мы создадим для пузырика background. Делаем также правой кнопкой New, Drawable и называем его как background_item. Создадим другое, так как именно это будет значком, поэтому это не background, а сам элемент. Назовём его как ic_circle_selectable, то есть с возможностью выбора во время его щёлканья. Как раз здесь selector и пригодится. Нам необходимо добавить один item и у него добавим drawable, который clicked и добавим состояние, то есть state_enabled, когда у нас оно активно, то есть мы можем на него щёлкать и как-то взаимодействовать. Именно, когда он false. Если у нас состояние заблокировано, то будет добавляться именно этот цвет. Не color, а shape_ clicked. И по умолчанию, его в самый конец надо делать, именно drawable/shape_not_clicked. У нас теперь есть пузырик. Добавим его, чтобы мы могли его видеть. И добавим закрывающийся тег. Добавим ImageView. layout_width сделаем wrap_content и layout_height, также wrap_content. Src сделаем, ic_circle_selectable. Зададим ему вес: columnWeight сделаем 1. Есть вес по строке rowWeight, тоже зададим 1. Ещё добавим background добавим. Здесь тоже делаем layer-list. Добавим item. Сделаем shape, поскольку опять нужен наш прямоугольник. Мы будем на нём рисовать черту stroke шириной 4dp и цвет будет наш основной цвет. У самого item сделаем отступы слева и справа чуть больше, чем нужно, с отрицательным числом. Left и right. Давайте добавим отступ и сделаем padding 16dp. Следует сделать так, чтобы был ещё ландшафтный, потому что если мы сейчас соберём и повернём экран, то у нас будет он не очень красивый. Вёрстка, которая есть main_fragment - можем её скопировать. Надо путь directory, layout-land, то есть именно ландшафтный. bottom_top мы убираем, в ImageView, который у нас есть. Давайте уберём всех, и напишем то, что нам нужно. Нам нужно End_toStart. Начало мы привязываем к началу parent. Верхушку мы привязываем тоже toTop к parent. Теперь необходимо у TextView. Мы всё скопируем и вставим. Только нужно не toTop у него сделать, а именно toBottom, update_btn. И теперь у gridlayout start_toStart поменять на start_toEnd. В ресурсах есть темы, то есть themes. Themes (night), её убираем, потому что мы не будем делать тёмную тему. В саму тему провалимся, здесь надо поменять Components.DayNight.DarkActionBar. Просто Light_NoActionBar. Мы подкорректируем ещё в MainFragment. И уберём. Исправим, так как зависимости уже добавляли. lateinit убираем, меняем на val и by viewModels - это нужно выбрать. Это именно в Kotlin. У нас есть вёрстка, она называется main_fragment. Binding также был сгенерирован автоматически, то есть мы тоже MainFragment набираем и здесь появляется MainFragment Binding. Это именно Bind для нашей вёрстки. Надо её inflate, то есть надуть. Мы можем вот так вот сделать: .val. И у нас он предложит его здесь назвать. Shift + F6 мы нажимаем и мы назовём его binding. У binding возьмём root для того, чтобы нарисовался фрагмент. Val binding нам пригодится ещё в других местах, поэтому мы его сохраним как параметр. val можем удалить. Alt + Enter сделать и Create сделать. Enter, Enter нажимаем, и он называет его как lateinit - это поздняя инициализация. Давайте создадим. Набираем onvicr, когда у нас стоит именно View. Инициализацию пузырьков мы создадим в ViewModel. Мы делаем её private, чтобы она была не видна снаружи. Lateinit сделаем на var, matrix и присвою ему Array. Здесь она будет состоять из Boolean. Создадим такой метод, в Kotlin называется fun, и назовём его initGame. Мы будем вызывать этот метод для того, чтобы сбросить наше состояние по умолчанию. Вот этот matrix мы ему и создадим. Массив, размерность у него 25, так как это массив, в Kotlin он обязательно его просит проинициализировать. Мы его проинициализируем как true. Нужен посредник, который будет передавать во View. Мы будем использовать livedata. Создадим её. Назовём - private val mState и будем использовать MutableLiveData. Нам нужно передать тот класс, который мы будем там хранить. Мы будем хранить именно тот класс, потому что мы туда будет передавать матрицу. Во View мы не можем изменять состояние. Во внешке создадим state: LiveData. Теперь давайте со стороны View. Мы в binding field, который у нас есть, и удалим всё, что у нас есть. Будем бежать по всем элементам, используем forEachIndexed. Нам нужно определить колонку и строку. Для того, чтобы определить, какая это у нас строка, мы будем index делить на 5, потому что хотим сделать 5 на 5. Если мы хотим колонку, будем index уже остаток брать от деления, то есть это % 5. Нам нужен params, которые будем передавать во View. У GridLayout.LayoutParams, нужно GridLayout.Spec. В него передаём нашу строку row и вес 1. Продублируем, только здесь уже нужно column через Ctrl + D, тоже мы делаем 1f. GridLayout androidх. Давайте сделаем сам элемент item = AppCompatImageView. Воспользуемся методом Kotlin, apply, это само как будто View Если он просит у вас симпортировать нажимаем Alt + Enter. Нам надо setBackgroundResource. Можете и обычным ImageView. Блокируем isEnabled. Вибрацию давайте сразу же добавим, когда тыкаете, чтобы у вас была такая отдача. Добавим Listener на его кликание, выбираем, который с фигурной скобочкой. Чтобы был отдача, что мы щёлкнули. Сразу же сделаем ViewModel, что мы уже кликнули по элементу. Мы можем передать какие-то параметры, которые нам нужны. Далее нажимаем Alt + Enter. Он как раз создастся, убериаем сразу же TODO. Мы делаем binding.field.addView(item, params) передаём его параметры. Есть метод init, вместо конструктора. Здесь сделаем InitGame. В настоящий момент у нас есть отрисованный Pop it, но он заблокирован. Давайте сделаем так, чтобы уже можно было нажать после чего они бы меняли своё состояние, то есть чтобы были активны, и потом мы их лопнули. Здесь уже сделан OnClickListener, то есть когда мы будем щёлкать на пузырёк, сюда нам приходит event, и мы как раз его будем во ViewModel обрабатывать и менять состояние, это можно перейти через onPopClick. Мы это делаем по matrix, по index можем его найти. Сделаем у него false, получается заблокированный. Теперь нам необходимо его передать. Для этого можно использовать LiveData, но он будет не совсем корректно у нас работать, когда мы повернём экран, то есть мы повернём экран и этот элемент заново сработает. Для такого обходного пути есть SingleLiveEvent, но, к сожалению, в коробке его нет и надо его самому написать. Мы его перекопируем из другого проекта. Здесь используется самый простой. Вы можете скопировать скопировать и вставить, правой кнопкой или Ctrl +V и сама Studio сразу создаст этот файл. Здесь мы наследуемся от MutableLiveData, чтобы мы могли в него вносить изменения. Назовём этот наш класс SingleLiveEvent. Как один из вариантов, будем использовать AtomicBoolean, поскольку мы можем обращаться из нескольких потоков, поэтому нужно сделать так, чтобы у нас обязательно изменилось состояние только в тот момент, когда нам это нужно. Определим метод observe и будем делать свой observe и его передавать. Если всё правильно, тогда дальше будем отправлять то, что изменилось, то есть дёргать метод onChanged. При setvalue мы будем изменять на то, что у нас есть намерение, что нужно обновить состояние на вёрстке и просто будем вызывать этот setvalue. Мы как раз его будем использовать. Продублируем, чтобы несколько раз не писать и будем его исправлять через Ctrl + D. Назовём его mCellStateByIndex = SingleLiveEvent. Если нажать Tab, он сразу же заменит полностью весь этот класс, то есть до конца названия. Здесь нужно передать индекс, по которому мы должны передать состояние и смена состояния. Нам поможет такой класс, как Pair, то есть пара. Будет индекс и его состояние. Естественно, нам нужно его сюда тоже выкидывать. mCellStatebyIndex.setValue = index to false. Видите, программа ругается, что не может использовать этот метод value. Для того чтобы сохранилось такое же написание, чтобы было удобно, нам нужно привести его к этому классу. Закрыли реализацию на SingleLiveEvent. Теперь нам надо подписаться именно на cellStatebyIndex на фрагменте, чтобы менялось состояние. По такому же способу, давайте скопируем. Здесь просто поменяем на cellStatebyIndex. Чтобы у нас не было first, second, мы делаем так: будет index - это first, state - это second. Так будет более удобно. Теперь мы обращаемся к binding.field.getChildAt(index).isEnabled = state. Сейчас сделаем сброс. ClickListener нужно добавить. Alt + Enter. Здесь мы будем снова делать инициализацию InitGame В вёрстке надо добавить background. И в ландшафтном нам тоже надо поменять. Теперь давайте добавим звук. Мы подобрали звук, лучше скачайте wav. Когда его скачаете, переименуйте, чтобы название было не такое длинное, без пробелов, свой мы назвали pop.wav. И нам потребуется скопировать его в папочку assets. Нам её надо добавить. App, New, Folder. Assets Folder, нажимаете Finish и она у вас появится. Наш файлик туда отправляем. В assets мы можем создавать и подпапки, то есть распределять по разным смысловым нагрузкам компоненты, которые нам нужны. Теперь необходимо этот файл добавить в наш проект, чтобы мы могли им пользоваться. Для этого мы возьмём метод onCreateю. Таким образом, когда фрагмент создаётся, тогда мы его и вызываем. Есть метод requireContext, у него есть метод assets, и мы выберем метод openFd и здесь надо написать полное название нашего файла. Во viewModel будем его передавать. Нужно обернуть в try catch. Здесь мы будем в e.printStackTrace делать. Создали метод InitSound. Нам нужен builder AudioAttributes, мы его и вызываем. Есть метод .setUsage(AudioAttributes.USAGE_GAME), когда звук будет происходить максимально быстро. Также нам потребуется: .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION), что это у нас просто какой-то звук. Всё это .build. Когда хотим переименовать переменную, мы нажимаем Shift + F6 и появляется подсказка, которую мы можем переименовать самостоятельно, либо выбрать то, что предлагается. Сделаем переменную val soundPool = soundPool.Builder. Здесь мы задаём количество звуков, которое мы можем одновременно производить, то есть как быстро будем щёлкать. .setAudioAttributes нужно добавить. Делаем .build. Мы сохраним его уже как create property. Alt + Enter и здесь есть Create Property. SoundPool будет именно здесь. Pop = load. Мы сохраняем как create property. Это число, которое принадлежит этому файлу и по умолчанию мы тоже сделаем 0. Сейчас мы добавим его вот сюда. SoundPool? мы будем ему передавать. Воспроизвести. Левое и правое ухо будут на максимуме из того, что нам дозволено, priority 1. Цикл нам не нужен, поэтому вставляем без какого-либо ускорения, без замедления, оставляем нормальное воспроизведение. Мы добавим таймер, тем самым фиксируя свой прогресс. Лучше иметь состояние игры, то есть она запущена, остановлена и вообще игра или есть, или нет. Для этого мы создадим такой enum class, чтобы нам было удобно им пользоваться. Мы его назовём GameState. Started будет запущен, Stopped и Ended. Это будет состояние, чтобы мы в дальнейшим могли понять, когда нам следует запустить, а когда его остановить. Будем сохранять это состояние. Введём для этого переменную gameState = GameState.Stopped. При инициализации игры мы тоже должны её сбрасывать. Также нам нужно считать сколько раз мы щёлкнули по нашим пузырькам, чтобы можно было остановить нашу игру тогда, когда она завершается. Заведём здесь ещё одну переменную private var currentClickPop = 0. Когда мы будем инициализировать игру, нам тоже необходимо её сделать 0. Когда мы щёлкаем на наш пузырёк, счётчик будем увеличиваться. Мы будем смотреть на то состояние, в котором мы сейчас находимся. Для этого есть оператор when. Здесь мы будем передавать GameState. Подсвечивает, потому что не все ветки описаны, и мы можем, чтобы сама Studio это сделала, то есть нажать Alt + Enter, здесь есть Add remanding branches. Она сразу же их все добавляет. TODO будем удалять. Если игра остановлена, будем менять состояние игры на то, что она запущена. Если наша игра запущена, будем проверять. GameState будет в состоянии «завершена». Если завершена, тогда мы делать ничего не будем. Как сделать счётчик? Я заведу ещё одну переменную private var startTime: Long = 0. Чтобы счётчик менялся, нам понадобится handler - это основная ручка, через которую многое происходит в системе Android, чтобы отображать на основном потоке. Весь основной поток - поток, который высвечивается у нас на экране. Возможно делать дополнительные потоки, но там мы создаём их самостоятельно. Мы не можем рисовать на экране из другого потока. Мы его создадим private val handler = Handler, нам нужен именно Android.os. Нужен runnable, надо сделать его реализацию, именно object. В нём нужно override единственный метод run. Мы должны завести MutableLiveData для таймера. Мы его назовём mTimerState и будем ещё долю секунды показывать. Будем состояние менять на mTimerState.value. Перезапустим в handler. Мы будем запускать его там, где меняется состояние. Надо ему задать текущее время, чтобы мы от текущего времени отсчитывали количество миллисекунд, которые прошли. В handler запустим post(runnable). В handler нужно будет сделать removeCallbacks(runnable), чтобы он остановил таймер, и чтобы он не вызывался постоянно. Нам необходимо, чтобы всё это появилось на экране, поэтому мы через timerState. В самом фрагменте мы подписываемся. Мы будем после точки рисовать ещё один символ. В инициализацию игры также добавляем это состояние: mTimerState.value = 0f Когда onUpdateClick делаем, нам также необходимо всё это остановить. Теперь давайте всё это закомитим, то есть нажимаем Ctrl +K и нам предлагается сразу же закомитить. Сегодня мы сделали игру. УЗНАТЬ О КУРСЕ ПОДРОБНЕЕ Редактор