Изучаем KTX для Android

Автор: admin от 13-02-2018, 12:45, посмотрело: 160

Привет, «Хабр»! Прошло почти 9 месяцев с тех пор, как на Google I/O 2017 компания Google анонсировала Kotlin в качестве официального языка разработки под Android. Кто-то использует его как основной инструмент намного дольше, учитывая, что на нём можно было писать уже с середины 2014 года. В документации Google стали появляться примеры реализации на Kotlin. За это время разработчики смогли «потрогать» и оценить все преимущества этого языка. И многие, включая меня, думали: какой же шаг будет следующим? Support Library на Kotlin? Или что-то новое? И вот, встречайте: Android KTX! А мы представляем вашему вниманию перевод статьи, посвящённой его разбору.



Изучаем KTX для Android
core-ktx и саму библиотеку здесь: android/android-ktx.



Animator functions



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



Animation listener



Для начала установим animation listener на animator:



animator.addListener { handleAnimation(it) }


Эта конструкция позволяет нам получать колбэки для событий анимации. Мы также можем использовать функции расширения для определённых колбэков от listener, нужно только реализовать функции, которые вы хотите получать:



animator.addListener(
       onEnd = {},
       onStart = {},
       onCancel = {},
       onRepeat = {}
)


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



Отдельные listeners для событий анимации



У нас появилась возможность устанавливать listener на отдельные события: например, добавить listener на событие pause можно так же, как и функцию addListener():



animator.addPauseListener { handleAnimation(it) }
// или
animator.addPauseListener(
       onPause = {},
       onResume = {}
)


Мы также можем «повесить» listener на событие анимации с помощью однострочного синтаксиса:



animator.doOnPause { handleAnimation(it) }
animator.doOnCancel { handleAnimation(it) }
animator.doOnEnd { handleAnimation(it) }
animator.doOnRepeat { handleAnimation(it) }
animator.doOnStart { handleAnimation(it) }
animator.doOnResume { handleAnimation(it) }


Если сейчас вы используете Java, то заметите, насколько меньше кода требуется для реализации и насколько легче это всё читается.



Content



Здесь мы рассмотрим функции расширения, добавленные в пакет Content. Если нам необходимо получить системный сервис, то расширение позволяет сделать так:



