Всем привет!

Сплеш-скрин (также известный как экран загрузки) – неотъемлемая часть приложения, когда оно разрастается так, что холодный старт начинает занимать какое-то ощутимое время. До недавнего обновления сплеш-скрин реализовывался двумя основными способами:

  1. Через атрибут темы windowBackground

  2. Через отдельную Activity

Первый подход является более оптимальным, т.к. не расходует лишних ресурсов на создание и манипуляции с отдельной активити. Однако с первым подходом нет возможности использовать анимации – картинка, помещаемая в windowBackground, должна быть статичной.

На помощь нам с выходом Android 12 разработчики предоставили новую API для создания сплеш-скринов.

В данной статье я не буду пересказывать официальную документацию по созданию нового сплешкрина (с которой можно ознакомиться здесь), однако расскажу, с какими сложностями и подводными камнями, не описанными в официальной доке, мы столкнулись при создании анимированного экрана загрузки в нашем приложении BestDoctor.

Ожидаемый результат

При старте приложения у нас происходит небольшая подгрузка изначальных данных, поэтому одновременно функцией сплешскрина должна быть и индикация загрузки. Лучше всего для этого подходит старый добрый спиннер (или лоудинг индикатор). Для этого удобнее всего закрутить логотип BestDoctor (среди сотрудников компании известный как АГРЕССИВНЫЙ КРОВАВЫЙ ЁЖ).

Вот такой сплешскрин хотим получить в результате:

Приступаем к реализации

Следуя официальной доке, реализовать новый сплеш-скрин можно двумя способами:

  1. Через атрибуты общей темы приложения

  2. Через compat-библиотеку

Разница этих двух методов в том, что первый работает только для Android 12+, а второй сделает такой же сплшскрин для старых версий.

ВАЖНО: анимированную картинку возможно использовать только на Android 12+, т.е. даже при использовании компат-библиотеки на предыдущих версиях все равно будет статичная картинка.

Т.к. наш сплеш-скрин должен заменить индикацию загрузки, нам критично важна анимация, поэтому мы будем реализовывать сплеш-скрин через атрибуты общей темы только для Android 12+; для старых версий оставим наш старый сплеш.
Приступим.

Начнем со статичной картинки с бэкграундом. Добавляем нужные атрибуты в тему приложения.

<item name="android:windowSplashScreenBackground">@color/splashscreen_background</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/ic_splashscreen_logo</item>

В качестве картинки используем векторный логотип BestDoctor:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="32dp"
    android:height="33dp"
    android:viewportWidth="32"
    android:viewportHeight="33">
    <path
        android:fillColor="@color/coral30"
        android:pathData="M32,18.3662H21.7073C21.2638,18.3662 21.042,18.9013 21.3551,19.2145L28.6343,26.4967L25.9992,29.133L18.733,21.8638C18.4199,21.5506 17.885,21.7724 17.885,22.2162V32.5H14.1541V22.2162C14.1541,21.7724 13.6192,21.5506 13.3062,21.8638L6.0008,29.146L3.3657,26.5098L10.6449,19.2276C10.958,18.9144 10.7362,18.3793 10.2927,18.3793H0V14.6468H10.2927C10.7362,14.6468 10.958,14.1117 10.6449,13.7985L3.3657,6.5033L6.0008,3.867L13.267,11.1362C13.5801,11.4494 14.115,11.2276 14.115,10.7839V0.5H17.8459V10.7839C17.8459,11.2276 18.3808,11.4494 18.6938,11.1362L25.96,3.867L28.6343,6.5033L21.3551,13.7855C21.042,14.0987 21.2638,14.6338 21.7073,14.6338H32V18.3662Z" />
</vector>

Запускаем приложение и получаем вот такой отстой:

Лого как-то непонятно растянулось, нам такое не подходит. Начинаем разбираться в проблеме.

Переходим в раздел официальной документации по размеру картинки и находим вот такой текст:

The splash screen icon uses the same specifications as Adaptive icons, as follows:

