Как стать автором
Обновить
85.82
SimbirSoft
Лидер в разработке современных ИТ-решений на заказ

То, чего нам так не хватало: Render Effect в Android 12

Время на прочтение 6 мин
Количество просмотров 8.8K

Иногда бывает нужно размыть задний план на экранах мобильного приложения, например в чате. Теперь это можно сделать всего парой строк кода. В Android 12 появился новый API Render Effect, который позволяет накладывать визуальные эффекты на Canvas или View. Этот API радует своей простотой и высокой скоростью отрисовки. Наибольший интерес представляет Render Effect для размытия (BlurEffect), но в этой статье мы затронем и остальные виды эффектов. Материал может быть полезен не только андроид-разработчикам, но и дизайнерам мобильных приложений.

Итак, каким образом можно размыть задний план при отображении диалога? Раньше в Андроиде для этого надо было отрисовать все вью с заднего плана на bitmap-е и затем размыть его с помощью RenderScript или OpenGL. Но это означает, что в проекте появится немало запутанного для прочтения кода, либо придется подключить стороннюю библиотеку для размытия. Плюс добавятся обработчики событий отрисовки и код для получения битмапа. К тому же по производительности эти решения могут не давать желаемого результата. При использовании некоторых популярных библиотек для размытия разработчики замечают лаги, например, если есть RecyclerView, который содержит много BlurView.

С помощью Render Effect мы можем всего парой строк кода реализовать блюр без лагов. Render Effect работает эффективно за счет того, что он использует Render Nodes (узлы отрисовки). Они образуют иерархию аппаратно ускоренной отрисовки, и эта отрисовка происходит с помощью GPU (графического процессора). Перерисовываться будут только те части UI, которые оказались в состоянии invalidated. Все вычисления для Render Effect выполняются в Render потоке и не блокируют UI. 

У Render Effect очень простой API:

val renderNode = RenderNode("myRenderNode")
renderNode.setPosition(0, 0, 50, 50)
renderNode.setRenderEffect(renderEffect) 
canvas.drawRenderNode(renderNode)

Мы добавили эффект для RenderNode методом setRenderEffect(), и его отрисовка происходит в методе Canvas.drawRenderNode().

Можно применить Render Effect и к узлу отрисовки вьюшки:

imageView.setRenderEffect(renderEffect)

Приведем пример создания эффекта:

val blurEffect = RenderEffect.createBlurEffect(
       20f, //radiusX
       20f, //radiusY       
       Shader.TileMode.CLAMP
)
imageView.setRenderEffect(blurEffect)

Здесь мы создаем эффект размытия и применяем его к узлу отрисовки, привязанному к imageView. Задаем интенсивность размытия по оси X и оси Y (Значения 20f) . Последний аргумент – Shader.TileMode – определяет то, как будет выглядеть эффект на краях отрисовываемой области.

Если применить размытие к корневому layout-у, то будут размыты все вьюшки: и кнопка, и ползунки.

Варианты значений TileMode:

  • MIRROR. Используются зеркальные отражения изображения по вертикали и горизонтали, при этом на границах нет резких переходов.

  • CLAMP. Если shader выходит за пределы границ, используется цвет пикселей на границе. Выглядит практически также как MIRROR, но возможно незначительное искажение формы объектов возле границы.

  • REPEAT. Изображение повторяется горизонтально и вертикально. На изображении заметно, как темная вода из нижней части изображения отразилась на  верхней границе изображения.

  • DECAL (появился в API 31). Отрисовка shader-а только в пределах границ. Можно заметить, что на границе изображение стало чуть светлее.

Мы также можем применить размытие при создании тени. Конечно, можно просто создать тень с помощью свойства Elevation:

android:elevation="10dp"

Однако, в этом случае мы не сможем задавать направление, в котором должна отбрасываться тень, или ее цвет. Также тень для Elevation по умолчанию работает только с формами скругленного прямоугольника (круг и прямоугольник – частные случаи прямоугольника со скругленными углами). Для тени другой формы нужно реализовать ViewOutlineProvider, чтобы он возвращал Outline с setPath(...), а это может быть весьма трудоемко.

Кастомную тень можно создать и с помощью xml, прописав <shape> с градиентом:

<gradient
       android:type="radial"
       android:centerColor="#90000000"
       android:gradientRadius="70dp"
       android:startColor="@android:color/white"
       android:endColor="@android:color/transparent"/>

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

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

<shape android:shape="oval">
   <solid android:color="#CCCCCC" />
</shape>

Затем этот drawable устанавливаем в качестве содержимого ImageView и применяем размытие к ImageView:

imageView.setImageResource(R.drawable.gray_circle)
val renderEffect = RenderEffect.createBlurEffect(20f, 20f, Shader.TileMode.CLAMP)
imageView.setRenderEffect(renderEffect)

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

<ImageView
   android:id="@+id/shadow"
   android:layout_width="150dp"
   android:layout_height="150dp"
   android:src="@drawable/ic_baseline_time_to_leave_24"
   android:tint="#444444"/>
val effect = RenderEffect.createBlurEffect(10f, 10f, Shader.TileMode.CLAMP)
imageViewfindViewById<ImageView>(R.id.shadow).setRenderEffect(effect)

Если попробовать сделать размытие с разными значениями радиуса, например, 20 по горизонтали и 0 по вертикали, то получится эффект, похожий на смазанный снимок.

