Рубрика: Java Android
Java Android. Урок 1 (“Студия”)
Сегодня я традиционно отмечаю день программиста. Потому что именно 5 сентября я первый раз оказался за ЭВМ ))…По этому поводу я решил отметить этот день выпуском первого урока программирования под Android. Прежде чем начать, вынужден сказать, что крайне желательно чтобы ваш компьютер был на базе процессора Intel. Дело в том, что виртуальная среда, где будут происходить запуски приложения, оптимизирована именно под эти процессоры. Информации о том, что процессоры AMD тоже оптимизированы, у меня нет (раньше такого не было). Как это произойдет, я уберу эту ремарку из этого урока. Если же у вас AMD процессор, то самым оптимальным будет использовать для тестов непосредственно ваш мобильный телефон, который необходимо переключить в режим откладки, подключить кабель к компу, и Android Studio должно успешно понять его. Некоторые телефоны и планшеты не могут быть опознаны, в этом случае нормальная комфортная разработка невозможна, нужно что-то делать с вашим железом. Дальнейшая настройка и уроки будут идти исключительно в контексте виртуальной среды.
Всё начинается с простого скачивания Android Studio на свой ПК по этой ссылке:
Download Android Studio & App Tools – Android Developers
где кликаем по кнопке “Download Android Studio”.
Через некоторое время, когда файл будет скачан, браузер запустит его сам, либо вы должны его запустить вручную (тут всё зависит от ваших персональных настроек безопасности). Установка особо ничем не примечательна, по этому все некстим (ставим программу кликанием по кнопкам “Next” и “ОК”). На этом этапе нам нужно как можно быстрее всё поставить и попробовать сделать наше первое самое простое приложение. Когда всё завершится, мы окажемся перед выбором, что будем создавать.
При первом запуске студио у вас список будет пуст (это всё мои проекты).
Теперь нужно кликнуть кнопку “New Project” (Новый проект). Да, привыкайте, студио на английском, но особых неразрешимых трудностей это не вызовет, если хотя бы несколько раз ходили в школе на занятие иностранного языка. Откроется окно, которое будет уточнять у вас, что за проект вы хотите сделать. Но не торопитесь!
Это не тот выбор, который предлагается сначала, нужен другой.
Необходимо выбрать “Empty Views Activity”:
Это правильный выбор, который сейчас нужен
Всё дело в том, что эта среда разработки поддерживает два языка программирования Java и Kotlin. Мы будем делать проекты на Java. Google постоянно старается продвигать именно Kotlin, хотя лично я к этому не стремлюсь – просто не хочу делать в голове кашу из разного числа языков программирования. Дело в том, что Java Android, Javascript, Java Tomcat, C, PHP – это похожие по своему синтаксису языки, а Kotlin не похож на них. Не охота вводить диссонанс в голове. В общем, выбираем вариант указанный на картинке. Далее в новом окне даём название нашему приложению. Будем делать свою первую игруху на Android с названием “Быки и коровы”, но написать это нужно латинскими буквами. По этому чтобы не изобретать ещё один “Олбанский” язык, пишем название “Bulls And Cows App”, и убеждаемся, что проект будет на языке Java (поле Language):
По прошествии некоторого времени, когда комп прошуршит жёстким диском и выместит на экран все сообщения пользователю, которые он хотел сказать, появится окно похожее на это. Если что-то будет лишнее справа или слева, или снизу, то это пока можно будет скрыть:
А вот полезное окно слева с названием Structure лучше сразу открыть:
Кликая дважды мышью по элементам структуры, удобно переходить сразу на начало объявления соответствующих элементов кода. Когда проект большой, это удобно.
Одна из основных заповедей в работе на компьютере это “Не навреди!”. По этому первое время правильным будет примерно такой подход к кликанию мышью в Android Studio (это я утрирую):
Т.е. серьёзность понимаете? )).
Ну, шутки шутками, а всё-таки пока лучше в незнакомые опции не заходить. Сейчас задача запустить своё первое приложение. Как раз именно сейчас для этого уже всё и готово. Да, Android Studio сделал для нас Empty проект, т.е. пустой, с самым минимумом того чтобы создать приложение. Давайте сразу же и посмотрим что получилось от ничего не программирования. По этому нужно кликнуть в меню Tools->Device Manager:
Справа появится панель управления виртуальными устройствами (эмуляторы мобильного телефона и иных устройств):
При первом запуске панели список устройств будет пуст (это мой список устройств)
Теперь нужно создать новое устройство кнопкой “Create Device”, которая находится над этим списком. Далее опять всё некстим и океем не вдаваясь в подробности, просто проследив, что создаётся именно Phone (мобильный телефон), а не Андроид ТВ, планшет или ещё что. Другие параметры сейчас не так важны.
Далее посмотрите, что с выбором операционной системы:
Список операционных систем
Если кнопка “Next” доступна, то жмём на неё; это значит образ операционной системы уже установлен вместе со Studio, если не доступна, то слева будет значок скачивания. Посмотрите внимательно на этот список, который на скрине – у Pie (активная строка) нет такого значка потому что она уже скачана и кнопка “Next” доступна, а у других есть – они ещё не скачаны. Качать все сразу смысла нет, они занимают достаточно много места, а использоваться будут в работе только несколько. По мере надобности можно потом скачать. В следующем окне тоже особо ничего пока не нужно и нужно нажать “Finish”.
Будет создано ваше первое виртуальное устройство.
Теперь необходимо убедиться, что устройство с этим именем находится в выборе запуска, и если не так, то сделать этот выбор (возможно у вас будет другое название):
Выбор конфигурации виртуального устройства для запуска
После выбора нужно кликнуть по кнопке с зеленой стрелкой вправо, находящейся сразу справа от списка. Среда разработки какое-то время подумает, поделает всякие действия и в результате справа должно появиться что-то вроде этого:
Нижнее окно и окно менеджера устройств можно теперь свернуть и тогда это будет выглядеть так:
Вот ваш виртуальный мобильник, который практически тот же, что и был бы реальный, тоже с установленной системой Android (помните версии образов на скачивание, у меня была Pie – Android 9). И да, вот о чём я писал в этой статье
Начало программирования вообще с нуля
про философию “”Hello, world!”. Благодаря этой надписи на экране вашего виртуального мобильника мы хорошо теперь знаем, что виртуальная среда настроена правильно, среда разработки настроена правильно, программа написана без ошибок (конечно же, мы ведь в ней ничего не меняли ещё), и вообще всё круто,- вы всё сделали правильно!
Теперь нужно перейти вот сюда (развернуть) и кликнуть двойным кликом по MainActivity.xml (если вы вдруг случайно закрыли эту вкладку):
После этого смените подвкладку на “Code” (скорее всего изначально будет открыта “Design”):
Замените надпись “Hello, world!” на “Привет, мир!” и снова перезапустите своё приложение кнопкой с зеленой стрелкой (кнопкой “Run”), которая может теперь быть завёрнутой стрелкой (см.далее):
Завернутая стрелка кнопки Run. Означает не запустить приложение, а перезапустить приложение. Фактически тоже самое, только в обновленном виде.
Должно получиться вот так:
Поздравляю! Теперь вы стали программистом под Android. И хотя программа ваша слишком проста, вы уже программист, просто находитесь с самом начале профессионального пути. Насколько написанный вами дальше код будет сложен и многогранен, зависит только от вашей фантазии, творчества и желания!
Задание по уроку:
1. Попробуйте самостоятельно найти в иерархии проекта (Панель слева “Project”) место, где хранится название вашего приложения. Замените его на название на русском “Быки и коровы”
2. В виртуальном мобильном телефоне удалите установленное это ваше приложение и переустановите заново с новым именем. Добавьте на этом виртуальном устройстве в интерфейсе системы русский язык чтобы всё было так, как-будто язык Андроида русский (это делается через обычные стандартные настройки языка). Это скорее больше задания как для пользователя мобильного телефона, но умение этого будет необходимо в дальнейшем.
Если всё получилось как нужно – молодцы! Если нет, давайте разберёмся что не так.
Java Android. Урок 2 (“Frontend”)
Как помните, в прошлом уроке мы стали создавать наше первое приложение “Быки и коровы”. В готовом виде это приложение есть в моём аккаунте по этой ссылке (чтобы понимать что мы хотим делать):
Быки и коровы в каталоге RuStore
В этом уроке вы будете создавать среду взаимодействия с пользователем, которую часто называют “интерфейс пользователя”, а креативные специалисты предпочитают название Frontend. Если простыми словами – это то, что видит на экране пользователь. Как понимаете, есть и то, что тоже является работой программы, но что пользователь не видит, но об этом позже.
Смысл этой детской игры достаточно прост. Играют двое (в данном случае одним из игроков будет вычислительная среда мобильного телефона, а вторым – человек / пользователь). Один загадывает число из 4-х знаков, все цифры которого различные, второй игрок, пользуясь подсказками должен угадать узнать это число. Загадывать будет компьютер, отгадывать человек, компьютер/мобильник/планшет будет давать подсказки. Подсказки даются следующим образом. Если в называемом числе есть цифра в загаданном и находится в своём разряде (на таком же месте), то это “бык”, а если есть такая цифра, но не находится на своём месте, то это “корова”. Если угаданных цифр несколько, то соответствующее число будет быков и коров. Как понимаете название игры весьма условно, т.к. реальных быков и коров вы в этой игре не увидите. Игра эта тренирует логическое мышление у детей, а также память, способность целостно представлять решение задачи, искать рациональные логические решения. Связано это с тем, что любое число можно отгадать за определенное минимальное число вопросов. Поясню подробно. Например:
Компьютер загадал число
4678
Мы пытаемся отгадать и называем первое придуманное число
2684
Компьютер должен нам сообщить, что в нашем числе 1 бык и 2 коровы:
бык – это цифра 6, она есть в загаданном числе и находится в 3-м разряде (разряды считаются справа-налево).
коровы две – это цифры 4 и 8, т.к. они тоже есть в числе, но стоят не в строгом соответствии разрядов.
Сложность тут в том, что компьютер нам не скажет, что именно является быком, а что коровами. Просто сообщает что они есть и сколько их. Теперь мы уже точно знаем, что какая-то из цифр нашего придуманного числа точно стоит на своём месте, а ещё есть две, но не на своём. По этому мы используя эту информацию должны снова придумать число и сказать его компьютеру, например мы говорим
2184
Компьютер нам сообщит после этого, что в этом числе только 2 коровы. По этому мы теперь точно знаем, что 6 была быком. Т.е. одну цифру мы знаем точно и где она находится в угадываемом числе. Однако, мы могли и не угадать с первого раза, а назвать число, например 1874 – это было бы тоже 1 бык и 2 коровы, мы бы от этого только запутались. В этом и заключается интерес – как правильно мы используем каждую очередную подсказку, как правильно называем вариант и как быстро сможем узнать загаданное компьютером число. Помню нас школьников эта игра очень увлекала в 3-м классе.
Итак, задача поставлена, теперь нужно создать пользовательский интерфейс, который будет отображать на экране телефона текущее состояние, уже введенные нами варианты, сколько в каждом было быков и коров и ещё должно быть поле ввода цифр, которое и будет принимать от нас каждый новый вариант числа. В последствии алгоритм будет его сравнивать с загаданным числом и выдавать результат сравнения.
Рассмотрим подробнее как выглядит файл MainActivity.xml, в котором на прошлом уроке вы меняли “Hello, world!” на “Привет, мир!”.
Файл выполнен в формате XML, об этом свидетельствует надпись вначале
“<?xml”
Тут придётся немного рассказать вкратце о его структуре, тем более что формат очень популярен и используется не только в Андроид Студии. Файл состоит из контейнеров, которые образно выглядят вот так (его границы, а также внутренности – тело):
<ИмяКонтейнера Атрибут1=”значение1″ Атрибут2=”значение2″ ещё атрибуты…>
…тут что-то находится внутри и называется телом…
</ИмяКонтейнера>
Т.е. контейнер состоит из открывающего элемента и закрывающего элемента, а внутри – тело. Эти элементы начала и конца называются открывающий тэг и закрывающий тэг. Конец контейнера определяется почти также как и его начало, у него имя точно такое же, но нет атрибутов и перед именем стоит косая черта (правый слеш /). Контейнеры нужны чтобы внутрь переменной ИмяКонтейнера запихнуть большую кучу всякой информации.
Название контейнеров, атрибутов в данном файле строго регламентировано и должно называться строго определенными названиями. Значение атрибутов должны быть только определенного для них типа и в строго определенном диапазоне значений, если такой диапазон предусмотрен.
Вторая конструкция xml файла – это просто тэги. Это тот же контейнер, только без тела внутри (у него нет закрывающего тэга). Но открывающий выглядит чуть по другому:
<ИмяТэга Атрибут1=”значение1″ />
т.е. правый слэш стоит в конце в тэге, атрибутов тоже любое количество, закрывающего нет и тела тоже нет.
Сами тэги, как наверно уже поняли – это то, что находится между угловыми скобками (они же знаки меньше-больше) :
<тут всё, что относится к тэгу вместе с атрибутами>
Атрибуты – это некая характеристика тэга, его параметр, иная информация относящаяся непосредственно только к этому тэгу.
В таких файлах xml и находится как раз полное описание того, что находится на экране…но это не точно)))…но об этом гораздо позже. Сейчас должны знать, что какой файл MainActivity вы сделаете, то это на экране и будет. Вопрос, а что конкретно можно описать там?
Если вы держите в руках мобильный телефон на Android не впервые в жизни (если это не так, то уже настало время это поделать), то наверно видели, что на экране могут быть:
- обычный текст
- поля для ввода какого-то текста
- картинки
- кнопки
- различные переключатели тип вкл/выкл
- различные выборы из списков
- перечни
- так называемые радиокнопки (несколько кружочков с текстом, где можно выбрать только один из нескольких вариантов)
- чекбоксы (где можно поставить галочку)
- географические карты
- и т.п.
Это всё можно и нужно описать в этом MainActivity.xml
По этому, чтобы далеко не ходить, разработчики Android так и решили называть тэги и контейнеры по типу того объекта, которые пользователю нужно показать. Например, взгляните на наш этот файл и посмотрите внимательно в каком тэге какой атрибут мы поменяли, когда изменили текст “Hello, world!” на “Привет, мир!”. Это атрибут “android:text” тэга TextView. Т.е. тэга, который выводит на экран просто обычный текст. Посмотрите, какие ещё у этого тэга атрибуты:
android:layout_width – это ширина этой текстовой области
android:layout_height – это высота этой текстовой области
есть и ещё другие атрибуты, которые мы будем рассматривать в следующих уроках, пока рано.
Ещё заметьте, что ширина и высота заданы не цифрами, а служебным словом “wrap_content”. Всё дело в том, что разработка под Android идёт с учетом различных устройств, а если быть более точным – более 10 тыс. различных устройств. Все эти устройства имеют разные характеристики, и что важно – разные размеры экранов. Работа программиста заключается в том, чтобы ни на каком из этих экранов интерфейс пользователя не отобразился криво, чтобы всё влезло по ширине и высоте, чтобы слова правильно переносились, чтобы текст не выходил за пределы экрана и т.п. Это называет адаптивный интерфейс (или адаптивный дизайн), т.е. интерфейс, который будет пытаться подстроиться под каждый размер экрана каждого устройства. Если, например, вы укажете точную ширину в пикселях, то на крохотных экранах часть текста может оказаться либо не видна, либо переноситься по середине слова, а на больших экранах окажется много свободного неиспользуемого места справа. По этому стараются делать так, чтобы приложение автоматически попробовало по ширине нужной области, а если уж это не получится, то и стало бы переносить. Но все бы так и делали, если бы в дизайне иногда не требовалось разместить несколько объектов слева направо, например фраза “Введите логин:” и справа от него поле ввода логина. Если бы они оба были по ширине экрана или оба имели бы фиксированную ширину, то см. выше, что я только что написал – дизайн бы съехал. По этому придумано много различных вариантов задания ширины и высоты, а именно:
- в условных единицах, например, 50dp, 125dp
- по ширине содержимого текста, wrap_content. Т.е. какой ширины отображаемый текст, таким и будет ширина этого объекта. Удобно как раз для текста “Введите логин:”
- по ширине вышестоящего контейнера, а если его нет – экрана, match_parent
Всё тоже касается и высоты. Каждый из этих вариантов выбирается под конкретную задачу по мере необходимости.
Теперь, после того как вы немного сориентировались с тэгом TextView, обратите внимание в каком контейнере он сам располагается. Это контейнер с именем androidx.constraintlayout.widget.ConstraintLayout. Не нужно запоминать такое длинное название, это просто контейнер ConstraintLayout. Длинный путь нужен среде разработки чтобы она сама не запуталась где его внутри своих библиотек кода искать. Если вам потом придётся вводить его вручную, то среда разработки сама подскажет откуда она его будет брать, как только начнете набирать слова ConstraintLayout (без пробелов), тут обычно никаких ошибок не возникает. Теперь заметьте какую ширину и высоту этот контейнер имеет – match_parent, т.е. учитывая что над ним нет уже никакого контейнера, этот значит, что он будет во весь экран по ширине и высоте. ConstraintLayout – это специальный контейнер, который на экран ничего не выводит, он просто позволяет выстраивать на экране те элементы, которые у него внутри в определенном порядке и по определенным правилам. О нём позже.
С размером элемента TextView всё понятно, а как же поменять цвет текста, цвет фона, сделать текст более жирным или наклонным?- возможно спросите вы. Давайте теперь сменим подвкладку на эту (“Design”):
Теперь нужно кликнуть на компонент TextView:
После этого справа отобразятся все его свойства, также обратите внимание на ползунок Scroll, который означает, что это лишь часть атрибутов, а есть ещё, если протащить ползунок вниз:
Видно поле text со значением “Привет, мир!”, видны layout_width и layout_height и ещё много чего. Далее вниз будут и жирность и наклон и т.п., находятся в группе textStyle:
Цвет текста – это textColor, а цвет фона – background
ОЧЕНЬ ВАЖНО! Заметьте как написан атрибут textColor – буква С – заглавная. В языке Java, а также и ещё во многих языках, textcolor и textColor воспринимаются как совершенно разные переменные и атрибуты, т.е. мы имеем дело с регистр чувствительным синтаксисом.
Задание по уроку:
1. Установите текст поля вместо “Привет, мир!” на текст “МОИ ХОДЫ”
2. Разберитесь самостоятельно, используя поисковик Гугл или Яндекс (это в будущем придётся делать вам часто, лучше сразу привыкать) как задаётся цвет текста в HTML, как правильно записывается цвет в цифровом виде RGB (красный, зеленый, синий). Как примерно происходит смешение трех цветов формата RGB. Установите на текст “МОИ ХОДЫ” цвет текста темно-сиреневый (насколько тёмно и сиреневый – на ваш вкус). Пришлите в коммент скриншот что получилось.
3. Попробуйте установить цвет фона для всего отображаемого экрана на бирюзовый
Если всё получилось как нужно – молодцы! Если нет, давайте разберёмся что не так.
Java Android. Урок 3 (“Элементы”)
На прошлом уроке мы начали рисовать пользовательский интерфейс, а на этом его закончим и приступим нажимать на клавиши в редакторе кода 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. Самостоятельно прогуглите тему, чем отличается векторная картинка от растровой в общих чертах.
Если всё получилось как нужно – молодцы! Если нет, давайте разберёмся что не так.
Java Android. Урок 4 (“Первый код”)
После того как вы разместили элементы отображения, задали им необходимый порядок на экране, давайте снова запустим приложение посмотреть его в двух режимах, в портретном и альбомном. Делается это при помощи кнопок поворота виртуального устройства:
В результате должно получиться как-то так:
Портретный режим:
Альбомный режим:
После этого хорошо стало видно, что игра очень плохо смотрится при такой настройке в альбомном режиме, по этому проще будет вообще такую возможность сразу отключить. Если вы замечали, некоторые приложения так и построены, как ни верти телефон, интерфейс не поворачивается. Ничего в этом плохого нет, особенно когда понимаешь, что в таком расположении будет просто не удобно при каждом повороте адаптироваться. Тут в альбомном режиме придётся часть интерфейса преносить вправо или влево и нарушается восприятие логики игры. Хотя, при желании можно и попробовать, но не сейчас. По этому познакомимся еще с одним файлом проекта под названием AndroidManifest.xml. Это файл описания структуры нашего приложения, его названия и еще много чего, что потом будет передано в итоговый файл для публикации приложения. Именно в этом файле также располагаются и все требования прав, например, такие как разрешение на доступ к дискам, доступ в интернет, доступ к камере устройства и т.д. Файл также оформлен в формате XML, с которым мы начали знакомиться на прошлом уроке, но только он описывает другие данные, но по аналогичному принципу. Сейчас в этом файле вам нужно найти контейнер, отвечающий за окно, которое делали в прошлом уроке, и добавьте новый атрибут с необходимым значением:
После этого как экран не вертите, окно будет в режиме портрета:
Верните положение виртуального телефона обратно в привычное:
Отройте вкладку с кодом, должен он выглядеть примерно так:
Начинаем разбираться. Весь основной код этого файла размещается внутри объекта “public class MainActivity…” между его фигурными скобками. Кто не читал мою статью:
Начало программирования вообще с нуля. Часть 1
крайне рекомендую. Я в этой статье объяснял что такое классы и для чего нужны. Язык Java не исключение, он тоже позволяет описывать такие объекты, как классы. Наш класс называется MainActivity и расширяет класс AppCompatActivity. Как всё происходит. При нажатии на иконку приложения в вашем мобильном телефоне для его запуска, или запуске приложения через виртуальную среду по кнопке “Run” (как вы уже это несколько раз делали), происходит сначала анализ файла AndroidManifest.xml, который мы только что смотрели и указывали там на портретное представление. Однако, там есть ещё один контейнер, который указывает, какой объект нужно активировать при запуске приложения:
Находится он внутри контейнера activity с атрибутом имени “.MainActivity”. Как мы уже знаем, что всё что находится внутри какого-то контейнера XML, относится именно к этому контейнеру. Следовательно выделенный контейнер intent-filter со всем его содержимым, относится именно к “.MainActivity”. Эти данные как раз и будут сообщать системе Android, что при запуске нужно создать объект класса MainActivity. Вы наверно спросите,- А зачем ему об этом сообщать, если ничего другого больше тут запустить нельзя?- Да, здесь действительно нельзя, но чаще всего можно. Например, вы наверно замечали, что при работе в каком-нибудь приложение вы переходите из одного окна в другое, а потом можете возвратиться назад, затем снова кликаете куда-нибудь, и текущее окно опять сменяется на другое, из которого тоже потом можно вернуться назад. Может показаться, что у нас есть один некий код, который по разным условиям просто отображает на экране новый интерфейс. Так, например, было до изобретения Андроида. Был один огромный код, который отвечал за всю программу что в ней отображается на каждом этапе (картинка входа, начальные настройки, может реклама какая, и т.д.) Пользователь кликал всё время “Далее” или “ОК”, но код был один единый на все. В Андроиде всё не так. Каждое окно принято разделять на отдельный структурный объект приложения, называемый Активити. Весь фокус в том, что когда вы перемещаетесь по приложению и открываются разные окна, это происходит приостановка работы одного Активити и запуск другого, который также помнит с какого другого Активити на него перешли. Потом вы нажимаете на кнопку “Возврат”, текущий Активити закрывается, а предыдущему снова передаётся управление. Например, рекламная заставка перед запуском приложения у многих разработчиков это тоже отдельный Активити. Как раз он и указан в манифесте как запускаемый. Например, у меня манифест приложения “Быки и коровы” выглядит так:
Сначала запускается PreActivity, где оставлена возможность показать рекламное сообщение. Оно несколько секунд его покажет, а затем при помощи специального кода автоматически откроет MainActivity. Класс RulesActivity – это Активити, которые вы тоже будете делать, в нём будут отображаться правила игры, если пользователь захочет их почитать. Вот по этому, возвращаясь к нашему вопросу, зачем указывать какой Активити нужно запускать при старте приложения, и нужно строго указать. Если этого не сделать, система не будет разбираться есть ли ещё какие Активити или их нет, сколько их,- не указали что запускать – вообще не запустит никакой Активити.
Вернемся к коду. Что происходит дальше. Система прочитала файл манифеста, узнала, что для запуска ей нужен класс MainActivity, она создает объект этого класса и вызывает метод этого класса onCreate():
Обратите внимание, что перед началом метода стоит аннотация @Override. Так делается чтобы не запутать компилятор и самому не запутаться. Это слово означает, что мы хотим в этом классе воспроизвести такой же метод, который есть в родительском классе AppCompatActivity или где-то ещё выше по уровню.
Вызов super.onCreate() необходим для правильной инициализации Активити.
Вызов setContentView передаёт Активити наш файл activity_main.xml для того чтобы система проанализировала его и на основании его данных создало интерфейс нашего Активити. Если вы закомментируете эту команду, то после запуска будет просто пустой экран:
По этому пока не будем так делать и вернём как было.
Попробуем теперь как-то получить доступ к нашему интерфейсу программным способом. Напишите следующий код внутрь метода onCreate:
После перезапуска должно получиться вот так:
Как вы поняли, таким образом мы получаем доступ к элементам интерфейса. Функция findViewById() пытается найти по идентификатору элемент нашего Активити. Однако, как я говорил уже на прошлом уроке, все элементы интерфейса – это классы, которые расширяют класс View, или как ещё часто говорят наследует класс. По этому findViewById() возвращает не именно класс, того элемента, который закреплен за этим идентификатором, а их родительский класс. Так делается исходя из рационального подхода, иначе пришлось делать на каждый поиск по ID отдельный метод со своим типом возвращаемого значения. Гораздо проще сделать одну, возвращающую родительский тип класса, а программист сам разберется к какому типу определенного элемента ему привести. Так и происходит в коде выше:
(TextView) findViewById(R.id.textViewNumber)
здесь (TextView) как раз и означает – привести к типу класса TextView. Это нам нужно для того чтобы мы смогли выполнить следующий метод setText(), применимый уже к объекту TextView, который устанавливает нужный текст. Т.е. у класса View нет такого метода, например, нельзя установить текст на контейнер ConstraintLayout, т.к. я говорил, что этот контейнер ничего не отображает, а просто расставляет элементы в нужном порядке; при этом он тоже наследуется от View. Иными словами, компилятор не даст нам сделать вот так:
Красная надпись говорит, что в таком виде скомпилирован код не будет, это ошибка
Если вы неправильно укажите приводимый тип, то скорее всего это вызовет ошибку выполнения уже во время выполнения этого кода в приложении (про ошибку выполнения я так же писал в статье о программировании с нуля).
Ошибка выполнения может также произойти, если findViewById не нашёл такой идентификатор в вашем файле activity_main.xml (тогда результат будет null), например, если такой же идентификатор был в файле другого Активити. Да, компилятор не анализирует, откуда вы тянете этот идентификатор, из этого Активити или другого, всё дело в том, что никто не запрещает менять интерфейс “на ходу”, по этому что вы делаете находится на вашей совести. Подсунуть вообще нигде несуществующий идентификатор не получится, Студио это хорошо отследит:
С текстовым полем и некоторыми особенностями немного разобрались. Теперь давайте посмотрим, как нам отработать нажатие на кнопки 0-9, чтобы нужные цифры появлялись в текстовом поле. С этим немного сложнее, но тоже разберёмся, это основы без которых нельзя:
Кнопки мы обозначали идентификаторами textView0…textView9. И вот следующее, если вы делали как я, и для кнопок использовали тоже TextView, то тогда получить объект кнопки вам нужно абсолютно аналогичным путём, например:
(TextView) findViewById(R.id.textView0)
Если же вы пошли своим путём и использовали для кнопок класс Button, то…правильно – нужно просто поменять приводимый вид:
(Button) findViewById(R.id.textView0)
Теперь нужно это всё заключить в отдельные круглые скобки чтобы сказать, что дальше мы будем обращаться не объекту View, который даёт findViewById, а к объекту TextView / Button, например:
((TextView) findViewById(R.id.textView0))
а вот теперь вызываем для этого приведенного класса вот такой метод:
((TextView) findViewById(R.id.textView0)).setOnClickListener(this);
Этот метод есть как и у TextView, так и у Button, и вообще он есть у родительского View, по тому является наследованным у всех элементов интерфейса. В данном примере, этот метод сообщает элементу textView0 (наша кнопка с цифрой “0”), что теперь это не просто элемент-кнопка с текстом “0”, а элемент, который будет отслеживать, не кликнул ли кто по нему пальцем, и если это так, то передать управление в этот наш Активити (ключевое слова this). По этому как только мы добавим этот метод, компилятор нас любезно попросит указать реализацию метода, который и примет на себя нажатие кнопки, а иначе будет проблема с компиляцией:
Слева видим, что появилась кнопка с выпадающим списком, в котором предлагается несколько решений проблемы. Нас интересует тот, который предлагает организовать реализацию требуемого метода. Далее среда разработки предложит более подробно пояснить реализацию чего мы хотим организовать. Тут обычно курсор сразу стоит там, где надо и остаётся только нажать ОК:
Кликнув на ОК, автоматически получим некоторые изменения в коде, но теперь появилась другая проблема, которую также можно решить автоматически:
Эта проблема говорит о том, что как бы ты – программист, решил реализовать интерфейс (это не тот интерфейс, который мы видим, это термин такой же, но смысл у него совершенно другой, это вносит небольшую путаницу, далее поясню) View.OnClickListener. Но для реализации нужно теперь выбрать методы этого интерфейса, которые ты хочешь прямо сейчас реализовать. Ни завтра, ни послезавтра, ни когда чаю попьёшь в раздумьях, а именно прямо сейчас, но реализовать их всех придётся по любому, теперь без этого никак. Благо что у этого интерфейса View.OnClickListener только один метод, требуемый для реализации:
Да, верно! Опять такое же окно. Океем и его тоже и опять наблюдаем автоматическое изменение кода:
В коде появился новый метод и все предупреждения об ошибках исчезли. Теперь рассказываю что всё это означает и что конкретно мы сделали.
Пока мы не стали вызывать метод setOnClickListener с параметром this, всё было хорошо. Но после его вызова мы сказали среде разработки следующую фразу:
Среда разработки, я знаю, что элемент экрана textView0 может отслеживать нажатие на него пальцем пользователя и вызывать метод onClick(). Сделай так, чтобы этот метод вызывался не просто где-то там внутри этой кнопки textView0, а ещё и вызывался у меня в моём классе тоже…ну заодно, не жалко же тебе?! У себя вызывал, сделал что-то, ну и мне тоже передай, я у себя тоже сделаю чего-нибудь теперь.
Такой подход называется имплементаций или по-русски – реализацией методов. Это работает только по тому, что когда разработчики Андроид делали классы TextView, Button и ещё много других, то они заранее предусмотрели, что программисту делающему приложение, явно понадобится по нажатию на эти элементы выполнять какие-нибудь полезные действия. Но программисты Андроида конечно же не могли знать заранее что это будут за действия у каждого программиста, по этому сделали лазейку и сказали, вот вам метод onClick(), если хотите, я буду его вызывать для вас после нажатия, но как вы будете использовать его, решайте сами в своём коде.
Интерфейсом в имплементации называется специальная конструкция кода, которую программисты класса View объявили в своём коде; как её делать я опять-таки буду рассказывать немного позже, когда наглядно смогу продемонстрировать его эффективность.
Возможно вы спросите, а почему система разработки сразу не создала все реализации на все элементы? Функцию onCreate она же создала сама, так почему на другие не сделала. Ответ прост – это не рационально. Зачем, например, мне отслеживать нажатие на надпись “МОИ ХОДЫ” или на текстовое поле с введенным числом..и самое главное – интерфейс View.OnClickListener не единственный у элементов. Элементы могут отслеживать не только событие нажатие на него пальцем. Есть событие длинного нажатия пальцем, у полей ввода есть события изменения текста, событие непосредственно перед изменением текста, есть события у списков, у скроллов, когда пролистывается экран, их сотни. Представляете как был бы загружен мусором код, сколько было бы в нем объявлено методов, которые в большинстве мы бы даже и не думали реализовывать и были бы нам не нужны.
Давайте вернёмся к коду. Перенесите теперь код отображения числа “1234” следующим образом:
Теперь перезапустите приложение. Число 1234 на экране не отобразилось. Теперь кликните мышкой в виртуальном устройстве по кнопке “0”. Теперь это число появилось только после нажатия на кнопку. На этом примере мы теперь можем понять как происходит взаимодействие в пользовательском интерфейсе – пользователь что-т нажимает, автоматически запускается определенный метод, мы его отрабатывает, возможно что-то отображаем на экране и т.д.
Для того чтобы все кнопки начали реагировать на нажатие, нужно у каждой вызвать метод setOnClickListener(this), однако реализация метода будет по прежнему одна, вам нужно будет только правильно определить какая именно нажата кнопка. Делается это вот по такому принципу:
Т.е. в методе onClick(), когда пользователь нажал на кнопку, определяем идентификатор той кнопки, на которую нажали, а затем командой if сравниваем с теми идентификаторами, которые мы давали этим кнопкам. Таким образом определяем какая же именно кнопка была нажата.
Но нам для нашего проекта потребуется сделать как-то так:
Вы наверно догадались, что такой код при каждом нажатии на кнопки, будет прибавлять справа к строке textNumber очередной символ с цифрой числа, а затем отображать его в поле textViewNumber. Обратите внимание как я прибавляю цифру “2”; это упрощённая запись, компилятор так позволяет делать. Обратите ещё внимание на то, что хотя мы в нашей игре говорим “загадать число”, по факту, мы работаем не с числом, а со строками, содержащими цифры. Это принципиальный факт.
Задание по уроку:
1. Добавьте код для обработки нажатия для всех кнопок
2. Самостоятельно найти в поисковых системах, как в Java Android уменьшить строку на один символ (стереть один символ справа), как узнать длину строки, как взять символ из середины строки, например, взять второй символ из строки, узнайте как правильно сравнить одну строку с другой.
Если всё получилось как нужно – молодцы! Если нет, давайте разберёмся что не так.
El Vinto, 2023 (Copyright)
Java Android. Урок 5 (“Второй код”)
На прошлом уроке вы сделали обработку нажатия пользователем на кнопку для каждой кнопки пользовательского интерфейса. Однако, код стал быстро расти в объёме. Давайте посмотрим вот на что:
Кто читал мою статью о программировании с нуля (Часть 1), то помнит, что обычно принято повторяющиеся команды помещать внутрь цикла. Это делает код рациональным и более близким к восприятию (программист, читающий код, будет понимать, что внутри цикла всё тоже самое, только как-то зависит от самого цикла). В нашем примере (а у вас должна быть уже написана обработка на каждую клавишу), мы должны объявить инициализацию реализации нажатия на каждую кнопку. Давайте теперь мы попробуем сделать так. Сначала создадим массив целых чисел и сразу же заполним его значениями:
Т.е. мы просто в массив перенесли идентификаторы всех наших цифровых(!) кнопок. Обратите внимание, что идентификатор – это целое число (иначе бы среда разработки начала “ругаться”).
Теперь внутри onCreate() поменяйте код инициализации на такой:
Согласитесь, так выглядит намного компактнее. Почему я не внёс три служебные кнопки в массив, по аналогии, будет понятно далее.
Теперь, конечно, хотело бы также оптимизировать и обработку нажатия. И тут появляется несколько возможных решений. Если бы у нас массив содержал сразу соответствие с цифрой числа и содержал бы только кнопки цифр 0-9 мы могли бы как-то это представить циклом. Можно конечно расписать каждую кнопку отдельно. Я так тоже часто делаю, чтобы легко было искать нужное с точки зрения программирования. Хотя если проявить творчество, то можно сделать и по третьему варианту, давайте его рассмотрим (но все варианты одинаково возможны); заодно сразу сделаем заготовки для служебных кнопок:
Рассмотрим какие изменения произошли. Первое, что мы сделали – это вынесли получение идентификатора кнопки в отдельную переменную id. Благодаря этому вызов функции getId() будет происходить только один раз. Это мы оптимизировали с точки зрения скорости выполнения. Далее определяем, если переменная textNumber, которая накапливает нажатия на цифровые кнопки, по длине меньше 4, то можно добавить ещё одну, ведь по правилам игрок должен загадать именно 4-х значное число. Далее запускаем цикл, в котором перебираем весь массив идентификаторов наших кнопок и сравнивает, идентификатор нажатой кнопки не равен ли идентификатору из массива. Если это так, то прибавим к textNumber индекс элемента массива.
Тут хочу обратить внимание вот на что – индексы массивов считаются от нуля (самый первый элемент массива с индексом ноль), и значения этого массива – это идентификаторы элементов наших кнопок тоже от кнопки с цифрой ноль. По этому значение идентификаторов цифровых кнопок, соответствует индексу массива. По этому-то и в сам массив мы отправили только цифровые кнопки, и не стали мешать служебные кнопки. В теории, конечно можно было бы сделать так:
Однако, это очень плохой вариант. Мы бы стали строить идеологию проекта исходя из того, что массив кроме цифровых содержит и другие кнопки, а потом, по прошествии какого-то времени, забыли бы, что мы должны использовать первые 10 элементов только(!) для цифровых кнопок и сделали бы, например, вот так:
По этому будем делать так, чтобы исключить из программирования пагубные привычки. Дело в том, что если это интерфейс пользователя, то ошибку такую мы сразу найдём (у нас по цифре ноль начнёт появляться на экране единица, а по единице – двойка). А вот если это какой-то другой массив, который например формирует какие-то расчетные данные, то заметить это можно будет только по прошествии многих часов поиска проблемы, ну или например, когда…Луна-XX вдруг полетит по другой траектории и накроет труд тысяч людей медным тазом…
Идем по анализу кода далее. Внимательный слушатель курса заметит один момент. У нас индекс массива – это целое число, а переменная textNumber – это строка, но при этом я делаю так:
Да, компилятор позволяет так делать, хотя по всем канонам вообще правильным было бы так:
Но такое делать компилятор умеет не всегда правильно. Например, если вы сделаете как-то так:
это приведёт к крэшу приложения (остановка работы по фатальной ошибке с принудительным выходом из приложения) . Вся проблема тут была в том, что компилятор подумал, что в качестве целочисленного значения вы передаёте ему не число 34, а идентификатор ресурса (кто разобрался как делать домашнее задание одного из прошлых уроков, понял о чём это я), который как раз тоже является целым числом. Поскольку идентификатора ресурса с ID=34 в приложении не существует, это вызвало ошибку выполнения. Т.е. вот следующий код прошел бы на “Ура!”:
Так, слишком отвлеклись…В нашем примере textNumber += i компилятор понял, что мы конечно же хотим именно число привести к типу String (просто здесь наиболее вероятным было бы ожидать от программиста, что он поставит сюда именно число, а не идентификатор ресурса, потому разработчики компилятора и сделали так), а потом произвести конкатенацию (сложение строк, когда к одной строке присоединяется другая строка справа или слева).
Если условие поиска цифровой кнопки отработало, то значит нажата была именно цифровая кнопка, мы выполнили конкатенацию, теперь должны обновить на экране пользователя это число и выйти из метода (больше там сравнивать вообще не чего).
Если пользователь нажал кнопку Backspace (стереть последний символ), т.е. кнопку с идентификатором textViewBackspace и при этом текущая длина строки больше нуля (ну, когда там вообще есть что стирать), то выполняем выражение, которое стирает последний символ строки. Кто по честному выполняет домашние задания, уже знает, что функция substring() получает часть строки. В качестве первого параметра указывается позиция в строке, с которой нужно взять эту часть, а в качестве второго параметра указывается позиция до которой взять (НЕ включительно).
Теперь измените значение по-умолчанию для поля (кто этого ещё не сделал):
и перезапустите приложение. Проверьте ввод цифр и возможность их стирания. Всё должно работать.
Что мне не особо нравится в работе этого кода, так это отображение знаков “_”. Сначала они есть и это смотрится красиво, но по мере набора и стирания они пропадают, а по идее хорошо бы, если они выглядели как-то так (да и хорошо бы введенные цифры блокировались, а при стирании разблокировались):
Для начала немного изменим код обработки нажатия:
Тут среда разработки начнёт подсказывать, типа “А что это такая за странная функция? Я её не знаю”. Конечно же не знает, мы её ещё только сейчас начнём писать. По этому добавьте её ниже метода onClick() и сделайте её сразу правильно:
В этом методе мы должны сначала создать отдельную переменную numberAsText, которая будет хранить число вместе со знаками подчеркивания “_”. Так же сразу выполним цикл, который отметит все кнопки доступными для нажатия setEnabled(true) и укажем для них зеленоватый цвет. Далее выполним цикл от 1 до 4 в котором будем делать следующее. Если индекс цикла больше длины строки введенного уже нами числа, то заполнить это знаком подчеркивания (эти знаки в конце после цифр), в ином случае взять цифру из нашего числа по этому индексу (в виде целого числа) и поместить в эту нашу новую переменную numberAsText, после чего взять по этому числу как по индексу идентификатор элемента пользовательского интерфейса и отметить кнопку как недоступную для нажатия и установить на неё серый цвет фона. Далее выполняется заодно код, который определяет длину строки (длина строки по логике – это как раз число разрядов числа), и если эта длина равна 4, то отобразить кнопку “Enter”, в ином случае скрыть её. Обязательно разберитесь с этой частью кода чтобы хорошо понимать каждое действие, что мы делаем каждым выражением. Обращаю внимание, ранее мы представляли из целого числа строку и компилятор это смог сделать самостоятельно, а вот наоборот из строки сделать число он уже не может, по этому необходимо выполнить соответствующую функцию Integer.parseInt(). Это нужно нам как число, т.к. дальше нам нужно обратиться к элементу массива digButtons, где аргументом не может быть строка.
Мы с вами уже познакомились с некоторыми инициализациями переменных, пора бы теперь поговорить о них немного подробнее. Давайте сначала разберемся какие вообще могут быть переменные.
String a = “”; // это строка, мы уже несколько раз такое делали в нашем коде
int b = 0; // что-то похожее делали буквально только что, когда преобразовывали из строки в целое число
boolean c = false; // Это булева переменная, которая может принимать значение только истина или ложь (true / false)
long d = 123; // длинная целая переменная, позволяет хранить максимальное целое число в два раза большее, чем int.
float e = 12.3; // позволяет хранить нецелые числа (дробная часть отделяется точкой)
double f = 12.34353453; // Позволяет хранить нецелые числа, но в два раза с большей точностью, чем float
ClassVariable g = null; // Переменная, которая хранит ссылку на какой-то класс. Например, вот так:
Есть и ещё, но пока этих достаточно.
Теперь пришло время поговорить об области видимости переменных. У нас есть уже переменная textNumber, которую я объявил вне какого-либо метода, но внутри класса:
Такая переменная называется переменной класса и видна из любого метода класса (к ней можно обратиться из любого метода, мы так и делаем, то из onClick(), то из setNumber() ).
Есть переменные метода, например int id в методе onClick() или String numberAsText в методе setNumber(). Они видны только из того метода, где объявлены. Т.е. я, например, не могу обратиться к numberAsText из метода onClick(), среда разработки не даст это сделать.
Переменные, которые объявлены внутри объявления цикла видны в этом объявлении и везде внутри этого цикла, снаружи уже не видны. Например, int i в цикле:
for (int i = 0; i < digButtons.length; i++ /* да, тут видна i */) {
/* где-то тут тоже видна i */
}
/* а тут уже i не видна, здесь её использовать не получится */ .
Переменные, которые объявлены внутри условия, видны везде внутри этого условия, например, int num внутри else:
if (i > textNumber.length()) {
/* тут вообще не знает что такое num */
} else {
/* кстати, если что, и тут не известно что такое num */
int num;
/* тут видна num */
}
Идем теперь далее. Перед началом каждого раунда, игра должна сгенерировать 4-х значное случайное число. Это будет то самое число, которое загадал компьютер и которое предстоит узнать человеку в процессе определенного числа попыток. Функционал Java позволяет сгенерировать целое случайное число. Делается это в два этапа. Сначала объявляется переменная класса Random, а затем выполняется один из её методов, отвечающих за генерацию числа в определенном диапазоне:
Random random = new Random();
int rndNum = random.nextInt(10);
В данном примере rndNum будет содержать случайное значение в диапазоне от 0 до 9, т.е. как раз одну случайную цифру. Однако, тут вы возможно воскликните,- Э-э-э, автор, у тебя что-то с математикой не в порядке! Нам надо 4-х разрядное число, а это тогда диапазон не до 10, а до 10 000. )))…Но я наверно сделал так тоже не спроста, наверно потому что задача не так тривиальна. Помимо того, что нам нужно сгенерировать 4-х значное число, нам нужно ещё и чтобы все цифры были в нём разные)). По этому объективных решения здесь минимум два, я просто выбрал одно из них, которое счет наиболее простым в реализации…В домашнем задании попробуйте разные способы.
Иногда возникает ситуация, когда в процессе разработки нужно посмотреть значение какой-либо переменной, но выводить это на экран телефона не всегда удобно – для этого придётся создавать какой-то элемент textView для отображения, а это вносит сумятицу в разработку. По этому разработчиками был придуман метод логгирования. Давайте, например, проверим какие числа будут сгенерированы случайным методом, описанным только что, например, с 10 попыток. Делается это примерно так:
Кстати, тут может возникнуть ещё вот такая ситуация:
Т.е. среда разработки не находит функцию Log. Наведите на это слово курсор мыши, в результате появится знакомая кнопка решения проблемы:
Нужно импортировать класс. Вся проблема тут в том, что изначально в приложение на загружаются весь функционал Андроида, т.е. например, зачем загружать в приложения огромный пласт кода для работе с трехмерной графикой, если вы делаете обычный калькулятор. Также и тут, изначально среда разработки не включила функционал логгирования в ваше приложение, но уж коли вы хотите его использовать, то скажите об этом наверняка. Иногда может получиться так, что библиотека импорта на выбор будет не одна (например, если эта функция с таким именем встречается сразу в нескольких различных библиотеках и выполняет несколько разные по смыслу действия), то в этом случае придётся разбираться и возможно гуглить, откуда же её тянуть. В данном случае, библиотека для импорта только одна: android.util.Log.
Может получиться и ещё одна ситуация, когда при наведении куда-то мышью, вдруг система разработки внезапно очнётся и увидит, что решение есть, но пользовательский контекст другой:
Тут спрашивает, мол это из android.util.Log? Тогда жми одновременно на клавиши Alt+Enter!
В действительности, будет добавлена библиотека импорта (в самом верху кода):
Перезапускаем приложение, но смотрим не виртуальный мобильник, а на экран разработки. В результате получится вот так:
Ещё давайте рассмотрим небольшую конструкцию, которую я использовал, это вот такое выражение:
i++
На самом деле – это упрощённая запись i = i+1 (до этого вы могли и догадаться), т.е. взять переменную i, прибавить к ней 1, а результат снова поместить в переменную i. Например, если до этого выражения в переменной i было значение 4, то после будет значение 5.
Нельзя пройти стороной и похожую конструкцию:
++i
Которая делает абсолютно тоже самое, но только как чесать левой рукой правое ухо.
Сделайте так чтобы посмотреть разницу и перезапустите приложение (мы немного отвлеклись и поиспользуем наше приложение для некоторых тестов):
Нет, здесь нет признаков корпускулярно-волнового дуализма, когда присутствие наблюдателя меняет результат. Возможно вы догадались, почему так произошло. Правильно! num1++ прибавило к num1 значение после выполнения всего выражения, которым был в конечном счете вывод лога, а выражение ++num2 сделало инкремент (увеличение на единицу) до того как начало выполнять выражение. В этом и вся разница. Если мы сделаем так:
То результат будет абсолютно одинаковый:
По этому я часто выношу p++ или ++p за пределы выражения как самостоятельные выражения…но не всегда)). Например, добавлять значения какого-нибудь массива удобно вот так:
ar[i++] = 5;
ar[i++] = 6;
ar[i++] = 7;
А брать значение с последнего вот так:
int len = ar.length;
while (len > 0) {
value = ar[–len];
// …
}
Задание по уроку:
1. Попробуйте самостоятельно сделать метод для генерации 4-х значного числа. Для его хранения сделайте строковую переменную класса compNumber.
2. Сделайте так, чтобы при нажатии на кнопку Enter программа считала число “Быков” в угадываемом числе. Проверьте метод логгирования в разных вариантах.
3. Ответьте, какое значение p будет после выполнения следующих действий, после этого проверьте своё решение при помощи метода логгирования:
p = 3;
p = p+++p;
Если всё получилось как нужно – молодцы! Если нет, давайте разберёмся что не так.
Урок 4 Урок 6
El Vinto, 2023 (Copyright)
Java Android. Урок 6 (“Третий код”)
Вернемся к разработке нашего первого приложения под Андроид. На прошлом уроке мы начали делать функционал (набор исполняемого кода и данных необходимых для выполнения определенных вычислительных операций) генерации числа, которое компьютер по правилом игры должен “загадать”. Напоминаю, нам нужны следующие условия для такого числа:
- Число должно состоять только из 4-х цифр
- Все цифры числа должны быть разными
Методов решения этой задачи несколько, по этому нужно просто какой-то выбрать. Заранее скажу как я буду это делать. Сначала я получу 4 цифры числа случайным образом таким образом, чтобы каждое следующее случайное число – значение цифры загадываемого числа, отличалось от уже всех ранее сгенерированных. Это даст выполнение правила, что все цифры числа должны быть разными. С первым условием всё просто, мы просто будем генерировать от 1 до 4-х.
Однако, прежде чем разбираться как же тут получается число со всеми необходимыми условиями, необходимо отвлечься и познакомиться с некоторыми конструкциями кода. В Java есть 3 вида циклов, т.е. конструкций кода, позволяющих несколько раз выполнять один и тот же код (циклическое выполнение).
Первый из них мы уже много раз встречали – это обычный цикл for. В области параметров этого цикла есть три части кода, разделенные точкой с запятой. Первая часть инициализирует переменную (задаёт ей начальное значение), вторая часть проверяет соблюдается ли условие (будет ли результат этого условия равен значению true), третья часть – выражение, которое должно выполняться после каждой итерации (этап прохождения по циклу). Пока значение второй части true, цикл будет совершать итерации (хоть до бесконечности). Давайте это рассмотрим прямо из нашего нового метода:
for (int i = 1; i < 5; i++) {}
Сначала объявляем и инициализируем переменную i значением 1. Во второй части будем сравнивать – значение переменной i для выполнения итерации должно быть меньше 5. В третьей части производим инкремент этой переменной (тема прошлого урока). Таким образом, каждую итерацию i будет увеличиваться на единицу и в конечном итоге достигнет значения 5. Условие i < 5 перестанет выполняться, цикл завершится и управление будет передано первому выражению следующему за циклом (за закрывающей скобкой этого цикла).
Вторая конструкция цикла у нас тоже в этом методе есть и выглядит так:
do {
} while (isNoOk);
Тут цикл начнёт выполняться без всяких условий сразу же, но вот будет ли выполнен повторно определяется условием после завершения этого цикла, что не скажешь о предыдущем, где чтобы начать выполнение цикла (вхождение в цикл) нам требовалось условие i < 5, это же условие требовалось и для продолжения его выполнения. Тут, как понимаете, по логике получается немного по другому. Тут стоит обратить внимание, что если переменная i в цикле for (который ещё иногда называют по-старинке “цикл for…next”) видима как в объявлении цикла, так и внутри него, то в цикле “do…while” нет инициализации, а все используемые там переменный должны быть либо объявлены где-то перед этим циклом, либо иметь более глобальную видимость, например, быть переменными класса (один из прошлых уроков). В связи с этим получается, что переменная isNoOK должна быть объявлена перед этим циклом. Если посмотрите на код метода generateComNumber(), я именно так и сделал. Итак, подытожим. Цикл do…while начинает выполняться без всяких условий и будет выполняться до тех пор, пока значение внутри while будет true, т.е. например, цикл
do { }
while(true);
будет выполняться “вечно” и никогда не завершиться, пока работа приложения не будет прервана извне этого приложения (действиями пользователя или действиями операционной системы). По этому задача программиста сделать так, чтобы внутри этого цикла было что-то, что может поменять условия для выхода из цикла. Для этого я выполняю действия влияющие на это:
do {
num = random.nextInt(10);
isNoOK = compNumber.indexOf(“” + num) > -1;
} while (isNoOK);
Сначала я получаю случайное число num в диапазоне 0…9, затем преобразовываю его в строку и пытаюсь найти в уже сформированной итоговой строке compNumber. Функция indexOf как раз это и делает. Если искомая строка не найдена, результат будет равен минус 1, а если найдена, то позиции искомой строки и строке поиска. Сама позиция нам не нужна, тут нам просто нужно проверить есть ли это в строке. Обратите внимание как я получаю из числа строку. На одном из прошлых уроков я говорил, что иногда компилятор правильно делает такое преобразование если ничего не указывать, но по всем правилам преобразование нужно делать через String.valueOf(). Но есть и ещё метод – это прибавить число к пустой строке:
“”+num
Компилятор видит перед знаком плюс строку (пусть она и пустая) и понимает, что нужно производить конкатенацию строк, а значит число справа скорее всего не какой-то там идентификатор ресурса, а обычное число.
Стоит сразу же сказать и о ещё одном виде цикла, который используется наравне с этими двумя. Это цикл
while(условие) {
}
Он является почти аналогом цикла do…while, но отличается только тем, что для первого вхождения в цикл нужно выполнения условия (для do…while этого не требовалось).
Из любого цикла можно выйти и не дожидаясь пока итерация закончится и наступит момент проверки условий. На примере цикла while делается это примерно так:
while (true) {
if (какое-то условие) {
break;
}
/* ничего этого выполнено не будет, если сработает break */
}
/* сюда будете передано управление после выполнения break */
В этом примере казалось бы условие цикла всегда будет true и цикл будет выполняться “вечно”, но это не так, т.к. внутри цикла встроена конструкция из условия, по истинному результату которой, будет выполнена команда break. Это команда прерывает цикл, выходит из него прямо с этой команды.
ВАЖНО! Команда break выходит только из текущего цикла. Если один цикл вложен в другой, выход будет осуществлён только из текущего цикла, но НЕ(!) из вышестоящего.
Все циклы имеют команду принудительной итерации. Это команда continue. Она работает похоже как break, только не прерывает цикл, а делает так, что цикл переходит на следующую итерацию, конечно же, если условия цикла, требуемые для этого, выполняются:
while (true) {
if (какое-то условие) {
continue;
}
/* ничего этого выполнено не будет, если сработает continue */
}
/* в данном примере здесь никогда управление не окажется, т.к. нет ничего чтобы прервало цикл. Это “вечный” цикл */
В следующем примере:
Цикл станет “вечным”, т.к. выход по break будет только из внутреннего цикла, но внешний продолжит выполнение. Это касается циклов всех типов.
Теперь, основываясь на наших новых знаниях о циклах, рассмотрим более подробно мой метод генерации числа. Сначала я объявил экземпляр класса Random и поместил его в переменную random. Этот класс позволяет программисту работать с генератором случайных чисел. Далее следует сброс compNumber – это строкова переменная класса, которую вы должны были добавить в домашнем задании. Далее делаем цикл от 1 до 4, а внутрь этого цикла помещаем ещё один, который будет выполняться до тех пор, пока не сгенерирует цифру числа, которой ещё нет в загадываемом числе. Когда функционал находит такую цифру, он конкатенирует её к compNumber и процесс повторяется 4 раза. Таким образом, в результате будет необходимое число. Посмотрите внимательно каждую команду этого метода generateComNumber(). Вы должны хорошо понимать какое действие будет выполняться в каждом выражении. Попробуйте мысленно или на бумаге воспроизвести как будет меняться значение compNumber при каждой итерации каждого цикла; мысленно воспроизведите как будет осуществляться вхождение в цикл do…while и выход из него при каких словиях.
Сделайте теперь так, чтобы этот метод запускался сразу при загрузке приложения, т.к. именно в этот момент игра уже должна быть готова принять первый вариант пользователя, желающего угадать число (чтобы было что отгадывать):
Теперь осталось дело за малым – при вводе варианта пользователя просто посчитать число быков и коров, и если количество быков равно 4, то значит пользователь отгадал число, в ином случае сказать пользователю каков результат его попытки.
Для начала напишем две эти функции, одна будет находить число быков, а вторая – число коров.
В домашнем задании вы должны были хорошо познакомиться с функцией substring() и что она делает в этом функционале, вы наверно уже поняли. Да, одна функция берет один символ из строки по индексу i из строки textNumber, другая тоже самое из строки compNumber. Первая переменная содержит число, которое ввел пользователь, а второе – которое “загадал” компьютер. Обратите внимание как происходит сравнение двух строк:
Строка1.equals(Строка2)
значение выражения истина, если Строка1 содержит тот же набор символов в той же последовательности, что и Строка2. В данном случае строки содержат по одному символу.
При расчете коров потребовалось выполнить цикл в цикле для того чтобы исключить из подсчета количество быков, которые бы неминуемо туда тоже попали, а по правилам игры корова – это совпадающая цифра числа находящаяся в другом разряде, но не в том же.
Очень внимательно посмотрите функцию подсчета коров. Вам должны быть понятно все от начала и до конца что там происходит. Если что-то оказывается неясным, лучше задать вопрос и разобраться в этом сейчас.
Осталось теперь только выполнить проверку после ввода числа, по этому допишите код для обработки события нажатия на соответствующую кнопку:
Проверьте выполнение кода, должно получиться как-то так:
Обратите внимание на всплывающее сообщение Toast. Как правильно его использовать в коде и какой будет результат. Такие сообщения вы наверно иногда встречаете в приложениях, когда на несколько секунд появляется информация поверх экрана, а потом сама исчезает. Для тестирования в данном случае такое подойти может, а вот для нашего игрового процесса нет. Нам нужно чтобы каждый вариант с результатом хранился в списке, и мы всегда могли посмотреть его, проанализировать и принять решение для следующего хода. Однако, список требует свежего восприятия, по этому начнём им заниматься на следующем уроке.
Задание по уроку:
1. Ответьте на вопрос, в функции подсчета коров сколько раз будет выполнена проверка условия “if (number.substring…”?
2. Чему будет равна сумма p+q после выполнения следующего кода:
3. Самостоятельно разберитесь с тем, как правильно сделать выход из двух вложенных циклов, если во внутреннем цикле наступает такое требование.
Если всё получилось как нужно – молодцы! Если нет, давайте разберёмся что не так.
Урок 5 Урок 7
El Vinto, 2023 (Copyright)
Java Android. Урок 7 (“Списки”)
Напоминаю, что на наших уроках мы разрабатывает приложение – логическую игру “Быки и коровы”. На прошлом уроке мы остановились перед тем, как начать знакомиться с таким функционалом, как списки. Да, это не просто объект или элемент пользовательского интерфейса, а целый функционал, т.к. комплекс взаимодействующих между собой методов и классов для отображения списков. Казалось бы что проще – вывести на экран один за другим элементы, содержащие строки, но вот тут и кроется вся сложность. Дело в том, что при выводе списка, Андроид выводит не строку, а целый набор элементов пользовательского интерфейса (текст, картинки, флажки и т.п.). Причём меняться они могут, что называется, налету, т.е. в процессе подготовки к выводу на экран. Сам список, тоже может меняться, модифицироваться. Данные списка – это не просто строки, это могут быть массивы различных типов. Однако, давайте начнем по порядку.
Первое, что нужно сделать, это поместить в наш пользовательский интерфейс сам элемент списка и дать ему соответствующие привязки (вообще-то мы это уже сделали ранее, просто не вникали зачем):
Привязки на первый взгляд кажутся странными, но если присмотреться внимательно, то окажется, что список тянется сразу в два направления, верх подтягивается к верхнему над ним элементу, а нижняя часть списка – к нижнему элементу. Вспомним, что самый верхний элемент на экране тянется к верху экрана, а самый нижний – к низу экрана. Таким образом, список будет вытягиваться по высоте в зависимости от того какой высоты экран. Т.е. он станет по высоте динамическим, адаптивным под каждое устройство.
Теперь перейдем к коду. Сначала я создам класс, который будет отражать данные каждого элемента списка:
Т.е. я хочу сделать так, чтобы каждый элемент списка хранил не только простой текст, а отгадываемый вариант числа и сколько у него было быков и коров. Теперь я сделаю класс, который будет наследоваться от класса обслуживающего списки:
public static class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> {
ArrayList<RecycleElement> recycleElements;
private Context context;
private LayoutInflater inflater;
class ViewHolder extends RecyclerView.ViewHolder {
private TextView bulls;
private TextView number;
private TextView cows;
public ViewHolder(View itemView) {
super(itemView);
bulls = (TextView) itemView.findViewById(R.id.textViewBulls);
number = (TextView) itemView.findViewById(R.id.textViewBCNumber);
cows = (TextView) itemView.findViewById(R.id.textViewCows);
}
}
RecyclerViewAdapter(Context context, ArrayList<RecycleElement> recycleElements) {
this.context = context;
this.inflater = LayoutInflater.from(context);
this.recycleElements = recycleElements;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v = inflater.inflate(R.layout.recycleelement_bc, parent, false);
return new ViewHolder(v);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, final int position) {
RecycleElement recycleElement = recycleElements.get(position);
String bulls = “”;
String cows = “”;
if (recycleElement.bulls == 1) {
bulls = “1 бык”;
} else if (recycleElement.bulls > 1) {
bulls = “”+recycleElement.bulls+” быка”;
}
if (recycleElement.cows == 1) {
cows = “1 корова”;
} else if (recycleElement.cows > 1) {
cows = “”+recycleElement.cows+” коровы”;
}
holder.bulls.setText(bulls);
holder.number.setText(recycleElement.number);
holder.cows.setText(cows);
}
@Override
public int getItemCount() {
return recycleElements.size();
}
}
В этом коде, возможно, вам покажется всё сложным, по этому пока особо не вникайте в некоторые конструкции языка, чуть позже всё это разберем. Сейчас остановимся на некоторых моментах. Это класс RecyclerViewAdapter, который мы создаём, наследуется от стандартного RecyclerView с той целью, чтобы могли взять некоторые части управления на себя, а именно – формирование каждого элемента списка из макета (аналогично active_main.xml для формирования пользовательского интерфейса). За формирование отображение отвечает специальный функционал inflater. В качестве одного из параметров ему передаётся ресурс с xml, который и хранит в себе набор элементов пользовательского интерфейса для каждого элемента списка. В данном случае это ресурс R.layout.recycleelement_bc. Добавьте его к проекту следующим образом. Кликните правой кнопкой мыши по папке res и выберите следующий пункт контекстного меню:
Дайте ему имя recycleelement_bc в поле “Layout File Name”. После этого будет создан новый файл проекта и он появится в уже знакомой вкладке; перейдем в ней сразу в подвкладку Code:
Замените находящейся в ней XML на следующий:
Заметьте, что у элементов появились незнакомые атрибуты:
- gravity – указывает к какому краю TextView будет прислоняться находящийся внутри текст. “end|center_vertical” указывает что одновременно по концу текста (в данном случае по правому краю) по горизонтали и по центру по вертикали.
- layout_margin – указывает, что между краями TextView и самим текстом должен быть отступ со всех 4-х сторон равный 4 условные единицы (это чтобы текст одного TextView визуально не примыкал вплотную к тексту другого TextView или иного элемента.
Тут есть ещё одна особенность. Раньше значения параметра gravity были не start и end (начало и конец), а назывались left и right (лево и право). Но потом почему-то называние было изменено. Найти где-то конкретное описание этому изменению мне не удалось, но полагаю связано это с тем, что письменность в некоторых иностранных языках идет не слева-направо, а справа-налево, как например, в арабском. Я думаю, новые значения параметров сделаны как раз для адаптации к этому, что бы в зависимости от языка не приходилось менять left-right местами вручную. Как понимаете, на форумах можно найти по прежнему старые варианты значений.
Теперь вновь возвратимся к классу RecyclerViewAdapter. Заметьте ещё одну интересную конструкцию:
ArrayList<RecycleElement> recycleElements;
В Java Android есть массивы, вы их уже видели, но есть и ещё один похожий объект – ArrayList. Особой разницы между ними нет, кроме того, что массив заранее имеет определенный размер, а ArrayList динамически может менять размер. Т.е. если бы в данном случае мы захотели использовать массив, он выглядел бы как-то так:
RecycleElement recycleElements[ТутКакоетоЧислоЭлементов];
Однако, для списка мы не можем использовать массив, т.к. не знаем заранее до какого размера будет разрастаться наш список. Сразу резервировать память под большое число не рационально. Вообще, если сравнивать их между собой с точки зрения рациональности, то получается такая зависимость (имеется ввиду для ArrayList и массивов большого размера):
Массив:
- Высокая производительность
- Большой расход памяти
ArrayList:
- Низкая производительность
- Низкий расход памяти
Т.е. если для массива вам сразу нужно выделить память такого размера, чтобы влезли туда все элементы, которых может не быть сейчас, но могут появиться потом, то для ArrayList этого не надо, он заполняет память по мере необходимости, но в ущерб скорости работы. Каждый обладает своими плюсами и минусами и именно вам выбирать что использовать в данных определенный момент времени.
Далее, метод onBindViewHolder в этом нашем классе списка – это имплементация (реализация) базового класса, которую, как мы помним, нужно делать всегда. Этот метод как раз отвечает за вывод на экран каждого элемента списка буквально перед самим его выводом. По этому мы можем кое что поменять и заполнить элементы списка так, как нам нужно. В качестве одно из параметров этого метода выступает целочисленный параметр position, в котором система сообщает какой именно сейчас выводится элемент списка. И вот как раз мы в этот момент берём данные этого элемента из нашего ArrayList, немного модифицируем и выводим в нужные элементы элемента списка (извините за тавтологию).
Взять элемент по текущей позиции:
RecycleElement recycleElement = recycleElements.get(position);
Сделать строки текста, понятные для игрока с учетом правил русского языка:
Запихнуть нужный текст в нужный элемент:
Теперь давайте разберемся, откуда программа знает что такое вот это:
Это мы объявили и проинициализировали в нашем классе чуть ранее:
В параметр itemView системой будет передан как раз тот набор элементов, которые мы описали в файле recycleelement_bc.xml. По этому мы должны уже знакомой функцией findViewById() искать не в activity_main или где-то ещё, а именно в нашем recycleelement_bc.xml, ведь его мы тоже передали нашему списку, если помните, вот тут:
Теперь осталось только правильно объявиться и все переменные и список будет готов. Объявите три переменные классе MainActivity:
Да, всё правильно, если вы заметили recycleElements мы уже объявляли, но внутри нашего класса списка и без инициализации, здесь инициализацию (new ArrayList() ) важна, без неё не заработает.
Далее в метод onCreate() добавьте код:
recyclerView – будет хранить в себе элемент пользовательского интерфейса – список; вспомните файл activity_main.xml. Создайте для него менеджер LinearLayoutManager как показано, это тоже необходимо, иначе список не будет обновляться.
recyclerViewAdapter будет хранить экземпляр нашего класса RecyclerViewAdapter, в качестве одного параметра мы передаём текущий класс, а в качестве второго наш объявленный и проинициализированный список. Посмотрите сейчас почему именно два параметра, ведь когда мы создавали наш класс, мы именно так и указали, что для создания экземпляра нам потребуется именно два этих параметра:
Первый был Context (к которому компилятор автоматически привёл наш this, а второй – список ArrayList).
Далее выполняем:
recyclerView.setAdapter(recyclerViewAdapter);
Это позволяет связать наш элемент пользовательского интерфейса recycleView с нашим новый классом. Теперь, когда на экране нужно будет системе перерисовать наш список, наряду с другими элементами, она будет понимать, что для исходных данных этого списка, нужно использовать какой-то класс программиста, в который нужно передавать управление перед отображением каждого элемента списка. Возвращаясь к одному из предыдущих уроков, когда программисты Андроид делали систему, они конечно же не знали как программисты приложений будут выводить списки и какие данные захотят отображать, в каком виде будет это отображение, по этому просто оставили лазейку, мол вот я вывожу элемент списка, могу, программист, и тебе дать возможность там что-то изменить, если хочешь; мы просто этим воспользовались.
Следующая команда
recyclerViewAdapter.notifyDataSetChanged();
запускает процесс регенерации списка. Именно тут она не нужна (только если вы не хотите задать начальный список и сразу же это показать). Однако, познакомить с ней я решил сейчас. Т.е. как понимаете, это команда сообщает системе следующее – “Система, мой список уже недостоверен, какие-то данные там изменены, может какие-то элементы уже удалены, может что-то появилось новое, перерисуй мне весь список заново”.
Теперь измените немного код обработки кнопки ввода числа в методе onClick():
Заметьте, что здесь notifyDataSetChanged() как раз выполняет свою функцию – перезапускает отрисовку элементов (у нас появился новый элемент списка и надо отобразить обновленный список). Перезапустите приложение, после ввода чисел и нажатия на кнопку ввода, должно получаться как-то так:
Задание по уроку:
1. Сделайте так, чтобы если в варианте числа 4 коровы, то в списке он отмечались всегда зеленым цветов
2. Добавьте в список ещё колонку со значком галочка. Сделайте так, чтобы если в варианте числа сумма быков и коров была равно четырем, то у всей этой строки фон был жёлтого цвета.
3. Самостоятельно разберитесь с тем, как правильно получить размер списка ArrayList, как его очистить. Сделайте так, что если игрок совершил 12 попыток и не смог угадать число, то он проиграл, а на экран будет выведена соответствующая надпись при помощи Toast, и игра начнётся заново.
4. Вопрос “на засыпку”. Почему для правил русского языка я сделал только варианты “1 бык”, а если больше единицы, то окончание “-а” в слове “быка”, ну типа 2 быка, 3 быка, почему я не сделал ещё, ведь правильно говорить “5 быкОВ”, “6 быкОВ”, почему нет такого варианта? Также и с коровами.
Если всё получилось как нужно – молодцы! Если нет, давайте разберёмся что не так.
Урок 6 Урок 8
El Vinto, 2023 (Copyright)
Java Android. Урок 8 (“Новый Активити”)
Продолжим разрабатывать наше приложение-игру “Быки и коровы”. Нам осталось сделать совсем немного. Сама игра уже сделана, работает, но не хватает только нескольких улучшений. Начнем с простого. Добавим ещё один активити, который будет пользователю отображать правила игры. Для этого правой кнопкой мыши кликнем по названию пакета в дереве проекта и выберем следующий пункт контекстного меню:
Дайте название RulesActivity.
Теперь перейдите в манифест (это мы уже делали) и добавьте портретный режим тоже и для этого нового Активити:
В дереве проекта выберите ресурс с описанием пользовательского интерфейса этого Активити и откройте уже известную подвкладку “Code”:
Видим, что пока здесь только корневой контейнер. Теперь сюда нужно добавить TextView с описанием нашей игры. Однако, не всё так просто. Как понимаете, объем описания зависит только от ваших возможностей и возможно вы захотите описать всё в мельчайших деталях, а заодно и включить туда какую-то контактную информацию о себе, мол куда обращаться, если что не понятно или нашлись какие-то ошибки в процессе игры. Этот текст может оказать таким, что будет намного превышать размеры экрана. В этом случае пользователь увидит только текст, который вместился у него на устройстве. Это плохо. По этому, когда возникает подобная ситуация и что-то на экране гарантированно может не уместиться, используют такой контейнер как ScrollView. Если помните, до этого мы использовали только контейнер ConstraintLayout. Вернёмся в подвкладку “Design”. Найдите ScrollView в дереве элементов и добавьте его в описание представления:
Вернитесь в подвкладку “Code”. Код теперь может выглядеть так:
Да, контейнер ScrollView может выглядеть не совсем контейнером, а совсем даже не контейнером, а обычным тегом. Если так, то преобразуйте его в контейнер, для этого уберите слэш в конце и добавьте закрывающий тэг:
Теперь внутрь можно уже что-то поместить:
<ScrollView
android:layout_width=”match_parent”
android:layout_height=”0dp”
android:orientation=”vertical”
app:layout_constraintEnd_toEndOf=”parent”
app:layout_constraintStart_toStartOf=”parent”
app:layout_constraintTop_toBottomOf=”@+id/textViewTitle”>
<TextView
android:id=”@+id/textView”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_margin=”5dp”
android:textSize=”16sp”
android:text=”Компьютер загадывает число из 4 разных цифр. Цель игры – делая попытки, узнать это число. Совершить попытку можно только если заполнены все четыре разряда (после этого появится справа внизу соответствующая кнопка). После каждой попытки компьютер будет сообщать сколько в указанном вами числе быков и коров. Корова – это когда в вашем варианте есть цифра, которая присутвтвует в загаданном, но не соответствуют разряду (позиции), бык – это когда в вашем варианте есть цифра и она также находится в правильном разряде. Получается, что игрок выигрывает когда в его варианте оказывает четыре быка. Например, компьютер загадал вариант 1234, а вы предлагаете вариант 4235. В этом случае будет два быка – цифры 2 и 3, т.к. они есть в загадонном и расположены в таких же разрядах, и одна корова – цифра 4, т.к. она тоже есть, но находится не на своём месте; цифра 5 в вашем варианте отсутствует в загаданном, по этому это и не бык и не корова.”
app:layout_constraintEnd_toEndOf=”parent”
app:layout_constraintStart_toStartOf=”parent”
app:layout_constraintTop_toTopOf=”parent” />
</ScrollView>
Теперь нужно только добавить в обработчик нажатия соответствующей кнопки нужный код, и всё готово для отображения вашей подсказки:
Перезапустите приложение и кликните по кнопке со знаком вопроса (кнопке отвечающей за вывод подсказки):
Задание по уроку:
1. Сделайте заготовку для нового Активити, который будет запускаться при старте программы. Назовите его PreActivity. Добавьте на него текст с названием игры, картинкой игры и какой-нибудь информацией о вас как о разработчике. Добавьте на него кнопку “Продолжить”, по нажатию которой будет осуществлён переход в саму игру (в Активити MainActivity).
2. Самостоятельно погуглите назначение диалоговых окон в Андроид, чтобы иметь преставление как они выглядят и для чего нужны (более подробно будем рассматривать на следующем уроке).
Если всё получилось как нужно – молодцы! Если нет, давайте разберёмся что не так.
Урок 7 Урок 9
El Vinto, 2023 (Copyright)
Java Android. Урок 9 (“Диалог”)
Продолжаем разработку нашего первого приложения на Андроид. На этом уроке займемся таким необходимым функционалом, как отображение диалоговых окон. Возможно вы видели уже в приложениях, когда некое окно появляется поверх существующего, при этом основное окно становится полупрозрачным, выглядит это как-то так:
Простые диалоговые окна обычно используются чтобы задать пользователю какой-то вопрос и предложить несколько вариантов ответа, тип “Да”, “Нет” и “Отмена”. По этому они и называются диалоговые, т.к. компьютер с пользователем вступает в диалог и ему для продолжения работы нужны со стороны пользователя какие-то действия и решения. В данном случае диалоговое окно будет сообщать пользователю, что игра закончена, ему удалось угадать число и можно начать новую игру. Однако, мы не будем ограничиваться стандартным механизмом диалоговых окон, а сделаем свое собственное. Такие элементы называются кастомизированными (от анг.слова Custom – собственный, пользовательский, настраиваемый), т.е. мы будем делать не просто вариант “Да-Нет”, а со своими кнопками и внешним видом.
Добавим внутрь нашего класса MainActivity следующий код:
DialogData dialogData;
public static class DialogData {
public AlertDialog.Builder alertDialogBuilder = null;
public AlertDialog alertDialog = null;
public TextView textViewButtonNewGame;
public Context context;
public static DialogData CreateDialog(Context context) {
DialogData dialogDataYesNo = new DialogData();
dialogDataYesNo.context = context;
View promptsViewYesNo = LayoutInflater.from(context).inflate(R.layout.dialog_endgame, null);
dialogDataYesNo.alertDialogBuilder = new AlertDialog.Builder(context);
dialogDataYesNo.alertDialogBuilder.setView(promptsViewYesNo);
dialogDataYesNo.textViewButtonNewGame = promptsViewYesNo.findViewById(R.id.buttonNewGame);
dialogDataYesNo.textViewButtonNewGame.setOnClickListener((View.OnClickListener) context);
dialogDataYesNo.alertDialogBuilder
.setCancelable(false).setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialogInterface) {
((MainActivity) context).dialogButtonNewGame();
}
});
dialogDataYesNo.alertDialog = dialogDataYesNo.alertDialogBuilder.create();
return dialogDataYesNo;
}
}
В результате получится, что один класс (DialogData) расположен внутри другого класса (MainActivity). Также объявим сразу же переменную класса MainActivity, которая будет хранить экземпляр класса DialogData:
DialogData dialogData;
Теперь нужно добавить новый layout (мы это уже делали), дайте ему аналогичное имя dialog_endgame:
Содержание его будет крайне простое, нужно просто разместить заголовок со словом “УГАДАЛ!” и кнопку для начало новой игры:
В метод onCreate не забудьте добавить создание экземпляра диалогового окна и его инициализацию, кстати, заметьте, если вы захотите менять текст элементов, то делать это нужно будет примерно так, т.к. экземпляр элемента объявлен не в основном классе MainActivity, а в классе DialogData:
В данном случае я просто меняю текст кнопки, хотя мог это сделать и в описании xml. Но из кода текст при необходимости можно менять динамически.
Теперь нужно разместить код отображения диалогового окна, если игрок угадал число (Toast который там был, уже не нужен):
И теперь остался последний штрих – обработать нажатие на кнопку “Ещё играть”. Добавьте этот метод куда-нибудь внутрь класса MainActivity, я разместил его сразу за классом DialogData, хотя можно было и более аккуратно – после всех методов:
Теперь давайте рассмотрим некоторые моменты прямо начиная с этого метода. Следующий код закрывает развернутое на экране диалоговое окно:
dialogData.alertDialog.cancel();
Тут надо отметить, что диалоговое окно может вообще открываться в двух режимах – закрываемое и незакрываемое. Этот режим мы выбрали тут:
Обычно, если пользователь ткнет пальцем по экрану мимо окна, то оно закроется, это получается, когда setCancelable(true), в ином случае, хоть пользовать затыкается по экрану, окно так и будет оставаться открытым, закрыть его можно будет только программным путём через код, либо принудительно завершить приложение. Нам нужно как раз сделать именно так, потому что если пользователь закроет без вызова метода dialogButtonNewGame то не будет ничего что начало бы новую игру. Пользователю останется только созерцать на результат предыдущей. Это нехорошо, вряд ли это не начнёт бесить даже самого выдержанного))). По этому нужно стремиться делать так, чтобы пользователю всё нравилось.
Ещё один момент касаемый обработки вызова нажатия кнопки диалогового окна. Начинает он отсюда:
Помните, как мы добавляли обработчик нажатия кнопок через имплементацию? Там мы указывали ключевое слово this и компилятор кидал вызов метода onClick в наш класс. Здесь мы делаем по другому. Переменной класса нет, по этому такой класс называется анонимным, а имплементация нужна в любом случае. Деть её некуда кроме как запихнуть прямо туда. Однако, управление нам нужно передать в основной класс каким-то образом, чтобы мы могли генерировать новый код игры, очистить список и т.п. По этому, если бы мы заранее не позаботились об этом при создании класса и не передали ему переменную context с указанием this на наш класс MainActivity, то сделать это было бы невозможным. Таким же образом, класс DialogData содержит context и “знает” куда передать. Тип можно было бы использовать и не Context, а объявить, например, MainActivity context, тогда бы приводить к типу ((MainActivity) context) не пришлось бы. Однако, это дело вкуса, и я стараюсь делать более универсальный метод и использовать Context там, где это возможно.
Задание по уроку:
1. Разберитесь самостоятельно как программным путем (не по кнопке возврат) завершить работу Активити. Добавьте в диалоговое окно ещё одну кнопку “Выйти из игры”
2. Сделайте так чтобы при первом окончании игры надпись первой кнопки была “Ещё играть!”, а при следующих текст был бы “Может сыграем ещё?!”.
3. Попробуйте сделать так чтобы кнопка “Выйти из игры” случайным образом выводила один из вариантов текста: “Выйти из игры”, “Покинуть игру”, “Хватит, больше не хочу играть”, “Сыграю ещё потом”
Если всё получилось как нужно – молодцы! Если нет, давайте разберёмся что не так.
Урок 8 Урок 10
El Vinto, 2023 (Copyright)