* Branded image: This should be 200×80 dp.
* App icon with an icon background: This should be 240×240 dp, and fit within a circle of 160 dp in diameter.
* App icon without an icon background: This should be 288×288 dp, and fit within a circle of 192 dp in diameter.

For example, if the full size of an image is 300×300 dp, the icon needs to fit within a circle with a diameter of 200 dp. Everything outside the circle will be invisible (masked).

Что все это значит:
В Android 12 при запуске приложения по дефолту в качестве сплеш-скрин картинки используется иконка приложения. Собственно, для той картинки, которую мы хотим использовать явно, используется те же спецификации, что и для адаптивных иконок. Мы используем картинку без бэкграунда (без бэкграунда именно картинки, а не самого экрана), поэтому, судя по доке, наша картинка должна быть размера 192x192 dp. Окей. Обновим наш вектор, установив атрибуты viewportWidth и viewportHeight. Также придется завернуть весь наш вектор в group и добавить translate, чтобы в увеличенном viewport разместиться по центру.
Получаем:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="32dp"
    android:height="33dp"
    android:viewportWidth="192"
    android:viewportHeight="192">
    <group
        android:translateX="80"
        android:translateY="79.5">
        <path
            android:fillColor="@color/coral30"
            android:pathData="M32,18.3662H21.7073C21.2638,18.3662 21.042,18.9013 21.3551,19.2145L28.6343,26.4967L25.9992,29.133L18.733,21.8638C18.4199,21.5506 17.885,21.7724 17.885,22.2162V32.5H14.1541V22.2162C14.1541,21.7724 13.6192,21.5506 13.3062,21.8638L6.0008,29.146L3.3657,26.5098L10.6449,19.2276C10.958,18.9144 10.7362,18.3793 10.2927,18.3793H0V14.6468H10.2927C10.7362,14.6468 10.958,14.1117 10.6449,13.7985L3.3657,6.5033L6.0008,3.867L13.267,11.1362C13.5801,11.4494 14.115,11.2276 14.115,10.7839V0.5H17.8459V10.7839C17.8459,11.2276 18.3808,11.4494 18.6938,11.1362L25.96,3.867L28.6343,6.5033L21.3551,13.7855C21.042,14.0987 21.2638,14.6338 21.7073,14.6338H32V18.3662Z" />
    </group>
</vector>

Запускаем приложение:

Ура! Картинку больше не размазывает, размер правильный. Двигаемся дальше.

Анимация кручения

Splash Screen API поддерживает использование AnimationDrawable и AnimatedVectorDrawable – воспользуемся же этой возможностью!

  1. Внесем изменения в код нашего вектора: добавим атрибуты name (идентификатор group нашего вектора, нужен для обращения к нему) и pivotX, pivotY (координаты точки вращения, в нашем случае – центр картинки).

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="32dp"
    android:height="33dp"
    android:viewportWidth="192"
    android:viewportHeight="192">
    <group
        android:name="logoGroup"
        android:pivotX="16"
        android:pivotY="16.5"
        android:rotation="0"
        android:translateX="80"
        android:translateY="79.5">
        <path
            android:fillColor="@color/coral30"
            android:pathData="M32,18.3662H21.7073C21.2638,18.3662 21.042,18.9013 21.3551,19.2145L28.6343,26.4967L25.9992,29.133L18.733,21.8638C18.4199,21.5506 17.885,21.7724 17.885,22.2162V32.5H14.1541V22.2162C14.1541,21.7724 13.6192,21.5506 13.3062,21.8638L6.0008,29.146L3.3657,26.5098L10.6449,19.2276C10.958,18.9144 10.7362,18.3793 10.2927,18.3793H0V14.6468H10.2927C10.7362,14.6468 10.958,14.1117 10.6449,13.7985L3.3657,6.5033L6.0008,3.867L13.267,11.1362C13.5801,11.4494 14.115,11.2276 14.115,10.7839V0.5H17.8459V10.7839C17.8459,11.2276 18.3808,11.4494 18.6938,11.1362L25.96,3.867L28.6343,6.5033L21.3551,13.7855C21.042,14.0987 21.2638,14.6338 21.7073,14.6338H32V18.3662Z" />
    </group>
