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

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

Шкодим

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

ImageDecoder и AnimatedImageDrawable

Загружаем изображение из источника
Интерфейс 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

Интерфейс ImageDecoder.OnHeaderDecodedListener с единственным методом onHeaderDecoded() позволяет изменять изображение - изменить размеры, сделать обрезку, закруглить углы, наложить маску.

У метода onHeaderDecoded(decoder, info, source) три параметра:

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

Указанные способы работают и с анимированными картинками. В этом случае, например, закругление углов будет применено ко всем кадрам анимации.

AnimatedImageDrawable

Появилась поддержка анимированных 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

  • SOURCE_EXCEPTION
  • SOURCE_INCOMPLETE
  • SOURCE_MALFORMED_DATA
Реклама