val alarmManager = systemService[leech=https://developer.android.com/reference/android/content/ContentValues.html]ContentValues[/leech], используя функцию contentValuesOf, передав в качестве аргументов экземпляры Pair:

[code]val contentValues = contentValuesOf(somePairs...)


Time operations



KTX также предлагает использовать методы, относящиеся к работе со временем. Посмотрим, что мы здесь имеем.

Теперь можно получить DayOfWeek, Month и Year как Int-значение простым вызовом:



DayOfWeek.FRIDAY.asInt()
Month.APRIL.asInt()
Year.now().asInt()


Класс Duration также имеет несколько доступных функций расширения:



// Получение значений
val (seconds, nanoseconds) = Duration.ofSeconds(1)
// умножение
val resultValue = Duration.ofSeconds(1) * 2
// Деление
val resultValue = Duration.ofSeconds(2) / 2
// Инверсия
val resultValue = -Duration.ofSeconds(2)


Свойства Instant, LocalData, LocalDateTime, LocalTime могут быть получены следующими функциями расширения:



// Получение значений
val (seconds, nanoseconds) = Instant.now()
// Получение значений
val (year, month, day) = LocalDate.now()
// Получение значений
val (localDate, localTime) = LocalDateTime.now()
// Получение значений
val (hour, minute, second, nanosecond) = LocalTime.now()


Так же, как в методах, перечисленных выше, доступ к свойствам классов MonthDay, OffsetDateTime и OffsetTime может быть получен через вызовы следующих методов:



// Получение значений
val (month, day) = MonthDay.now()
// Получение значений
val (localDataTime, ZoneOffset) = OffsetDateTime.now()
// Получение значений
val (localTime, ZoneOffset) = OffsetTime.now()


Если вы используете класс Period, то библиотека KTX содержит несколько функций расширения для доступа к свойствам и операциям этого класса:



// Получение значений
val (years, month, days) = Period.ofDays(2)
// Умножение
val resultValue = Period.ofDays(2) * 2
// Инверсия
val resultValue = -Period.ofDays(2)


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



// Получение значений
val (year, month) = YearMonth.now()
// Получение значений
val (localDateTime, ZoneId) = ZonedDateTime.now()


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



someInt.asDayOfWeek() // возвращает экземпляр DayOfWeek
someInt.asMonth() //  возвращает экземпляр Month
someInt.asYear() //  возвращает экземпляр Year
someInt.days() //  возвращает экземпляр Period
someInt.hours() //  возвращает экземпляр Duration
someInt.millis() //  возвращает экземпляр Duration
someInt.minutes() //  возвращает экземпляр Duration
someInt.months() //  возвращает экземпляр Period
someInt.nanos() //  возвращает экземпляр Duration
someInt.seconds() //  возвращает экземпляр Duration
someInt.years() //  возвращает экземпляр Period


Так же это работает для Long-значений:



someLong.asEpochMillis() // возвращает экземпляр Instant
someLong.asEpochSeconds() // возвращает экземпляр Instant
someLong.hours() // возвращает экземпляр Duration
someLong.millis() // возвращает экземпляр Duration
someLong.minutes() // возвращает экземпляр Duration
someLong.nanos() // возвращает экземпляр Duration
someLong.seconds() // возвращает экземпляр Duration


OS



Здесь собраны функции расширения, направленные на взаимодействие с пакетом Android OS.

Они включают в себя несколько функций расширения для работы с классом Handler:



handler.postAtTime(uptimeMillis = 200L) {
   // Какое-то действие
}
handler.postDelayed(delayInMillis = 200L) {
   // Какое-то действие
}


Создание экземпляра класса Bundle сейчас выглядит намного приятнее:



val bundle = bundleOf("some_key" to 12, "another_key" to 15)
val bundle = persistableBundleOf("some_key" to 12, "another_key" to 15)


И если вы записываете trace events для Systrace tool, запись сообщений trace будет проще и красивее:



trace("section_name") { }


Utils



В пакете Util собраны функции расширения для работы с файлами, массивами и другими основными типами данных.

Если вы работаете с AtomicFiles, то вы можете использовать следующие функции:



val fileBytes = atomicFile.readBytes()
val text = atomicFile.readText(charset = Charset.defaultCharset())
atomicFile.tryWrite {
   //  Ваша операция записи
}
atomicFile.writeBytes(byteArrayOf())
atomicFile.writeText("some string", charset = Charset.defaultCharset())


Для LongSparseArray, SparseArray, SparseBooleanArray, SparseIntArray, SparseLongArray типов нам стали доступны:



array.contains(someKey)
array.containsKey(someKey)
array.containsValue(someValue)
array.forEach { key, value  doSomething(key, value) }
array.getOrDefault(key = keyValue, defaultValue = defaultValue)
array.getOrElse(key = keyValue, defaultValue = defaultValue)
array.isEmpty()
array.isNotEmpty()
val keyIterator = array.keyIterator()
val valueIterator = array.valueIterator()
array.plus(anotherArray)
array.putAll(anotherArray)
array.remove(key = keyValue, value = value)
array.set(key = keyValue, value = value)
array.size


Работа с классом Pair стала немного легче:



val pair = android.util.Pair("dsfn", "sdihfg")
// Получение значений
val (key, value) = pair
// Конвертирование Android Pair в Kotlin Pair
val kotlinPair = pair.toKotlinPair()


Мы также можем конвертировать Kotlin Pair в Android Pair:



val pair = Pair("dsfn", "sdihfg")
val androidPair = pair.toAndroidPair()


Если вы работаете с классом Half, то благодаря KTX стало проще конвертировать в него данные других типов:



short.toHalf()
string.toHalf()
float.toHalf()
double.toHalf()


Используя функции расширения, теперь можно преобразовать экземпляр класса ClosedRange в Range:



val range = closedRange.toRange()


Над экземпляром класса Range появилась возможность выполнять следующие действия:



val range = closedRange.toClosedRange()
// Возвращает пересечение двух диапазонов
val resultValue = closedRange and someOtherRange
// Возвращает наименьший диапазон, включающий два диапазона
val resultValue = closedRange += someOtherCloseRange
// Возвращает пересечение диапазона и заданного значения
val resultValue = closedRange += someValue


Оба класса Size и SizeF могут использовать функции расширения:



val size = Size(5, 5)
// Получение значений
val (width, height) = size


Database Cursor



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


  • первая функция возвращает тип non-null, используя заданное имя столбца;

  • вторая функция возвращает тип данных (или null), используя заданное имя столбца;

  • третья функция возвращает тип данных (или null), используя заданный индекс.



cursor.getBlob(columnName = "some_column")
cursor.getBlobOrNull(columnName = "some_column")
cursor.getBlobOrNull(index = 0)

cursor.getDouble(columnName = "some_column")
cursor.getDoubleOrNull(columnName = "some_column")
cursor.getDoubleOrNull(index = 0)

cursor.getFloat(columnName = "some_column")
cursor.getFloatOrNull(columnName = "some_column")
cursor.getFloatOrNull(index = 0)

cursor.getInt(columnName = "some_column")
cursor.getIntOrNull(columnName = "some_column")
cursor.getIntOrNull(index = 0)

cursor.getLong(columnName = "some_column")
cursor.getLongOrNull(columnName = "some_column")
cursor.getLongOrNull(index = 0)

cursor.getShort(columnName = "some_column")
cursor.getShortOrNull(columnName = "some_column")
cursor.getShortOrNull(index = 0)

cursor.getString(columnName = "some_column")
cursor.getStringOrNull(columnName = "some_column")
cursor.getStringOrNull(index = 0)


SQLite



В данный момент для SQLite есть всего одна функция, но зато очень полезная. Она позволяет нам производить транзакции, используя заданные SQL-операторы.



sqLiteDatabase.transaction { "some SQL statement" }


Resources



Что касается ресурсов, то пока добавлены только те функции расширения, которые упрощают работу с классом TypedArray.



val boolean = typedArray.getBooleanOrThrow(0)
val int = typedArray.getColorOrThrow(0)
val colorStateList = typedArray.getColorStateListOrThrow(0)
val float = typedArray.getDimensionOrThrow(0)
val int = typedArray.getDimensionPixelOffsetOrThrow(0)
val int = typedArray.getDimensionPixelSizeOrThrow(0)
val drawable = typedArray.getDrawableOrThrow(0)
val float = typedArray.getFloatOrThrow(0)
val typeface = typedArray.getFontOrThrow(0)
val int = typedArray.getIntOrThrow(0)
val int = typedArray.getIntegerOrThrow(0)
val string = typedArray.getStringOrThrow(0)
val charSequenceArray = typedArray.getTextArrayOrThrow(0)
val charSequence = typedArray.getTextOrThrow(0)


Примечание: Все throw пробрасывают IllegalArgumentException, если указанный индекс не существует.



Text



Большинство приложений, над которыми мы (разработчики) работаем, используют текст в разным местах этих самых приложений. К счастью, в KTX есть несколько функций для работы с ним, в частности для класса SpannableStringBuilder.

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



val builder = SpannableStringBuilder(urlString)       
   .bold { append("hi there") }
// или bold / italic / underlined текст, если хотите.
val builder = SpannableStringBuilder(urlString)       
   .bold { italic { underline { append("hi there") } } } 


Также есть build-функции, которые могут установить цвет фона или добавить отступы в текст:



.backgroundColor(color = R.color.black) {
   // Действие в builder
}
.inSpans(spans = someSpans) {
   // Действие в builder
}


И последняя функция — buildSpannedString, которая позволяет составить строку, используя вышеперечисленные функции расширения:



textView.text = buildSpannedString { bold { append("hitherejoe") } }


Net



В пакете .net у нас есть одна функция, позволяющая с лёгкостью конвертировать строку в URI. То, что нужно!



val uri = urlString.toUri()


Graphics



Пакет Graphics в KTX вышел довольно массивным, зато он даёт нам возможность легко реализовывать все визуальные тонкости приложения.

В первую очередь хотелось бы отметить функции, конвертирующие Bitmap (и не только) в следующие типы:



val adaptiveIcon = bitmap.toAdaptiveIcon()
val drawable = bitmap.toDrawable(resources)
val icon = bitmap.toIcon()
val drawable = someInt.toDrawable()
val icon = someByteArray.toIcon()
val icon = someUri.toIcon()
val colorDrawable = someColor.toDrawable()
val bitmap = drawable.toBitmap(width = someWidth, height = someHeight, config = bitMapConfig)


Далее рассмотрим ключевые операции для работы с Bitmap:



val bitmap = someBitmap.applyCanvas(block = { })
val colorInt = someBitmap.get(x, y)
val bitmap = someBitmap.scale(width, height, filter = true)
someBitmap.set(x, y, color)


И работа с Canvas стала намного легче:



canvas.withRotation(degrees, pivotX, pivotY) { // Обработка }
canvas.withSave { // Обработка }
canvas.withScale(x, y, pivotX, pivotY)  { // Обработка }
canvas.withSkew(x, y)  { // Обработка }
canvas.withTranslation(x, y) { // Обработка }


Также появилось несколько нововведений для Color:



// Получение значений
val (r, g, b, a) = color
// Смешивание цветов
val color += someColor


Функция plus() действительно крута и позволяет нам смешивать два цвета и получить в результате смешанный Color!

Кроме этого, стало проще работать с матрицами. Теперь можно перемножить две матрицы и в результате получить один объект Matrix:



// Умножение
val resultMatrix = matrix * someOtherMatrix
val values = matrix.values()


А ещё мы можем работать с Picture через функцию record, используя блок параметров, чтобы выполнить соответствующие действия:



val resultField = picture.record(width = someWidth, height = someHeight) {
   // Какие-то действия над Canvas
}


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



drawable.updateBounds(left = 16, top = 16, right = 16, bottom = 16)


Нужно произвести трансформацию на Shader? Без проблем!



shader.transform { // Трансформация }


Появилось несколько функций расширения для работы с классом PorterDuff:



val porterDuffColorFilter = mode.toColorFilter(someColor)
val porterDuffXfermode = mode.toXfermode()


Работая с классом Region, теперь мы можем использовать эти функции:



// Возвращает объединение someRegion с Rect
val region = someRegion and someRect
// Возвращает объединение someRegion с Region
val region = someRegion and someRegion
// Возвращает разницу someRegion и Rect
val region = someRegion - someRect
// Возвращает разницу someRegion и другим Region
val region = someRegion - someRegion
// Возвращает пересечение someRegion и Rect
val region = someRegion or someRect
// Возвращает пересечение someRegion и другим Region
val region = someRegion or someRegion
// Возвращает объединение someRegion с Rect
val region = someRegion + someRect
// Возвращает объединение someRegion с Region
val region = someRegion + someRegion
// Возвращает объединение без пересечения someRegion и Rect
val region = someRegion xor someRect
// Возвращает объединение без пересечения someRegion и другим Region
val region = someRegion xor someRegion
val boolean = someRegion.contains(somePoint)
someRegion.forEach { doSomethingWithEachRect(it) }
val iterator = someRegion.iterator()
// Возвращает инвертированный someRegion как новый Region
val region = -someRegion


Классу PointF также добавили некоторые функции для упрощения:



val (x, y) = somePointF
val pointF = somePointF - someOtherPointF
val pointF = somePointF - someFloat
val pointF = somePointF + somePointF
val pointF = somePointF + someFloat
val point = somePointF.toPoint()
val pointF = -somePointF


Тоже самое доступно для класса Point:



val (x, y) = somePoint
val point = somePoint - somePoint
val point = somePoint - someFloat
val point = somePoint +somePoint
val point = somePoint + someFloat
val point = somePoint.toPointF()
val point = -somePoint


И для класса Rect тоже:



val rect = someRect and anotherRect
val (left, top, right, bottom) = someRect
someRect.contains(somePoint)
val region = someRect - anotherRect
val rect = someRect - someInt
val rect = someRect - somePoint
val rect = someRect or someRect
val rect = someRect + someRect
val rect = someRect + someInt
val rect = someRect + somePoint
val rectF = someRect.toRectF()
val region = someRect.toRegion()
val region = someRect xor someRect


Вы не удивитесь, но для RectF они также доступны:



val rectF = someRectF and anotherRectF
val (left, top, right, bottom) = somerectF
someRectF.contains(somePoint)
val region = someRectF - anotherRectF
val rectF = someRectF - someInt
val rectF = someRectF - somePoint
val rectF = someRectF or someRect
val rectF = someRectF + someRect
val rectF = someRectF + someInt
val rectF = someRectF + somePoint
val rect = someRectF.toRect()
val region = someRectF.toRegion()
val reactF = someRectF.transform(someMatrix)
val region = someRectF xor someRect


При работе с классом Path мы можем использовать следующие варианты:



val path = somePath and anotherPath
val path = somePath.flatten(error = 0.5f)
val path = somePath - anotherPath
val path = somePath or anotherPath
val path = somePath + anotherPath
val path = somePath xor anotherPath


Велика вероятность, что при работе с графикой мы будем работать с типами данных Int и Long. Тип Int предлагает нам следующие функции в KTX:



val alpha = int.alpha
val blue = int.blue
val green = int.green
val red = int.red
val luminance = int.luminance
val (alphaComp, redComp, greenComp, blueComp) = someInt
val color = someInt.toColor()
val color = someInt.toColorLong()


С другой стороны, тип Long содержит немного больше функций:



val alpha = long.alpha
val blue = long.blue
val green = long.green
val red = long.red
val luminance = long.luminance
val (alphaComp, redComp, greenComp, blueComp) = someLong
val color = someLong.toColor()
val color = someLong.toColorInt()
long.isSrgb
long.isWideGamut
long.colorSpace


Transitions



Итак, дойдя до класса Transition, мы видим, что здесь можно использовать функции расширения, аналогичные animation listeners:



transition.addListener { doSomethingWithTransition(it) }
transition.addListener(
       onEnd = {},
       onStart = {},
       onCancel = {},
       onResume = {},
       onPause = {}
)


Но есть небольшое отличие в синтаксисе метода для отдельных колбэков:



transition.doOnCancel {  }
transition.doOnEnd {  }
transition.doOnPause {  }
transition.doOnResume {  }
transition.doOnStart {  }


Views



Аналогичные функции были добавлены также и для класса View. Установка колбэков предельно понятна:



view.doOnLayout {  }
view.doOnNextLayout {  }
view.doOnPreDraw {  }


Метод postDelayed теперь доступен в качестве функции:



view.postDelayed(delayInMillis = 200) { // Какое-то действие }


То же самое и с методом postOnAnimationDelayed:



view.postOnAnimationDelayed(delayInMillis = 200) { // Какое-то действие }


Обновление паддингов для View теперь намного легче и понятнее, для этого нам были предоставлены несколько функций:



view.setPadding(16)
view.updatePadding(left = 16, right = 16, top = 16, bottom = 16)
view.updatePaddingRelative(
       start = 16, end = 16, top = 16, bottom = 16)


Если вам нужно конвертировать View в Bitmap, то теперь это можно сделать одной строкой кода!



val bitmap = view.toBitmap(config = bitmapConfig)


ViewGroup



Несколько достаточно крутых функций расширения были добавлены и для ViewGroup. Думаю, вам понравится! Например, проверка, содержит ли ViewGroup конкретную View:



val doesContain = viewGroup.contains(view)


Цикл по child ViewGroup (где it представляет собой child):



viewGroup.forEach { doSomethingWithChild(it) }
viewGroup.forEachIndexed { index, view 
   doSomethingWithChild(index, view) }


Доступ к child конкретной позиции в стиле Kotlin:



val view = viewGroup[0]


Получение экземпляра MutableIterator:



val viewGroupIterator = viewGroup.iterator()


И несколько других операций с ViewGroup:



viewGroup.isEmpty()
viewGroup.isNotEmpty()
viewGroup.size
// Удаление view из данной viewgroup
viewGroup -= view
// Добавление view в данную viewgroup
viewGroup += view


Margins



Так же, как и паддинги для View, мы можем добавлять margins для LayoutParams с помощью следующих функций:



params.setMargins(16)
params.updateMargins(left = 16, right = 16, top = 16, bottom = 16)
params.updateMarginsRelative(
       start = 16, end = 16, top = 16, bottom = 16)


Заключение



Как мы видим, KTX предлагает нам мощные инструменты для использования Kotlin в разработке Android-приложений. Я очень рад возможности использовать их в своих проектах и с нетерпением жду, что же будет ещё добавлено в ближайшее время.

Источник: Хабрахабр

Категория: Game Development, Google, Android

Уважаемый посетитель, Вы зашли на сайт как незарегистрированный пользователь.
Мы рекомендуем Вам зарегистрироваться либо войти на сайт под своим именем.

Добавление комментария

Имя:*
E-Mail:
Комментарий:
Полужирный Наклонный текст Подчеркнутый текст Зачеркнутый текст | Выравнивание по левому краю По центру Выравнивание по правому краю | Вставка смайликов Выбор цвета | Скрытый текст Вставка цитаты Преобразовать выбранный текст из транслитерации в кириллицу Вставка спойлера
Введите два слова, показанных на изображении: *