Освой Android играючи
/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000
После простой программы, где мы здоровались с котом, пора написать более сложный пример, где мы будем будить кота, кормить, поить и гладить. Тогда наш котик будет ухоженный и довольный.
Наша цель - научиться добавлять щелчок к компоненту (не к кнопке), перемещаться по различным макетам экрана, а также обрабатывать щелчки определённым образом.
Для начала нам нужно подготовить четыре картинки. Это могут быть векторные изображения или обычные растровые. Затем их добавляем в папку drawable. Этот процесс вам уже знаком по предыдущим урокам.
Далее откройте файл res/values/strings.xml и добавьте строковые ресурсы. Вам нужно придумать текст к надписям, которые будут находиться под картинками.
<string name="cat_sleep">Разбудите кота (осторожно!)</string>
<string name="cat_feed">Накормите кота (не жадничайте)</string>
<string name="cat_drink">Напоите кота (чистой водой)</string>
<string name="cat_pet">Погладьте кота (не против шерсти)</string>
<string name="cat_description">Это кот</string>
Также желательно добавить строковые ресурсы, которые будут описывать картинку. Для экономии места я создал только один строковый ресурс cat_description, который будет соотноситься со всеми изображениями.
Начинаем строить экран приложения. Идём от простого к сложному. Нам нужно разместить на экране картинку и текст.
@Composable
fun CatApp(modifier: Modifier = Modifier) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxSize()
) {
Text(text = stringResource(R.string.cat_sleep))
Spacer(modifier = Modifier.height(32.dp))
Image(
painter = painterResource(R.drawable.cat_sleep),
contentDescription = stringResource(R.string.cat_description),
modifier = Modifier
.wrapContentSize()
.clickable {
// позже добавим код
}
)
}
}
Обратите внимание, что у изображения есть модификатор clickable, способный видеть щелчки пользователя. Пока он не задействован.
Чтобы увидеть результат, подключим созданный экран к активности.
class MainActivity : ComponentActivity() {
@OptIn(ExperimentalMaterial3Api::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
Cat_composeTheme {
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
CenterAlignedTopAppBar(
title = { Text(text = "Жизнь кота от заката до рассвета") },
colors = TopAppBarDefaults.largeTopAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer
)
)
}
) { innerPadding ->
CatApp(
modifier = Modifier.padding(innerPadding)
)
}
}
}
}
}
Далее нам нужно перейти на другой макет. В принципе, мы могли бы менять картинку и текст в тех же Image и Text. Но в более сложных проектах может быть проще использовать отдельные макеты для каждого вида экрана. Поэтому мы создадим похожий макет с текстом и изображением.
Чтобы переходить с одного экрана на другой, нам нужно отслеживать, а где собственно мы находимся. Создадим отдельную переменную-счётчик currentStep и инициализируем её со значением 1.
var currentStep by remember { mutableIntStateOf(1) }
Когда мы щёлкнем по картинке спящего кота на первом экране, то значение счётчика изменим на 2, чтобы показать, чт мы перешли на второй экран. И также можно проделать и с другими экранами. Но не будем забегать вперёд и поработаем пока только с двумя экранами. Для перехода нам понадобится условие when.
@Composable
fun CatApp(modifier: Modifier = Modifier) {
var currentStep by remember { mutableIntStateOf(1) }
when (currentStep) {
1 -> Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxSize()
) {
Text(text = stringResource(R.string.cat_sleep))
Spacer(modifier = Modifier.height(32.dp))
Image(
painter = painterResource(R.drawable.cat_sleep),
contentDescription = stringResource(R.string.cat_description_1),
modifier = Modifier
.wrapContentSize()
.clickable {
currentStep = 2
}
)
}
2 -> Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxSize()
) {
Text(text = stringResource(R.string.cat_feed))
Spacer(modifier = Modifier.height(32.dp))
Image(
painter = painterResource(R.drawable.cat_feed),
contentDescription = stringResource(R.string.cat_description_1),
modifier = Modifier
.wrapContentSize()
.clickable {
currentStep = 3
}
)
}
}
}
Запустите пример и нажмите на изображение. Вы должны перейти на второй экран. Общую идею, надеюсь, вы уловили.
Можете самостоятельно добавить новые ветки условий, чтобы перейти на третий и четвёртый экран. Но можно заметить, что код в ветках повторяется. Меняется только надпись и картинка, а сама структура остаётся прежней. Это служит сигналом, что нужно выделить повторяющий код в отдельную функцию с параметрами. Давайте так и поступим и создадим функцию CatTextAndImage().
@Composable
fun CatTextAndImage(
textLabelResourceId: Int,
drawableResourceId: Int,
contentDescriptionResourceId: Int,
onImageClick: () -> Unit,
modifier: Modifier = Modifier
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = modifier.fillMaxSize()
) {
Text(
text = stringResource(textLabelResourceId),
style = MaterialTheme.typography.displaySmall
)
Spacer(modifier = modifier.height(32.dp))
Image(
painter = painterResource(drawableResourceId),
contentDescription = stringResource(contentDescriptionResourceId),
modifier = modifier
.wrapContentSize()
.clickable(
onClick = onImageClick
)
)
}
}
Большинство входных параметров понятно. Это числовые значения строковых ресурсов. Чуть подробнее остановимся на параметре onImageClick: () -> Unit.
Мы можем передать в параметрах лямбду-функцию. Для этого соблюдайте следующую нотацию - в нашем примере указано имя функции onImageClick с указанием типа () -> Unit. Это означает, что функция не имеет входных параметров (пустые круглые скобки до стрелки) и не возвращает значений (Unit после стрелки). Теперь любая функция, которая соответствует типу () -> Unit может быть использована в тех местах, где это предусмотрено. Например, у кнопки есть параметр onClick с таким типом, а значит мы может вызвать в этом месте onImageClick.
В нашем примере у модификатора clickable есть параметр onClick с нужным нам вариантом. Поэтому мы можем там использовать onClick = onImageClick.
Заменим повторяющий код в примере и допишем остальные экраны.
@Composable
fun CatApp(modifier: Modifier = Modifier) {
var currentStep by remember { mutableIntStateOf(1) }
when (currentStep) {
1 -> CatTextAndImage(
textLabelResourceId = R.string.cat_sleep,
drawableResourceId = R.drawable.cat_sleep,
contentDescriptionResourceId = R.string.cat_description,
onImageClick = {
currentStep = 2
}
)
2 -> CatTextAndImage(
textLabelResourceId = R.string.cat_feed,
drawableResourceId = R.drawable.cat_feed,
contentDescriptionResourceId = R.string.cat_description,
onImageClick = {
currentStep = 3
}
)
3 -> CatTextAndImage(
textLabelResourceId = R.string.cat_drink,
drawableResourceId = R.drawable.cat_drink,
contentDescriptionResourceId = R.string.cat_description,
onImageClick = {
currentStep = 4
}
)
4 -> CatTextAndImage(
textLabelResourceId = R.string.cat_pet,
drawableResourceId = R.drawable.cat_pet,
contentDescriptionResourceId = R.string.cat_description,
onImageClick = {
currentStep = 1
}
)
}
}
В целом мы получили рабочую версию приложения, где можем перемещать по щелчку по разным экранам. Но давайте немного усложним приложение. Представим, что кота очень трудно разбудить и одного щелчка по спящему коту будет маловато. Скажем, чтобы разбудить кота, нам нужно сделать от 2 до 4 щелчков. Добавим новую переменную.
var wakeupCount by remember { mutableIntStateOf(0) }
Число щелчков будет задаваться случайным образом в диапазоне от 2 до 4. Затем, находять на первом экране, мы будем уменьшать значение на единицу при каждом щелчке по картинке, как только значение станет равным 0, переходим на второй экран. Изменим код.
@Composable
fun CatApp(modifier: Modifier = Modifier) {
var currentStep by remember { mutableIntStateOf(1) }
var wakeupCount by remember { mutableIntStateOf(0) }
wakeupCount = (2..4).random()
when (currentStep) {
1 -> CatTextAndImage(
textLabelResourceId = R.string.cat_sleep,
drawableResourceId = R.drawable.cat_sleep,
contentDescriptionResourceId = R.string.cat_description_1,
onImageClick = {
wakeupCount--
if (wakeupCount == 0) {
currentStep = 2
}
}
)
// Остальное без изменений
}
}
Теперь программа полностью готова.