Всего есть семь видов RenderEffect:

  • Bitmap – отрисовывает содержимое битмапа. Обычно используется в цепочке для наложения последующих эффектов.

  • Blur – размытие по оси X и Y.

  • ColorFilter – применяется цветной фильтр (см. скриншот ниже).

  • Offset – сдвиг по осям X, Y (жаль, что нет поворота).

  • Shader – отрисовывает шейдер, переданный в аргументах. С помощью шейдеров можно создавать, например,  различные градиенты.

  • Chain – комбинация 2 эффектов. Результат отрисовки одного эффекта используется как источник для второго эффекта.

  • BlendMode – для объединения 2 эффектов в определенном режиме.

Можно добиться красивого эффекта “замерзшего стекла” (или “запотевшего окна”): он достигается сочетанием размытия и наложения прозрачно-белого цвета.

Применим такой эффект к панели внизу экрана. Фактически здесь надо сочетать три эффекта: отрисовка битмапа + размытие + цветной фильтр. К сожалению, нельзя сделать так, чтобы размывалась та часть UI, которая отрисована под вьюшкой. Поэтому RenderEffect нужно применить не к самой панели, а к тому, что находится под ней и содержит фоновое изображение: к imageView или к корневому layout-у. Комбинировать эффекты можно 2 способами: передавать один эффект в параметр create-метода другого эффекта, либо использовать метод createChainEffect():

val bmpEffect = RenderEffect.createBitmapEffect(...)
val blurBmpEffect = RenderEffect.createBlurEffect(..., bmpEffect, ..)
val finalEffect = RenderEffect.createColorFilterEffect(..., blurBitmapEffect)

или

val finalEffect = RenderEffect.createChainEffect(colorEffect,
blurEffect)

Приведем пример наложения цветного фильтра:

val argb = Color.valueOf(red, green, blue, transparency).toArgb()
val colorEffect = RenderEffect.createColorFilterEffect(
       PorterDuffColorFilter(argb, PorterDuff.Mode.SRC_ATOP)
)

Результат:

Или можно с помощью цветного фильтра поменять насыщенность изображения:

val matrix = ColorMatrix()
matrix.setSaturation(0f)
imageView.setRenderEffect(RenderEffect.createColorFilterEffect(ColorMatrixColorFilter(matrix)))

Результат для setSaturation(0f) и setSaturation(100f):

BlendMode

С помощью метода RenderEffect.createBlendModeEffect() можно объединить два эффекта в одном из режимов:

  • CLEAR

  • SRC

  • DST

  • SRC_OVER

  • DST_OVER

  • SRC_IN

  • DST_IN

  • SRC_OUT

  • DST_OUT

  • SRC_ATOP

  • DST_ATOP

  • XOR

  • PLUS

  • MODULATE

  • SCREEN

  • OVERLAY

  • DARKEN

  • LIGHTEN

  • COLOR_DODGE

  • COLOR_BURN

  • HARD_LIGHT

  • SOFT_LIGHT

  • DIFFERENCE

  • EXCLUSION

  • MULTIPLY

  • HUE

  • SATURATION

  • COLOR

  • LUMINOSITY

Подробное описание режимов можно посмотреть здесь. Для примера приведем три режима (здесь источник, то есть первый RenderEffect – это синий квадрат, а приемник, то есть второй Render Effect – красный круг):

DST_ATOP выбрасывает пиксели, которые не накладываются на источник.
DST_ATOP выбрасывает пиксели, которые не накладываются на источник.
OVERLAY умножает цвета.
OVERLAY умножает цвета.
COLOR сохраняет оттенок и насыщенность источника, а яркость делает как у приемника.
COLOR сохраняет оттенок и насыщенность источника, а яркость делает как у приемника.

Как уже упоминалось выше, RenderEffect работает крайне эффективно. При скролле RecyclerView с большим количеством элементов, у которых размыто по две ImageView (размытие с параметрами radiusX: 20f, radiusY: 20f, Shader.TileMode.CLAMP), никаких подтормаживаний не наблюдается. Размытие вью с анимацией тоже работает без задержек. В настоящий момент работа RenderEffect была проверена автором на эмуляторе Android 12 (API 31). 

Что касается поддержки RenderEffect API, можно надеяться, что она будет добавлена в библиотеке AndroidX  для Android 10 и 11, так как Render Nodes, лежащие в основе RenderEffect, были добавлены в Android 10 (API level 29). Однако, как пишут в официальной документации, Render Effect могут поддерживать не все устройства: “Different Android devices may or may not support the feature due to limited processing power”.

Отметим также, что Render Effect является одним из вариантов замены для RenderScript API, который устарел начиная с Android 12.

Заключение

Итак, в Android 12 мы получили то, чего так долго не хватало многим разработчикам – простой API для отрисовки визуальных эффектов. Сочетая простые эффекты (размытие, цветные фильтры, шейдеры), мы можем получать интересные графические результаты. При этом больше не нужно возиться с вытаскиванием bitmap-изображения, эффект можно просто повесить на вьюшку. А благодаря использованию RenderNodes, отрисовка RenderEffect работает очень быстро. 

Спасибо за внимание! Надеемся, что наш опыт был вам полезен.

Теги:
Хабы:
+6
Комментарии 3
Комментарии Комментарии 3

Публикации

Информация

Сайт
www.simbirsoft.com
Дата регистрации
Дата основания
Численность
1 001–5 000 человек
Местоположение
Россия