Освой Android играючи

Сайт Александра Климова

Шкодим

/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000

Обработка щелчков для перемещения по экранам (Compose)


После простой программы, где мы здоровались с котом, пора написать более сложный пример, где мы будем будить кота, кормить, поить и гладить. Тогда наш котик будет ухоженный и довольный.

Наша цель - научиться добавлять щелчок к компоненту (не к кнопке), перемещаться по различным макетам экрана, а также обрабатывать щелчки определённым образом.

Для начала нам нужно подготовить четыре картинки. Это могут быть векторные изображения или обычные растровые. Затем их добавляем в папку drawable. Этот процесс вам уже знаком по предыдущим урокам.

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)
                    )
                }
            }
        }
    }
}

Cat is sleeping

Далее нам нужно перейти на другой макет. В принципе, мы могли бы менять картинку и текст в тех же 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
                }
            }
        )

        // Остальное без изменений

    }
}

Теперь программа полностью готова.

Cat App

Дополнительное чтение

Реклама