</vector>
  1. В res/animator добавляем аниматор кручения:

<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1500"
    android:interpolator="@android:anim/linear_interpolator"
    android:propertyName="rotation"
    android:repeatCount="-1"
    android:repeatMode="restart"
    android:valueFrom="0"
    android:valueTo="360" />

Обратите внимание, что здесь duration="1500" (время, за которое картинка совершает полный оборот на 360 градусов), repeatCount="-1" (бесконечный повтор анимации), мы к этому еще вернемся.

  1. В res/drawable добавляем ic_splashscreen_logo_animated. В target.name указываем имя группы, в которую завернут path всего нашего вектора.

<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/ic_splashscreen_logo">
    <target
        android:name="logoGroup"
        android:animation="@animator/splashscreen_animator" />
</animated-vector>
  1. И наконец добавляем нужные атрибуты темы (заменив ссылку с обычной иконки на анимированную):

<item name="android:windowSplashScreenBackground">@color/splashscreen_background</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/ic_splashscreen_logo_animated</item>
<item name="android:windowSplashScreenAnimationDuration">1500</item>

Обратите внимание, что в windowSplashScreenAnimationDuration указаны те же самые 1500, что в animator, т.е. мы хотим, чтобы лого совершало один полный оборот за 1500 миллисекунд.
Запускам и получаем вот такое:

Хммм... Проблемка: лого делает один оборот и останавливается, хотя загрузка у нас еще не завершилась.

В атрибутах темы отсутствует что-то, отвечающее за повтор анимации, хотя в нашем animator установлен repeatCount="-1". Что ж, будем чинить это дерзким хаком.

  1. В animator оставляем желаемое время одного цикла анимации.

  2. В windowSplashScreenAnimationDuration вместо времени одного цикла анимации подставляем Integer.MAX_VALUE, то есть 2147483647. Если загрузка не будет занимать 25 дней, то пользователи никогда и не заметят, что анимация закончилась.

Обновленные атрибуты темы выглядят вот так:

<item name="android:windowSplashScreenBackground">@color/splashscreen_background</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/ic_splashscreen_logo_animated</item>
<item name="android:windowSplashScreenAnimationDuration">2147483647</item>

Запускаем и получаем ожидаемый результат, ура!

Брендинг

Конечно же нам хочется, чтобы пользователи знали, услугами какой компании они пользуются, поэтому добавим брендинг на сплеш-скрин. Эта возможность также предусмотрена в новом API.

Добавляем нужный атрибут темы с векторной картинкой:

<item name="android:windowSplashScreenBrandingImage">@drawable/ic_splashscreen_branding</item>

Запускаем и получаем вот эту кашу:

Немножко погуглив, я нашел легкий обход этой проблемы:

  1. В res/drawable добавляем ic_splashscreen_branding_centered:

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:drawable="@drawable/ic_splashscreen_branding"
        android:gravity="center" />
</layer-list>
  1. Меняем в теме ссылку на отцентрированную картинку:

<item name="android:windowSplashScreenBrandingImage">@drawable/ic_splashscreen_branding_centered</item>

Вуаля: картинка стала нормальной :)

Что ж, вот мы и получили ожидаемый результат! Такой сплешскрин мы держим на экране до окончания изначальной загрузки данных. Как это сделать, описано в официальной доке.

Большое спасибо за внимание!

Комментарии (5)


  1. gatoazul
    29.01.2022 23:37
    +4

    А что при этом видно на старых Андроидах?


    1. VXP
      30.01.2022 03:11
      +1

      Их старый сплеш-скрин


      1. gatoazul
        30.01.2022 09:08

        Старый - ваш? Где он прописан? Или какой-то системный?


        1. etozhesano
          30.01.2022 10:55

          Я так понял они не увидят анимацию, для них картинка будет статична.


  1. Revertis
    30.01.2022 17:56

    А как этот новый API работает с тёмной и светлой темами приложения? Есть ли мигание?