- PVSM.RU - https://www.pvsm.ru -
Перевод статьи Elvis Chidera [1] "The (Complete) Android Splash Screen Guide" [2]. Но для начала немного истории о проблеме Splash Screen на Android.
Google в гайдлайнах Material Design представляет две концепции [3] Splash Screen (или Launch Screen):
Placeholder UI — концепция для приложений, которые загружаются довольно быстро и не требуют показа перед запуском логотипов, красивых анимаций и прочих украшений. Суть в том, что во время холодного старта основной фон приложения, строка состояния, фон для панели инструментов уже раскрашиваются в цвета приложения, но до полной загрузки без контента. Такой подход, по мнению дизайнеров Google, позволяет более плавно с точки зрения пользователя переходить от момента запуска приложения к работе с ним.
Branded launch screen — собственно и есть то, что большинство разработчиков (по крайней мере Android-разработчиков), именуют Splash Screen. Это некоторый логотип, изображение, реже анимация, которые пользователь на короткое время видит во время старта приложения.
Теперь, прежде чем перейдем к переводу статьи Элвиса, которая рассказывает о Branded launch screen, немного о грустном в лагере поклонников Placeholder UI.
Не смотря на собственные гайдлайны, Google не смог реализовать подход Placeholder UI в собственных приложениях. Я навскидку выбрал три популярных приложения от Google, где по логике должен быть Placeholder UI, но он не работает. Показан переход от холодного старта к рабочему состоянию приложения:
Как видим, во время холодного старта загружается только фон приложения, панель инструментов и строка состояния — либо под цвет фона, либо случайного цвета (как в примере с Play Market).
Причины этому следующие:
Toolbar
, как часть макета панели инструментов. Это дает много плюшек: реакция на прокрутку, анимации и т.д. Но вместе с тем, необходимо использовать тему NoActionBar
. Это влияет на то, что цвета главной темы приложения не подгружаются при холодном старте.@style/Theme.AppCompat.Light.DarkActionBar
к @android:style/Theme.Material.Light.DarkActionBar
лечит эту проблему, но, вероятно, приложения все еще рассчитаны на поддержку версий до Lollipop.Впрочем, есть решение [4].
О подходе Placeholder UI все. Переходим к переводу статьи Элвиса.
В прошлом в Android не рекомендовалось [5] делать Splash Screen в приложениях. Не было смысла заставлять пользователя ждать n секунд, чтобы показать заставку. И несомненно, что никто не запускает приложение ради заставки (об этом ниже).
Предоставление пользователю контента должно быть вашим приоритетом №1
Когда в Material Design [6] появился раздел под названием Launch Screen [7] (Splash Screen), кто-то из команды Android опубликовал пост [8] о том, как сделать Splash Screen правильно.
В этом посте я рассмотрю четыре основных способа реализации Splash Screen на Android:
Когда ваше приложение запускается и оно еще не в памяти, может иметь место задержка между тем, когда пользователь нажал на запуск, и тем, когда у Activity вызвано onCreate()
. Этот, так называемый "холодный старт" — лучшее время, чтобы показать ваш Splash Screen.
Во время "холодного старта" оконный менеджер пытается отрисовать placeholder UI, используя элементы из темы приложения (app theme), такие как windowBackground
. И то, что показывает windowBackground
по-умолчанию (обычно белый или черный фон), вы можете поменять на какой угодно drawable, создав тем самым свой Splash Screen. Этот способ показывает Splash Screen только там, где необходимо, и не замедляет пользователя.
Итак, вам необходимо создать кастомную тему, переопределив android:windowBackground
, заменив использование стандартной темы на вашу перед вызовом super.onCreate
в вашей Activity.
В этом примере, я предполагаю, что главная тема вашего приложения называется AppTheme, но если это не так, просто во всех местах замените AppTheme на имя главной темы вашего приложения.
Вы должны создать новую тему AppTheme.Launcher
. Единственный элемент, который необходимо переопределить — это windowBackground
. В файл styles.xml добавим:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Ваша AppTheme или другие темы и стили здесь -->
<!-- Кастомная AppTheme.Launcher. Устанавливает в качестве фона вашу drawable -->
<style name="AppTheme.Launcher">
<item name="android:windowBackground">@drawable/launch_screen</item>
<!-- для Android 5+ вы можете поменять цвет colorPrimaryDark, чтобы соответствовать цвету windowBackground -->
<!-- <item name="colorPrimaryDark">@android:color/white</item> -->
</style>
</resources>
При этом мы наследуем все остальные атрибуты главной темы AppTheme, используя ее название, как префикс для названия нашей темы Launcher.
Определяем drawable launch_screen
. Хотя вы могли бы использовать простую картинку, но она будет растянута на весь экран. Вместо этого используем XML-файл:
<?xml version="1.0" encoding="utf-8"?>
<!-- Атрибут android:opacity=”opaque” крайне важен для предотвращения черных миганий при переходе к вашей теме -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque">
<!-- Цвет фона, предпочтительно как в главной теме -->
<item android:drawable="@android:color/white"/>
<!-- Лого приложения - 144dp версия иконки приложения -->
<item>
<bitmap
android:src="@drawable/product_logo_144dp"
android:gravity="center"/>
</item>
</layer-list>
Пропишите тему для Splash Screen в файле манифеста в вашей стартовой Activity:
<activity android:theme="@style/AppTheme.Launcher">
Теперь нужно вернуть главную тему в стартовую Activity (если, конечно, мы не хотим, чтобы Splash Screen радовал нас и во время работы приложения)
Самый простой способ сделать это — это вызвать setTheme(R.style.AppTheme)
до super.onCreate()
и setContentView()
:
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
class MainActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// убедитесь, что вызываете до super.onCreate()
setTheme(R.style.AppTheme)
super.onCreate(savedInstanceState)
// …
}
}
Все. Вы можете узнать подробнее об этом подходе здесь [8].
Плюсы:
Минусы:
Я встречал три довольно распространенные жалобы на этот подход:
onCreate()
вашего приложения легким насколько это возможно.Я думаю, что следует избегать таких вещей, как долгий Splash Screen, как ProgressDialog, которые заставляют пользователя просто смотреть на экран и не дают ему выполнить никакое действие.
Если ваше приложение подключается к сети, предположите, что все, что должно пойти не так, пойдет не так. Таким образом вы сможете создавать приложения для миллионов людей, которые все еще используют нестабильные соединения 2G и 3G
Этот способ базируется на первом способе. Он требует отдельной Activity для Splash Screen. Первые два шага пропускаем, они аналогичны первому способу.
Осталось создать Activity для Splash Screen и указать в манифесте для нее тему AppTheme.Launcher
. Теперь отредактируем Activity так, чтобы она перенаправляла на другие страницы. Смотрим пример ниже:
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
class SplashActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// убедитесь, что вызываете до super.onCreate()
setTheme(R.style.AppTheme)
super.onCreate(savedInstanceState)
val user = UserDb.getCurrentUser()
routeToAppropriatePage(user)
finish()
}
private fun routeToAppropriatePage(user: User) {
// пример перенаправления
when {
user == null -> OnboardingActivity.start(this)
user.hasPhoneNumber() -> EditProfileActivity.start(this)
user.hasSubscriptionExpired() -> PaymentPlansActivity.start(this)
else -> HomeActivity.start(this)
}
}
}
Плюсы:
Минусы:
Это старый добрый подход. Надо просто создать отдельную для Splash Screen Activity, которая будет показываться x секунд. Затем открыть подходящую Activity. Используя такой подход, вы получаете больше гибкости, потому что можете добавить анимацию, кастомные view или любые другие элементы, которые вы можете поместить в макет Activity. Вот минимальная реализация такого подхода:
import android.os.Bundle
import android.os.Handler
import android.support.v7.app.AppCompatActivity
class SplashActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_splash)
scheduleSplashScreen()
}
private fun scheduleSplashScreen() {
val splashScreenDuration = getSplashScreenDuration()
Handler().postDelayed(
{
// После Splash Screen перенаправляем на нужную Activity
val user = UserDb.getCurrentUser()
routeToAppropriatePage(user)
finish()
},
splashScreenDuration
)
}
private fun getSplashScreenDuration() = 2000L
private fun routeToAppropriatePage(user: User) {
// пример перенаправления
when {
user == null -> OnboardingActivity.start(this)
user.hasPhoneNumber() -> EditProfileActivity.start(this)
user.hasSubscriptionExpired() -> PaymentPlansActivity.start(this)
else -> HomeActivity.start(this)
}
}
}
Плюсы:
Минусы:
windowBackground
и, затем, просматривая еще Splash Screen до того, как запустится рабочая Activity.Этот подход базируется на третьем способе. Но вместо постоянной задержки, вы запускаете Splash Screen или нет, основываясь на том, первый это запуск или нет. Вот пример, который использует SharedPreferences
:
import android.content.Context
import android.os.Bundle
import android.os.Handler
import android.support.v7.app.AppCompatActivity
class SplashActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_splash)
scheduleSplashScreen()
}
private fun scheduleSplashScreen() {
val splashScreenDuration = getSplashScreenDuration()
Handler().postDelayed(
{
// После Splash Screen перенаправляем на нужную Activity
val user = UserDb.getCurrentUser()
routeToAppropriatePage(user)
finish()
},
splashScreenDuration
)
}
private fun getSplashScreenDuration(): Long {
val sp = getPreferences(Context.MODE_PRIVATE)
val prefKeyFirstLaunch = "pref_first_launch"
return when(sp.getBoolean(prefKeyFirstLaunch, true)) {
true -> {
// Если это первый запуск ставим задержку (> 3 сек) и устанавливаем флаг в false
sp.edit().putBoolean(prefKeyFirstLaunch, false).apply()
5000
}
false -> {
// Если запуск не первый, показываем Splash Screen быстрее (<= 1 сек)
1000
}
}
}
private fun routeToAppropriatePage(user: User) {
// пример перенаправления
when {
user == null -> OnboardingActivity.start(this)
user.hasPhoneNumber() -> EditProfileActivity.start(this)
user.hasSubscriptionExpired() -> PaymentPlansActivity.start(this)
else -> HomeActivity.start(this)
}
}
}
Плюсы:
Минусы:
SharedPreferences
.Это все о Splash Screen. Если я что-то упустил, напишите в комментариях.
Следует заметить, что на Хабре уже была статья [9] (перевод), где речь шла о Splash Screen. Однако затронутый там подход (соответствует второму способу в этой статье), как мы могли убедиться, не самый оптимальный для большинства случаев. Ну и последнее, в Android Oreo якобы добавлено [10] Splash Screen API, что позволит разработчикам легко добавлять Splash Screen в свои приложения, но на данный момент в официальной документации по этому поводу никакой информации нет.
Автор: ziginsider
Источник [11]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/android/271472
Ссылки в тексте:
[1] Elvis Chidera: https://android.jlelse.eu/@elvischidera?source=post_header_lockup
[2] "The (Complete) Android Splash Screen Guide": https://android.jlelse.eu/the-complete-android-splash-screen-guide-c7db82bce565
[3] две концепции: https://material.io/guidelines/patterns/launch-screens.html#
[4] есть решение: https://medium.com/@lucasurbas/placeholder-ui-launch-screen-d85c35552119
[5] не рекомендовалось: https://www.cyrilmottier.com/2012/05/03/splash-screens-are-evil-dont-use-them/
[6] Material Design: https://material.io/guidelines/
[7] Launch Screen: https://material.io/guidelines/patterns/launch-screens.html
[8] пост: https://plus.google.com/+AndroidDevelopers/posts/Z1Wwainpjhd
[9] была статья: https://habrahabr.ru/post/312516/
[10] добавлено: https://www.xda-developers.com/android-oreo-splash-screen-api/
[11] Источник: https://habrahabr.ru/post/345380/?utm_source=habrahabr&utm_medium=rss&utm_campaign=sandbox
Нажмите здесь для печати.