Прямые ссылки на публичные уроки для быстрого старта и стабильной индексации lesson-страниц.
Изучив способы обращения к элементам экрана, переходим к взаимодействию с ними. Задача — реагировать на нажатия кнопок и изменять контент на экране в зависимости от действия. Код в этом уроке не доведён до production-ready уровня: реализуем базовую логику, чтобы она заработала. Правильная архитектура с применением стейтов и ViewModel будет рассмотрена в отдельных уроках.
Приводим верстку в исходное положение: скрываем атрибутом visibility инфо-блок и возвращаем кнопку пропуска.
Из описания ТЗ выделяем 3 состояния контейнера:
Логика по состояниям:
При клике на правильное слово:
При клике на неправильное слово:
По кнопке «Продолжить»: сбрасываем состояние до исходного.
В Android все действия, связанные с пользовательским вводом, обрабатываются через обработчики событий. Когда пользователь нажимает на элемент, система генерирует событие, и обработчик позволяет задать произвольное действие в ответ.
Обработчик событий можно привязать к любому view. Для контейнера варианта ответа задаём id: layoutAnswer3, а для внутренних элементов — tvVariantNumber3 и tvVariantValue3.
Для отслеживания клика используется метод setOnClickListener() с лямбдой. Параметр it с типом View — это объект, к которому мы обращаемся (контейнер слова). Всё, что находится внутри фигурных скобок, выполняется при клике:
binding.layoutAnswer3.setOnClickListener { it.isVisible = false }
isVisible = false — альтернатива атрибуту visibility = gone: view полностью пропадает с макета и не занимает места.
Для каждого состояния создаём отдельный метод. Метод markAnswerCorrect() вызывается при клике на правильный вариант:
private fun markAnswerCorrect() { binding.layoutAnswer3.background = ContextCompat.getDrawable( this@MainActivity, R.drawable.shape_rounded_containers_correct, ) binding.tvVariantNumber3.background = ContextCompat.getDrawable( this@MainActivity, R.drawable.shape_rounded_variants_correct, ) binding.tvVariantNumber3.setTextColor( ContextCompat.getColor(this@MainActivity, R.color.white) ) binding.tvVariantValue3.setTextColor( ContextCompat.getColor(this@MainActivity, R.color.correctAnswerColor) ) binding.btnSkip.isVisible = false binding.layoutResult.setBackgroundColor( ContextCompat.getColor(this@MainActivity, R.color.correctAnswerColor) ) binding.ivResultIcon.setImageDrawable( ContextCompat.getDrawable(this@MainActivity, R.drawable.ic_correct) ) binding.tvResultMessage.text = resources.getString(R.string.title_correct) binding.btnContinue.setTextColor( ContextCompat.getColor(this@MainActivity, R.color.correctAnswerColor) ) binding.layoutResult.isVisible = true }
Важно:
isVisibleдля контейнера результата устанавливается последним — это предотвращает мигание при отрисовке элементов, особенно если данные приходят из сети или базы данных.
Обратите внимание на два способа задания Drawable:
// Property access syntax (для View) binding.layoutAnswer3.background = ContextCompat.getDrawable(...) // Прямой вызов метода (для ImageView) binding.ivResultIcon.setImageDrawable(ContextCompat.getDrawable(...))
В первом случае используется синтаксис доступа к свойствам (property access syntax). background — замена вызова сеттера setBackground(). При необходимости можно прописать его явно:
binding.layoutAnswer3.setBackground( ContextCompat.getDrawable( this@MainActivity, R.drawable.shape_rounded_containers_correct, ) )
Среда разработки рекомендует упрощённый синтаксис через = вместо явного вызова метода.
Аналогично работает resources.getString(): resources — это property access для метода getResources(). Вызов getString(id) возвращает строку нужной локали по идентификатору.
Алгоритм аналогичный:
setOnClickListener() и создаём метод markAnswerWrong()Цвет инфо-блока при ошибке отличается от цвета контейнера варианта ответа — не забудьте добавить его в
colors.xml.
private fun markAnswerWrong() { binding.layoutAnswer1.background = ContextCompat.getDrawable( this@MainActivity, R.drawable.shape_rounded_containers_wrong, ) binding.tvVariantNumber1.background = ContextCompat.getDrawable( this@MainActivity, R.drawable.shape_rounded_variants_wrong, ) binding.tvVariantNumber1.setTextColor( ContextCompat.getColor(this@MainActivity, R.color.white) ) binding.tvVariantValue1.setTextColor( ContextCompat.getColor(this@MainActivity, R.color.wrongAnswerColorVariant) ) binding.btnSkip.isVisible = false binding.layoutAnswerInfo.setBackgroundColor( ContextCompat.getColor(this@MainActivity, R.color.wrongAnswerColor) ) binding.ivResultIcon.setImageDrawable( ContextCompat.getDrawable(this@MainActivity, R.drawable.ic_wrong) ) binding.tvResultMessage.text = resources.getString(R.string.title_wrong) binding.btnContinue.setTextColor( ContextCompat.getColor(this@MainActivity, R.color.wrongAnswerColor) ) binding.layoutAnswerInfo.isVisible = true }
Метод markAnswerNeutral() вызывается по кнопке Continue. Внутри нужно вернуть элементы 1 и 3 в исходную палитру, скрыть инфо-блок и показать кнопку Skip.
Реализация использует несколько удобных конструкций Kotlin:
with(binding) — убирает повторения binding. внутри блокаfor со списком listOf() — применяет одно действие к нескольким view сразуapply {} — extension-функция, которая вызывает несколько методов на одном объекте без повторного обращения к немуfor (layout in listOf(layoutAnswer1, layoutAnswer3)) { layout.background = ContextCompat.getDrawable( this@MainActivity, R.drawable.shape_rounded_containers, ) }
for (textView in listOf(tvVariantValue1, tvVariantValue3)) { textView.setTextColor( ContextCompat.getColor(this@MainActivity, R.color.textVariantsColor) ) }
Для TextView с цифрой нужны две модификации на одном view — применяем apply {}:
for (textView in listOf(tvVariantNumber1, tvVariantNumber3)) { textView.apply { background = ContextCompat.getDrawable( this@MainActivity, R.drawable.shape_rounded_variants, ) setTextColor( ContextCompat.getColor(this@MainActivity, R.color.textVariantsColor) ) } }
В конце метода скрываем инфо-блок и возвращаем кнопку Skip.