Новый современный набор инструментов пользовательского интерфейса Jetpack Compose был анонсирован компанией Google более года назад, и, наконец, в июле была выпущена стабильная версия 1.0. Также многие компании, такие как Twitter, lyft, Square, уже адаптировали Jetpack Compose на своих производственных уровнях, потому что он очень интуитивный, мощный и упрощает всю структуру пользовательского интерфейса, если правильно его использовать. Эта новая парадигма структуры пользовательского интерфейса поменяет в дальнейшем очень многое, также нам придется приложить еще немало усилий для миграции предыдущих вещей, связанных с пользовательским интерфейсом, таких как загрузка изображений из Url.
Загрузка и отрисовка изображений для Jetpack Compose
Когда Google анонсировал Jetpack Compose 1.0.0-alpha01, я задался вопросом, как мы должны перенести все предыдущие системы, связанные с пользовательским интерфейсом, в проекты Jetpack Compose? Простое получение изображений из Url (сети) и рисование на компонуемых Image потребовало бы совершенно иного подхода, чем раньше. В то время была выпущена библиотека accompanist Криса Бейнса, и она меня очень вдохновила. Эта библиотека поддерживала Coil (позже появилась поддержка Glide), но я хотел использовать и предоставить как можно больше вариантов для выбора библиотек загрузки изображений. Потому что миграция целых систем загрузки изображений (например, с Glide на Coil) может доставить разработчикам немало хлопот. Поэтому Landscapist был создан для поддержки многих вариантов, таких как Glide, Coil и Fresco для Jetpack Compose.
Landscapist
Landscapist - это библиотека загрузки изображений для Jetpack Compose. Есть три варианта: Glide, Coil и Fresco. Таким образом, мы можем выбирать по своему вкусу. Эта библиотека также поддерживает анимацию загрузки, например, эффект мерцания и круговое раскрытие. Если вы хотите узнать больше о применении этой библиотеки, то можете обратиться к демонстрационным проектам, использующим Landscapist для рисования изображений.
GlideImage
Мы можем загружать и рисовать изображения из Url с помощью компонуемой функции GlideImage
, как показано ниже. Это довольно просто.
GlideImage(
imageModel = poster.poster
)
Мы также можем предоставить основные атрибуты, такие же, как у компонуемого Image
, например contentScale
и modifier
.
GlideImage(
imageModel = poster.poster,
contentScale = ContentScale.Crop,
modifier = Modifier
)
Если мы хотим показать плейсхолдер (загрузка) и изображение ошибки в зависимости от состояния выборки, то можем использовать атрибуты placeHolder
и error
, как показано ниже.
GlideImage(
imageModel = imageUrl,
// Crop, Fit, Inside, FillHeight, FillWidth, None
contentScale = ContentScale.Crop,
// shows an image with a circular revealed animation.
circularRevealedEnabled = true,
// shows a placeholder ImageBitmap when loading.
placeHolder = ImageBitmap.imageResource(R.drawable.placeholder),
// shows an error ImageBitmap when the request failed.
error = ImageBitmap.imageResource(R.drawable.error)
)
Различные варианты компоновки на основе состояний запроса
Иногда нам необходимо реализовать разные пользовательские интерфейсы в зависимости от состояния запроса. В принципе, мы можем использовать различные варианты компоновки в зависимости от состояния загрузки/успеха/ошибки. Приведенный ниже пример показывает индикатор, когда состояние запроса загружается, и отображает компонуемый элемент Text
, когда запрос не выполняется. При успешном запросе будет показано загруженное изображение.
GlideImage(
imageModel = poster.poster,
modifier = modifier,
// shows a progress indicator when loading an image.
loading = {
ConstraintLayout(
modifier = Modifier.fillMaxSize()
) {
val indicator = createRef()
CircularProgressIndicator(
modifier = Modifier.constrainAs(indicator) {
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
start.linkTo(parent.start)
end.linkTo(parent.end)
}
)
}
},
// shows an error text message when request failed.
failure = {
Text(text = "image request failed.")
})
Если мы хотим настроить свою композицию успеха, мы также можем настроить результат пользовательского интерфейса успеха, как показано ниже. Лямбда success
передает состояние запроса, и мы можем получить ImageBitmap
из его инстанса.
GlideImage(
imageModel = poster.poster,
success = {
Image(
bitmap = it.imageBitmap!!,
contentDescription = null,
modifier = Modifier
.width(128.dp)
.height(128.dp)
)
}
)
LocalGlideRequestBuilder
CompositionLocal - одна из наиболее часто используемых концепций в Jetpack Compose, и в официальном справочнике Android она описывается следующим образом.
Compose передает данные через дерево композиции в явном виде посредством параметров к компонуемым функциям. Часто это самый простой и лучший способ передачи данных по дереву. CompositionLocals можно использовать как неявный способ передачи данных через композицию.
Landscapist также поддерживает CompositionLocal
для предоставления RequestBuilder
(основа запроса в Glide) в компонуемом потоке данных.
// customize the RequestBuilder as needed
val requestBuilder = Glide.with(LocalView.current)
.asBitmap()
.thumbnail(0.1f)
.transition(BitmapTransitionOptions.withCrossFade())
CompositionLocalProvider(LocalGlideRequestBuilder provides requestBuilder) {
// This will automatically use the value of current RequestBuilder in the hierarchy.
GlideImage(
imageModel = ...
)
}
CoilImage
Coil - это почти то же самое, что и Glide
, только вместо GlideImage
используется CoilImage
.
CoilImage(
imageModel = poster.poster
)
Также мы можем предоставить множество пользовательских атрибутов, таких как Glide.
CoilImage(
imageModel = imageUrl,
// Crop, Fit, Inside, FillHeight, FillWidth, None
contentScale = ContentScale.Crop,
// shows an image with a circular revealed animation.
circularRevealedEnabled = true,
// shows a placeholder ImageBitmap when loading.
placeHolder = ImageBitmap.imageResource(R.drawable.placeholder),
// shows an error ImageBitmap when the request failed.
error = ImageBitmap.imageResource(R.drawable.error)
)
LocalCoilImageLoader
Также поддерживает CompositionLocal
для обеспечения подобия ImageLoader
в компонуемой иерархии.
val imageLoader = ImageLoader.Builder(context)
// customize the ImageLoader as needed
.build()
CompositionLocalProvider(LocalCoilImageLoader provides imageLoader) {
// This will automatically use the value of current imageLoader in the hierarchy.
CoilImage(
imageModel = ...
)
}
FrescoImage
Fresco немного отличается от описанных выше, перед его использованием следует инициализировать пайплайн изображений. Если нам нужно получать изображения из сети, рекомендуем использовать OkHttpImagePipelineConfigFactory
.
Используя ImagePipelineConfig
, мы можем настраивать стратегии кэширования, работу в сети и пул потоков. Вот другие ссылки, которые связаны с конфигурацией пайплайна.
class App : Application() {
override fun onCreate() {
super.onCreate()
val pipelineConfig =
OkHttpImagePipelineConfigFactory
.newBuilder(this, OkHttpClient.Builder().build())
.setDiskCacheEnabled(true)
.setDownsampleEnabled(true)
.setResizeAndRotateEnabledForNetwork(true)
.build()
Fresco.initialize(this, pipelineConfig)
}
}
Также и другие вещи почти не отличаются от вышеупомянутых библиотек.
FrescoImage(
imageUrl = stringImageUrl,
// Crop, Fit, Inside, FillHeight, FillWidth, None
contentScale = ContentScale.Crop,
// shows an image with a circular revealed animation.
circularRevealedEnabled = true,
// shows a placeholder ImageBitmap when loading.
placeHolder = ImageBitmap.imageResource(R.drawable.placeholder),
// shows an error ImageBitmap when the request failed.
error = ImageBitmap.imageResource(R.drawable.error)
)
Эффект мерцания
Landscapist поддерживает эффект мерцания при загрузке изображений из сети, и мы можем реализовать его с помощью ShimmerParams
. В примере ниже используется CoilImage
, но ShimmerParams
также можно применить для GlideImage
и FrescoImage
.
CoilImage(
imageModel = poster.poster,
modifier = modifier,
// shows a shimmering effect when loading an image.
shimmerParams = ShimmerParams(
baseColor = MaterialTheme.colors.background,
highlightColor = shimmerHighLight,
durationMillis = 350,
dropOff = 0.65f,
tilt = 20f
),
// shows an error text message when request failed.
failure = {
Text(text = "image request failed.")
})
Можно настроить все детали ShimmerParam
, используя baseColor
, highlightColor
, durationMillis
, dropOff
и tilt
.
Анимация кругового раскрытия
Landscapist поддерживает анимацию кругового раскрытия при показе изображений. Это можно реализовать очень просто, задав для атрибута circularRevealedEnabled
значение true
.
FrescoImage(
imageUrl = stringImageUrl,
// Crop, Fit, Inside, FillHeight, FillWidth, None
contentScale = ContentScale.Crop,
// shows an image with a circular revealed animation.
circularRevealedEnabled = true,
// shows a placeholder ImageBitmap when loading.
placeHolder = ImageBitmap.imageResource(R.drawable.placeholder),
// shows an error ImageBitmap when the request failed.
error = ImageBitmap.imageResource(R.drawable.error)
)
CircularRevealedImage
Мы можем использовать составное изображение CircularRevealedImage
независимо от библиотек загрузки (Glide, Coil, Fresco) и реализовать анимацию кругового раскрытия, используя наше рисованное изображение ресурса.
CircularRevealedImage(
bitmap = ImageBitmap.imageResource(R.drawable.flower),
contentDescription = null,
circularRevealedEnabled = true,
circularRevealedDuration = 350
)
Мы должны установить параметр circularRevealedEnabled
в true
, если хотим применить анимацию кругового раскрытия, также можно изменить продолжительность анимации с помощью атрибута circularRevealedDuration
.
Палитра
Landscapist поддерживает API палитры для извлечения цветовых профилей из изображений. В основном, необходимо использовать BitmapPalette
для извлечения основных цветов из изображений, как показано ниже. И можно ссылаться на типы цветов здесь.
var palette by remember { mutableStateOf<Palette?>(null) }
GlideImage( // CoilImage, FrescoImage also can be used.
imageModel = poster?.poster!!,
bitmapPalette = BitmapPalette {
palette = it
}
)
Crossfade(
targetState = palette,
modifier = Modifier
.padding(horizontal = 8.dp)
.size(45.dp)
) {
Box(
modifier = Modifier
.background(color = Color(it?.lightVibrantSwatch?.rgb ?: 0))
.fillMaxSize()
)
}
Мы можем настроить больше опций, используя interceptor
и paletteLoadListener
.
var palette by remember { mutableStateOf<Palette?>(null) }
GlideImage(
imageModel = poster?.poster!!,
modifier = Modifier
.aspectRatio(0.8f),
bitmapPalette = BitmapPalette(
imageModel = poster.poster,
useCache = true,
interceptor = {
it.addFilter { rgb, hsl ->
// here edit to add the filter colors.
false
}
},
paletteLoadedListener = {
palette = it
}
)
)
Заключение
В этом посте мы рассмотрели, как загружать и рисовать изображения для Jetpack Compose с помощью Landscapist. К большому удовольствию, эта библиотека используется многими мировыми компаниями, включая Twitter. Ее путь начался с Jetpack Compose 1.0.0-alpha, и на данный момент она уже была выпущена более 30 раз, пока не достигла Jetpack Compose 1.0 stable. К счастью, я получил массу вдохновения и помощи от accompanist (спасибо, Крис Бейнс! спасибо за все Google Compose, Android Team!); и это долгое путешествие, кажется, только начинается.
Материал подготовлен в рамках курса «Android Developer. Professional». Всех желающих приглашаем на двухдневный онлайн-интенсив «Android Lint». На нем мы:
- изучим Android Lint API;
- научимся писать кастомные Lint детекторы и тесты на них;
- разберемся, как правильно подсвечивать контекст и реализовывать LintFix’ы;
- напишем несколько проверок на частые ошибки использования популярных библиотек.
>> РЕГИСТРАЦИЯ