Заявление об ограничении ответственности: я сотрудник Google, но взгляды, выраженные в этой статье, не совпадают с мнениями моего работодателя.

Kotlin и Android: эксперимент с латунными гвоздями, часть 4

Добро пожаловать в четвертую часть этого цикла! Если вы пропустили часть 2 и часть 3, подумайте о том, чтобы вернуться к ним на минуту, чтобы увидеть, где мы находимся в этом путешествии, чтобы узнать, что язык Kotlin® может уникально предложить для разработки под Android.

На данный момент у нас есть пара функций, которые мы можем использовать, чтобы лаконично выразить создание всей иерархии представлений Android в коде Kotlin вместо обычных ресурсов XML. Шаблон Kotlin типобезопасный конструктор действительно великолепен! Однако на практике с этой схемой все еще есть шероховатости. В частности, разработчики Android привыкли иметь некоторые специальные выражения в XML для определенных концепций Android, таких как размеры представлений, измеренные в пикселях, не зависящих от плотности. Это очень просто в XML и очень утомительно в коде!

Вот пример утомительного способа установить для свойства maxWidth TextView значение 120dp с помощью нашей новой функции v:

Сравните это с макетами XML, где вы просто скажете:

Ба! Мы просто потеряли все удобство, которое пытались получить наши v-функции!

Нам нужен сокращенный способ преобразования dp в px.

Мне нужна функция, обеспечивающая сокращенный синтаксис для указания измерений пикселей в других единицах измерения. Вот что я хочу сказать вместо приведенного выше беспорядка:

Я предлагаю функцию, которая принимает значение, измеренное в dp, и возвращает значение, преобразованное в px для текущего устройства. Зачем вызывать функцию «dp_i», а не только «dp»? Иногда Android хочет измерять пиксели как число с плавающей запятой, а иногда и как целое число. Я не хочу вручную приводить возвращаемое значение (все еще слишком много символов), поэтому я просто сделаю по одной функции для каждого типа, «dp_i» и «dp_f».

Но здесь есть морщинка. Если вы посмотрите на полный код, который вычисляет значение dp, он требует для работы контекста. Я не хочу передавать контекст dp_i в качестве еще одного аргумента каждый раз, когда я его вызываю. Поэтому я собираюсь использовать функцию Kotlin под названием функции расширения, чтобы добиться желаемой краткости.

Давайте сразу перейдем к коду. Вот как выглядят dp_i и dp_f, написанные как функции расширения:

Как работает функция расширения?

Первое, на что следует обратить внимание в приведенном выше коде, - это очевидное имя функций. Вы, возможно, ожидали увидеть только «dp_f» для первой функции, но вместо этого у нас есть «View.dp_f». Это специальный синтаксис Kotlin для функций расширения. Между именем класса (здесь android.view.View) и именем определяемой функции стоит точка. Здесь мы сказали Kotlin расширить класс View двумя новыми методами, названными «dp_f» и «dp_i». Вот несколько вещей, которые вы получите с такими функциями расширения.

Во-первых, код в теле функций расширения может действовать как член View, получая доступ к его элементам и методам (но только с «общедоступной» и «внутренней» видимостью). Это означает, что dp_f может использовать ссылка на контекст представления, представленная синтетическим свойством, называемым «контекст». Теперь нам не нужно передавать Context в качестве параметра, поскольку он неявно передается с View.

Во-вторых, другой код, который импортирует эти функции расширения, может вызывать их, как если бы они были обычными методами-членами для экземпляров объектов View. Это означает, что лямбда нашей функции v с аргументом-приемником типа View может вызывать эти методы аналогично обычным функциям, неявно используя ссылку на объект View получателя. Таким образом, вы можете сказать «maxWidth = dp_i (120)» в лямбда-выражении, и Kotlin распознает, что вы хотите вызвать функцию dp_i для объекта-приемника типа View.

Здесь важно знать, что Kotlin на самом деле не вносит изменений в определение класса при определении функции расширения. Класс всегда будет его собственным полным модулем после того, как он был загружен программой, поэтому мы можем использовать функции расширения только для добавления кода вокруг него. Кроме того, существующие методы в View также не могут подключаться к функции расширения, потому что это не настоящий член, определенный с классом.

Результатом этого эксперимента является то, что теперь у нас есть удобные функции для преобразования dp в px в наших лямбдах v-функций!

Мы не останавливаемся на достигнутом! Как насчет другого ярлыка с использованием функций расширения?

Мы видели, что с функциями расширения можно делать сложные вещи, чтобы сделать некоторые функции более удобными в использовании. Давайте продолжим эту мысль, чтобы усилить наши v-функции.

В настоящее время у нас есть эти две сигнатуры функций, первая для создания корневого представления с использованием контекста, а вторая для создания вложенных дочерних представлений в родительском представлении:

Было бы неплохо, если бы нам не нужно было передавать Context или ViewGroup в качестве первого параметра. С помощью функций расширения мы достигаем этого так же, как и выше, избегая передачи контекста в dp_f. Вот повторная реализация обеих функций как функций расширения с закомментированными строками, показывающими исходный код для v над недавно измененными строками:

Вы можете видеть, что мы удаляем первый аргумент каждой функции (Context и ViewGroup) и вместо этого ожидаем, что эти ссылки будут исходить из экземпляра класса, который они расширяют. У функций теперь есть только один аргумент - лямбда с получателем, который изменяет созданный View.

С помощью этих модифицированных функций, если мы кодируем внутри Activity (который является подклассом Context), мы обращаемся к v как к члену объекта Activity. Мы можем воспользоваться этим, чтобы создавать вложенные представления еще проще:

Вызов v даже не выглядит как вызов функции, потому что нам больше не нужны круглые скобки. Если вы помните из части 2 этой серии, если последний аргумент функции - лямбда, вы можете поместить его после скобок. И в этом случае, когда аргумент только один, скобки вообще не нужны!

Функции расширения Kotlin только что помогли нам выразить иерархию представлений Android в очень удобочитаемом и кратком виде в коде. Однако есть еще несколько проблемных мест, на которые следует обратить внимание. Например, давайте возьмем этот код, который назначает 16dp левого отступа для TextView:

Довольно некрасиво смешивать вызов метода setPadding () с синтетическими средствами доступа к свойствам для layoutParams и текста. setPadding () вызывает здесь проблемы, потому что это не сеттер в стиле JavaBeans - он имеет более одного аргумента. Следовательно, Kotlin не может присвоить ему синтетическое свойство. Но не бойтесь! Это можно исправить с помощью умного использования другой функции языка Kotlin, как мы узнаем в предстоящей части 5.

Если вы хотите оставаться на вершине этой серии, вы можете подписаться на меня как здесь, на Medium, так и в Твиттере как CodingDoug, и получать мгновенные уведомления о будущих частях!