Эксперименты с новыми API Android 12: Render Effect
В Android 12 довольно много нововведений, касающихся разработки. Одно из самых интересных — это Render Effect, с его помощью можно довольно просто делать эффект размытия фотографии или заднего фона, цветовые палитры и другие графические эффекты. Теперь добиться размытия заднего фона можно всего лишь одной строчкой API:
view.setRenderEffect(RenderEffect.createBlurEffect(radiusX, radiusY, SHADER_TILE_MODE))
Это касается не только фотографий, размыть можно любой элемент на экране. Таким образом, например, можно размывать экран при отображении диалогового окна, или скрывать изображение или элементы дизайна до определенного времени или действия пользователя.
Новый API, к сожалению, будет поддерживаться не всеми устройствами, а только теми, которым будет хватать вычислительных мощностей. Все эти дизайнерские изменения всё больше сближают визуальные стили двух мобильных операционных систем: iOS и Android.
Давайте посмотрим, какие возможности есть у разработчика и что можно с этим сделать. Для наших экспериментов нам понадобится Android Studio, которую можно скачать бесплатно с официального сайта, а также эмулятор со свежей операционной системой на борту. Для этого установим новый эмулятор с API 31, но для начала создадим новый проект через File -> New Project. В меню выберем Empty Activity:
На следующем экране дадим название проекту и нажмём кнопку Finish:
Когда проект загрузится, перейдём в файл Gradle и убедимся, что указан верный SDK:
Нам нужны compileSdkVersion и targerSdkVersion именно 31 — это версия новой ОС. Если стоит версия ниже — исправьте на 31 и синхронизируйте проект:
Убедимся, что в Манифесте нашего приложения указан атрибут exported, иначе приложение просто не запустится. Это касается всех приложений, которые должны устанавливаться на телефоны с новой операционной системой:
<activity android:name=".MainActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>
Пришло время скачать и запустить эмулятор с новейшей операционной системой на борту. Если у вас эмулятор ещё не стоит, откройте Device Manager (вкладка Tools -> AVD Manager) и нажмите в появившемся окне кнопку Create Virtual Device:
Выберите любой смартфон по вкусу:
Далее во вкладке x86 Images выберите API 31 в качестве операционной системы эмулятора:
И, наконец, дайте название устройству. Finish:
Когда эмулятор скачается, его можно открыть и запустить наше приложение. Это будет пустой экран с надписью по центру:
Давайте увеличим шрифт и попробуем размыть надпись с помощью нового API Render Effect.
Для начала добавим два новых атрибута в макет activity_main.xml: Id — чтобы найти наш макет в коде и textSize — для увеличения размера шрифта:
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" > <TextView android:id="@+id/my_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" android:textSize="42sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
Если запустить приложение сейчас, то мы увидим следующее:
Отлично, основа для работы у нас есть. Теперь перейдем в класс MainActivity. Пока он выглядит так:
package com.example.myapplication import androidx.appcompat.app.AppCompatActivity import android.os.Bundle class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } }
Добавим в него следующий код:
package com.example.myapplication import android.graphics.RenderEffect import android.graphics.Shader import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.widget.ImageView import android.widget.TextView import androidx.annotation.RequiresApi class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) { makeBlur() } } @RequiresApi(31) private fun makeBlur() { val blurEffect = RenderEffect.createBlurEffect(15f, 0f, Shader.TileMode.MIRROR) findViewById<TextView>(R.id.my_text).setRenderEffect(blurEffect) } }
Мы написали метод makeBlur, который создает нужный нам эффект размытия blurEffect, находит надпись Hello World! через findViewById и устанавливает эффект:
Как вы видите, метод createBlurEffect принимает три аргумента:
- сила размытия по горизонтали (чем выше число, тем сильнее размытие);
- сила размытия по вертикали (чем выше число, тем сильнее размытие);
- способ размытия по краям элемента.
Также мы добавили обязательную аннотацию @RequiresApi(31) над методом, потому что этот эффект работает только на смартфонах с операционной системой 12 и выше. Для этого же мы делаем проверку на версию ОС в методе onCreate:
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) { makeBlur() }
Теперь давайте добавим какое-то изображение в проект и попробуем размыть его. Положим в папку res/drawable картинку:
Добавим её в наш макет:
<?xml version="1.0" encoding="utf-8"?><?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" > <TextView android:id="@+id/my_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" android:textSize="42sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <ImageView android:id="@+id/image_view" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/android" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@+id/my_text" /> </androidx.constraintlayout.widget.ConstraintLayout>
Найдем её в коде и добавим эффект размытия:
package com.example.newapi import android.graphics.RenderEffect import android.graphics.Shader import android.os.Bundle import android.widget.ImageView import android.widget.TextView import androidx.annotation.RequiresApi import androidx.appcompat.app.AppCompatActivity class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) { makeBlur() makeBlurImage() } } @RequiresApi(31) private fun makeBlurImage() { val blurEffect = RenderEffect.createBlurEffect(15f, 15f, Shader.TileMode.MIRROR) findViewById<ImageView>(R.id.image_view).setRenderEffect(blurEffect) } @RequiresApi(31) private fun makeBlur() { val blurEffect = RenderEffect.createBlurEffect(15f, 0f, Shader.TileMode.MIRROR) findViewById<TextView>(R.id.my_text).setRenderEffect(blurEffect) } }
Запускаем:
Обратите внимание, что размытие по вертикали и горизонтали может быть разным по силе. Так можно достигать эффекта движения в определённую сторону.
Более того, Render Effect умеет работать не только с отдельными view, но и с иерархией view, то есть этот эффект можно применять на ViewGroup, часть экрана или целый экран: мы можем размыть весь экран, не размывая каждый элемент в отдельности. Для этого немного поменяем макет — добавим id корневому элементу, чтобы найти его в коде:
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/root_layout" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" > <TextView android:id="@+id/my_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" android:textSize="42sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <ImageView android:id="@+id/image_view" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/android" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@+id/my_text" /> </androidx.constraintlayout.widget.ConstraintLayout>
Теперь просто найдем контейнер в коде и добавим эффект размытия на весь экран:
package com.example.newapi import android.graphics.RenderEffect import android.graphics.Shader import android.os.Bundle import android.widget.ImageView import android.widget.TextView import androidx.annotation.RequiresApi import androidx.appcompat.app.AppCompatActivity import androidx.constraintlayout.widget.ConstraintLayout class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) { //makeBlur() //makeBlurImage() blurAll() } } @RequiresApi(31) private fun blurAll() { val blurEffect = RenderEffect.createBlurEffect(15f, 15f, Shader.TileMode.MIRROR) findViewById<ConstraintLayout>(R.id.root_layout).setRenderEffect(blurEffect) } @RequiresApi(31) private fun makeBlurImage() { val blurEffect = RenderEffect.createBlurEffect(15f, 15f, Shader.TileMode.MIRROR) findViewById<ImageView>(R.id.image_view).setRenderEffect(blurEffect) } @RequiresApi(31) private fun makeBlur() { val blurEffect = RenderEffect.createBlurEffect(15f, 0f, Shader.TileMode.MIRROR) findViewById<TextView>(R.id.my_text).setRenderEffect(blurEffect) } }
Обратите внимание, что мы закомментировали предыдущие методы, то есть размытие действительно применяется ко всем элементам на экране. Размытие можно регулировать по интенсивности, сделаем экран менее размытым:
@RequiresApi(31) private fun blurAll() { val blurEffect = RenderEffect.createBlurEffect(5f, 5f, Shader.TileMode.MIRROR) findViewById<ConstraintLayout>(R.id.root_layout).setRenderEffect(blurEffect) }
В итоге
Начиная с API 31 у нас появляется довольно мощный инструмент в лице Render Effect, который позволяет улучшать функционал и внешний вид наших приложений буквально парой строк.
Код проекта можно скачать тут.
Читайте больше полезных статей для начинающих Android-разработчиков:
- «Android 12: пристальный разбор главных фич»
- «Эксперименты с новыми API Android 12: Splash Screen»
- «Гайд по отладке Android-приложения: ищем баги и читаем логи»
- «Облегчаем жизнь Android-разработчика c помощью Tools»
А если затянет — приходите на факультет Android-разработки. В время учебы вы разработаете Android-приложение и выложите его в Google Play, даже если никогда не программировали. А также своите языки Java и Kotlin, командную разработку, Material Design и принципы тестирования.