На прошлом уроке мы начали рисовать пользовательский интерфейс, а на этом его закончим и приступим нажимать на клавиши в редакторе кода Java.
Цель этого занятия будет сделать как-то вот так:
Что на экране нам видно? Вверху системная область с часами и значками, внизу тоже системная область с кнопками возврата, выхода из приложения и смены активного приложения, а между ними наш пользовательский интерфейс. Этот интерфейс состоит из следующих элементов:
- Текстовое поле с надписью “МОИ ХОДЫ”
- Слегка зеленоватое пустое поле, где будет список этих ходов
- Четыре разряда вводимого нами угадываемого числа
- 10 кнопок с цифрами от 0 до 9 для ввода угадываемого нами числа
- кнопка стереть цифру, если пользователь ошибся с вводом
- кнопка помощи для отображения пользователю правил игры
- кнопка ввода числа (её видно на следующем рисунке)
После ввода последней четвертой цифры, появляется ещё одна кнопка ввода (нельзя ввести число, которое меньше 4 разрядов):
При вводе очередной цифры, нужно отметить пользователю, что эту цифру в этом ходе больше использовать нельзя, т.к. по правилам игры все цифры числа должны быть разными.
В домашнем задании прошлого урока была одна из задач, изменить текст поля TextView на текст “МОИ ХОДЫ”. Это как раз первый элемент нашего интерфейса. Вторым элементом должен быть список, который будет содержать историю наших ходов/попыток угадать число, загаданное компьютером. Делается это при помощи тэга RecyclerView. Его можно начать писать вручную (я часто делаю так) в подвкладке “Code”, а можно потянуть из набора элементов в подвкладке “Design”. Второй вариант делается так, как показано далее. Находим элемент в панели (Common->RecyclerView):
Далее хватаем левой кнопкой мыши и тянем на панель ниже после TextView (метод Drag-and-Drop). Должно получиться так:
Теперь снова откройте подвкладку “Code”. Там должно быть примерно так:
Укажите цвет фона для списка как бледно-салатовый. С точки зрения распределения цветов это F0FFF0. В результате в коде должно получиться так:
Что означают эти буквы и нули и как это влияет на цвет было в домашнем задании прошлого урока. Для кого незнакомо само понятие шестнадцатиричной системы чисел (в которой и задаётся этот цвет), то рекомендую прочитать мою статью “Начало программирования вообще с нуля. Часть 2”. Обратите внимание на знак решетки # перед числом, он необходим, сообщает среде разработки, что вы указываете цвет числовым методом в формате RGB (красный, зеленый, синий).
Перезапустите ваше приложение и посмотрите какая бодяга получается:
Надпись куда-то исчезла, а зелёный список стал на весь экран. Возвращаемся к коду. Пришло время познакомиться с контейнером ConstraintLayout, о котором я рассказывал на прошлом уроке. В этом контейнере сейчас находятся два элемента – TextView и RecyclerView. Теперь понимаете зачем нужны контейнеры вообще? Правильно, чтобы туда накидать разных элементов и среда разработки понимала, что всё что внутри контейнера (его тела) относится непосредственно к этому контейнеру, и так вкладывать друг в друга можно как матрешки настолько много раз, насколько это позволительно в данном контексте.
Так вот, контейнер ConstraintLayout как раз и нужен чтобы правильно размещать элементы друг по отношению к другу методом привязки. Это метод, в котором мы, например, говорим так: “Сделай так, чтобы левая граница элемента RecyclerView зависела от положения левой границы экрана (которая всегда будет сама по себе постоянной), а верхняя граница элемента RecyclerView пусть зависит от положения нижней границы текстового поля, где надпись “МОИ ХОДЫ”. При такой зависимости как бы не крутили экраном (портретный режим или альбомный как на планшете), левая граница RecyclerView начнёт подтягиваться к левой части экрана (в том положении, в котором сейчас он находится), а верхняя граница всегда будет подтягиваться к “МОИ ХОДЫ”, таким образом RecyclerView всегда будет ниже этого текстового поля и вплотную к нему примыкать. Это как раз нам и надо, но прежде нужно привести всё в порядок. Посмотрите куда сейчас тянется поле TextView, это выполняется атрибутами типа layout_constraintBottom_toBottomOf. Слегка английского:
- Top – верх
- Bottom – низ
- Start – начало
- End – конец
constraintBottom_toBottomOf переводится как “привязать низ к низу…чего-то там” и чего-то там указывается в кавычках. Там сейчас указано “parent” (родитель). Т.е. ситуация сейчас такая – левая граница TextView тянется к левой части родительского контейнера, правая к правой, верхняя к верхней, а нижняя к нижней. Родительским контейнером для TextView является ConstraintLayout (потому что он и располагается в его теле), а родительский для самого контейнера ConstraintLayout является уже экран, т.к. он ни в чём другом не располагается (он корневой контейнер). По этой причине мы и видели, если кто заметил, что надпись “МОИ ХОДЫ” была расположена прямо по центру. Тут как в басне Крылова “Лебедь, рак и щука”, каждый стал тянуть на себя со всех сторон и надпись зависла посередине – а воз и ныне там. Но нам сейчас такое не надо, нам надо так, что Бог с ним с лево-право, пусть тянут каждый на себя, а вот тянуть вниз не надо, нужно чтобы надпись притянулась вверх к краю экрана. По этому лишний атрибут layout_constraintBottom_toBottomOf убираем вообще, должно получиться так:
RecyclerView наоборот – сейчас не имеет никаких привязок, при такой ширине и высоте, как у него,- match_parent – совпадает с родительским, т.е. в данном случае весь экран, они и не нужны. Именно по этому он и занял весь экран. Сделайте ему тоже привязки, только верх его нужно притянуть к низу TextView. И вот тут резонный вопрос был бы такой: “А как это сказать системе разработки? Когда parent – это понятно, но TextView как указать? Их наверно в интерфейсе может быть не один и не два, я же не могу просто TextView указать, мне нужно как-то идентифицировать, что я хочу привязать именно к этому TextView!” Да действительно, когда на экране должно быть что-то больше, чем “Hello, world!”, то элементы придётся уже идентифицировать, и разработчики среды разработки уже придумали как это делать,- нужно просто добавить к элементам их идентификатор, который будет уникален для каждого (т.е. не будет ни у кого повторяться). Делается это специальным атрибутом, в результате чего должно получиться как-то так:
Т.е. TextView мы дали идентификатор textViewTitle, а для RecyclerView мы дали идентификатор rvSteps. Как понимаете, придумать можно и что-то своё, но делать этого сейчас не нужно, т.к. потом мы будем к ним ещё обращаться по идентификаторам, и может возникнуть путаница. Будете делать что-то своё потом, называйте как хотите, лишь бы потом сами не запутались. В моих названиях есть закономерность – сначала идет признак типа самого элемента (textView или rv – RecyclerView), а затем смысловое значение,- Title – заголовок, Steps – шаги/ходы и т.д. Для RecyclerView мы дали идентификатор потому что возможно его само придётся к чему-то привязывать, а может и нет, пока не знаем. Вообще, хорошая практика, что всё что внутри контейнера ConstraintLayout обязательно должно содержать id, а я так вообще могу давать id на любой элемент интерфейса, не усматриваю в этом ничего плохого, пусть среда разработки сама разбирается что ей потребуется для расположения элементов, а что она проигнорирует.
Теперь приступим делать привязки. Тут можно наверно интуитивно догадаться, что есть наверно какая-то привязка, которая выглядит по принципу “Привяжи верх к низу”, а не “Низ к низу” или “Верх к верху”. Так и есть, и выглядит она соответствующе. Кстати, если вы будете их вводить в коде, например начнете набирать слово Constraint, то развитая система подсказок начнет постепенно понимать что же вы хотите ввести, тут останется только в нужный момент на Enter на клавиатуре нажимать:
Привязки налево и направо тоже нужно дать. Дело в том, что если вы не растягиваете элемент по родительскому контейнеру, то вы как минимум должны сообщить куда по горизонтали и вертикали нужно вообще привязать ваш элемент, ну это и понятно, иначе что он вообще делает в этом контейнере. В результате будет как-то так:
Сами понимаете, горизонтальные привязки у RecyclerView точно такие же как и у TextView, по этому я их просто выделил и скопировал (откопипастил) )).
Следующее поле за RecyclerView тоже будет TextView, как это ни странно, ведь это такой же простой текст выводимый на экран, как и “МОИ ХОДЫ”, только, если заметить, отличается размером шрифта и самим шрифтом:
Да, совершенно верно, наверно есть у этого тэга TextView атрибуты, которые отвечают и за размер и за сам шрифт, добавьте теперь такой элемент и дайте ему соответствующие привязки:
textSize – размер шрифта
text – текст по умолчанию, т.е. начальный текст, который особо здесь не нужен, т.к. это будет у нас управляемое поле – мы будет менять в нем текст программным путём причём сразу при загрузке приложения. Но для вида пусть будет.
fontFamily – сам шрифт, в данном случае не стандартный Android. Этот шрифт нужно подгрузить в среду разработки. С этим придётся немного заморочиться, но тоже решаемо. Сейчас расскажу как.
Для начала создайте в своем проекте ещё один каталог внутри каталога res. Делается это так:
Тут правой кнопкой мыши
Дайте имя нового каталога “font” и обязательно укажите в списке выбора тип ресурса тоже как font:
После этого скачайте файл шрифта с моего ресурса:
и перетащите этот файл в Андроид Студио в эту свежесозданную папку font. После всех манипуляций сначала получиться так, где среда разработки предложит разместить этот шрифт внутри папки:
А после нажатие на Refactor так:
Теперь при указании шрифта, студия разработки будет сама подсказывать какой шрифт взять:
А теперь взглянем ещё раз что получается, а получается какая-то проблема:
Что это я такой за элемент написал textView0, к верху которого мы привязываем низ textViewNumber? И да, система разработки сообщает красным цветом, что возникла проблема с привязкой.
В действительности так получилось потому что мы проектируем быстрее, чем понимает это система. Это поле кнопки “0” из серии кнопок от 0 до 9. Но вы тут наверно скажите,- Э-э-э, автор, стой! Не логично получается! Ты писал, что название элементов у тебя называются по типу элемента, а мы уже успели полазить по иерархии элементов и там кнопка – это не textView, а Button:
Тогда, спрашивается, почему ты делаешь название будущего элемента со слов “textView…”, а не со слова “button…”?
– Да,- отвечу я,- вы меня хорошо поймали на этом, респект! Но я ожидал ваш вопрос, вы не застали меня врасплох! )))
Всё дело в том, что некогда я понял, что мне не очень нравится как выглядит кнопка в Android с её границами, с тем, что весь текст она туда встраивает в верхнем регистре (заглавными буквами) и ещё много чего. По этому я решил, а что если я попробую вместо элемента Button использовать TextView, а потом сделаю так, что он будет отрабатывать нажатие на него как это делает кнопка. И это прошло на ура! И не удивительно. Если кто читал мою эту статью:
Начало программирования вообще с нуля. Часть 1
, где я рассказывал что такое в современном программирование классы, то вспомнит, что один класс может функционально расширять другой класс, который делали другие программисты или вы сами. И что тут неудивительно, внутри среды разработки TextView и Button, и еще много других – это тоже классы, которые расширяют класс View. Этот класс View и обладает методом обработки нажатия на него, когда пользователь кликает пальцем, а потом по логике и все расширяющие его классы тоже по идее должны обрабатывать. По этому Button это или TextView – это всё классы расширяющие класс View, только нужно правильно подтолкнуть его в нужном направлении.
Если бы разработчики среды разработки сделали метод обрабатывающий нажатие кнопки закрытым (private) для TextView и не сделали интерфейс метода для работы с нажатием, такой фокус бы не удался. Но об этом, когда более подробно будете изучать Java Android.
Хотите, сделайте кнопки 0-9 через Button, хотите как я через TextView, только имя давайте аналогичные моим, опять-таки для того чтобы потом не запутаться. Кстати, и картинки я часто делаю тоже через TextView, где картинка – это background. Да, именно так, в этот атрибут можно поставить не только цвет фона, но и картинку фона. Выглядит это так:
Однако тут нужно провести ряд подготовительных операций, почти как со шрифтом, но только проще. Дело в том, что в Android есть базовый набор картинок специально для общих случаев, чтобы настройки приложений различных разработчиков были однотипны, ну типа шестеренка – это настройка, фотоаппарат, значит сделать фото и т.д. Мы в этот раз используем как раз эти картинки. Сделать нужно следующее. Кликнуть по каталогу drawable в иерархии проекта правой кнопкой мыши:
Укажите тип ассета “Clip Art” и кликните по самой кнопке “Clip Art”
Откроется окно выбора:
Выбираем любое, что подходит по смыслу. Далее “некстим”. По результату должно получиться примерно такое:
В каталоге drawable появится новый элемент с именем, которое Студио сама дала (его мы просто не меняли, хотя могли), и если мы по нему дважды кликнем откроется описание и образец этой картинки. Теперь мы можем ссылку на неё подкинуть как background, не забывая поставить путь как на скриншоте (разве что имя поставьте то, которое у вас по факту будет):
Задание по уроку:
1. Разместите оставшиеся элементы – кнопки с цифрами и кнопки-картинки, картинке подберите по смыслу или визуально как у меня, можно похожие. Дайте идентификаторы кнопкам 0-9 соответственно textView0, textView1,…,textView9. Укажите идентификаторы для кнопок “стереть символ”, “правила” и “ввод числа” соответственно textViewBackspace, textViewRules и textViewEnter.
2. Самостоятельно прогуглите тему, чем отличается векторная картинка от растровой в общих чертах.
Если всё получилось как нужно – молодцы! Если нет, давайте разберёмся что не так.