Освой Android играючи
/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000
Загружаем изображение из источника
Интерфейс ImageDecoder.OnHeaderDecodedListener
AnimatedImageDrawable
Обработка ошибок
Класс ImageDecoder был представлен в Android 9.0 Pie (API 28). В некоторых случаях теперь можно отказываться от популярных библиотек для работы с изображениями, такими как Glide или Picasso.
API нового класса содержит около 20 методов и позволяют выполнить многие полезные вещи. В частности, теперь поддерживаются анимированные GIF и WEBP.
Мы можем создать изображение (drawable или bitmap) из различных источников (файлы, набор байтов, ресурсы, URI или активы).
Вспомним старый способ.
// drawable from file
Drawable.createFromPath(pathName)
// drawable from asset (or stream)
Drawable.createFromStream(context.assets.open(assetFileName), "")
// bitmap from file
BitmapFactory.decodeFile(pathName)
// bitmap from asset (or stream)
BitmapFactory.decodeStream(context.assets.open(assetFileName))
// bitmap from byte array
BitmapFactory.decodeByteArray(data, offset, length, opts)
// display drawable
imageView.setImageDrawable(drawable)
// display bitmap
imageView.setImageBitmap(bitmap)
Теперь посмотрим на новый способ.
Сначала создаётся объект из источника.
// Из файла
ImageDecoder.createSource(file)
// из набора байт
ImageDecoder.createSource(byteBuffer)
// из ресурсов
ImageDecoder.createSource(resources, resId)
// из URI
ImageDecoder.createSource(contentResolver, uri)
// из активов
ImageDecoder.createSource(assetManager, assetFileName)
Затем создаём нужный тип изображения.
// создаём bitmap
val bitmap = ImageDecoder.decodeBitmap(source)
// создаём drawable
val drawable = ImageDecoder.decodeDrawable(source)
Загрузим изображение из активов. Создадим папку assets и добавим в неё файл с котиком. Выводим изображение в ImageView при нажатии кнопки.
button.setOnClickListener{
val assets = this.assets
val assetFileName = "cat.png"
val source = ImageDecoder.createSource(assets, assetFileName)
val drawable = ImageDecoder.decodeDrawable(source)
imageView.setImageDrawable(drawable)
}
Если мы загрузим анимированный файл cat.gif, то увидим статичное изображение (первый кадр). Поэтому следует добавить пару строк для старта анимации. В этом случае мы начинаем работать с AnimatedImageDrawable.
button.setOnClickListener {
val assets = this.assets
val assetFileName = "cat.gif"
val source = ImageDecoder.createSource(assets, assetFileName)
val drawable = ImageDecoder.decodeDrawable(source)
imageView.setImageDrawable(drawable)
if(drawable is AnimatedImageDrawable)
drawable.start()
}
Указанный способ работает только с Drawable, если картинку-источник декодируем в Bitmap, то получим только первый кадр и анимацию не увидим.
Интерфейс ImageDecoder.OnHeaderDecodedListener с единственным методом onHeaderDecoded() позволяет изменять изображение - изменить размеры, сделать обрезку, закруглить углы, наложить маску.
У метода onHeaderDecoded(decoder, info, source) три параметра:
Сам слушатель используется во втором параметре перегруженных версий методов decodeDrawable() или decodeBitmap().
Уменьшим размеры изображения в два раза.
// resize transformation
private val resizeDecodedListener =
ImageDecoder.OnHeaderDecodedListener { decoder, info, source ->
decoder.setTargetSize(info.size.width / 2, info.size.height / 2)
}
button.setOnClickListener {
val assets = this.assets
val assetFileName = "cat.png"
val source = ImageDecoder.createSource(assets, assetFileName)
val drawable = ImageDecoder.decodeDrawable(source, resizeDecodedListener)
imageView.setImageDrawable(drawable)
}
Обрежем изображение по краям.
// crop transformation
private val cropDecodedListener =
ImageDecoder.OnHeaderDecodedListener { decoder, info, source ->
val size = 100
val centerX = info.size.width / 2
val centerY = info.size.height / 2
decoder.crop = Rect(
centerX - size, centerY - size,
centerX + size, centerY + size
)
}
button.setOnClickListener {
val assets = this.assets
val assetFileName = "cat.png"
val source = ImageDecoder.createSource(assets, assetFileName)
val drawable = ImageDecoder.decodeDrawable(source, cropDecodedListener)
imageView.setImageDrawable(drawable)
}
// round corners transformation - using PostProcessor
val roundCornersDecodedListener =
ImageDecoder.OnHeaderDecodedListener { decoder, info, source ->
val path = Path().apply {
fillType = Path.FillType.INVERSE_EVEN_ODD
}
val paint = Paint().apply {
isAntiAlias = true
color = Color.TRANSPARENT
xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC)
}
decoder.setPostProcessor { canvas ->
val width = canvas.width.toFloat()
val height = canvas.height.toFloat()
val direction = Path.Direction.CW
path.addRoundRect(0f, 0f, width, height, 40f, 40f, direction)
canvas.drawPath(path, paint)
PixelFormat.TRANSLUCENT
}
}
button.setOnClickListener {
val assets = this.assets
val assetFileName = "cat.jpg"
val source = ImageDecoder.createSource(assets, assetFileName)
val drawable = ImageDecoder.decodeDrawable(source, roundCornersDecodedListener)
imageView.setImageDrawable(drawable)
}
Указанные способы работают и с анимированными картинками. В этом случае, например, закругление углов будет применено ко всем кадрам анимации.
Появилась поддержка анимированных GIF (и WebP). Вам нужно вызвать метод decodeDrawable() и получить доступ к объекту AnimatedImageDrawable. Это позволяет отказаться от библиотек Glide и Picasso 3, которые поддерживали данный формат раньше.
Выше показывались примеры загрузки анимированных файлов, которые работали без дополнительного кода (пару строк не считаем). Но подобные файлы потребляют достаточно много ресурсов, возможно уместно будет обрабатывать подобные файлы в другом потоке. В Kotlin можно использовать корутины.
button.setOnClickListener {
val assets = this.assets
val assetFileName = "animcat.gif"
val listener = ImageDecoder.OnHeaderDecodedListener { decoder,_,_ ->
decoder.setOnPartialImageListener { decodeException ->
true
}
}
GlobalScope.launch(Dispatchers.Default) {
// worker thread
val source = ImageDecoder.createSource(assets, assetFileName)
//val source = ImageDecoder.createSource(resources, R.drawable.animcat)
val drawable = ImageDecoder.decodeDrawable(source, listener)
GlobalScope.launch(Dispatchers.Main) {
// UI thread
imageView.setImageDrawable(drawable)
if (drawable is AnimatedImageDrawable) {
drawable.start()
}
}
}
}
Сначала этот код не заработал. Оказалось, нужно в манифесте добавить строчку для блока активности.
<activity android:name=".MainActivity"
android:hardwareAccelerated="false">
Новый API также позволяет обнаруживать ошибки при декодировании изображения. Внутри слушателя OnHeaderDecodedListener устанавливаем OnPartialImageListener.
ImageDecoder.OnHeaderDecodedListener { decoder, info, source ->
decoder.setOnPartialImageListener { decodeException ->
true
}
...
}
Вы можете ловить ошибки, связанные с загрузкой файла или частичной загрузкой изображения. Тип ошибок можно узнать через поле decodeException.error