Вообще, это перевод моей статьи с medium.com, оригинал лежит тут, подписывайтесь, ставьте лайки, как говорится.
В целом, я видел уже достаточно много статеек на тему как же правильно запилить сплеш на Android, однако все они имеют одну проблему – разрабы забывают о том, что нативные приложения могут вовсе и не иметь единой точки входа – с этим я столкнулся еще в 2009 когда только начинал свой путь разработчика. Представьте себе что вы разрабатываете какой-нибудь клиент для какой-нибудь социалки и кроме
Начнем с того, что создадим базовую тему апа с кастомным `android:windowBackground`, в котором будет лежать картинка для нашего сплеша. Базовую тему следует применить ко всем точкам входа как минимум, я же обычно вообще все активити аппа делаю с ней:
А еще я добавил
У демо-проекта будет 3 активити: главный экран, авторизация и экран через который можно пошарить что-нибудь. Все они, за исключением авторизации, будут наследоваться от
Мы просто запускает авторизацию, ждем результата и пересоздаем себя в случае если авторизация прошла успешно. Вообще мне не очень нравится
Обычно я пользуют
Кроме всякой красивой анимации актитвит проверяет ввод юзера, показывает ошибки и собственно сохраняет логин и пароль. Но, если юзер нажмет «Cancel» или кнопку «Назад», активити завершится с резалт-кодом
Как видите, вообще ничего особенного ни в
И что же сейчас произошло вообще?
Мы объявили что все наши активити будут рендерится с темой, которая выглядит как сплеш-скрин и все они будут унаследованы от
Таким образом, если пользователь зайдет в Google Play, установит наше приложение и сразу же решит расшарить какой-нибудь контент через этот апп, он попадет на экран авторизации, но после нее он не потеряет контент который он хочет расшарить. Знаю по себе, приложения то я устанавливаю, но вот захожу в них не сразу, так что такой паттерн поведения юзера вполне имеет место быть.
Тут ссылка на проект с примером из этой статьи.
В целом, я видел уже достаточно много статеек на тему как же правильно запилить сплеш на Android, однако все они имеют одну проблему – разрабы забывают о том, что нативные приложения могут вовсе и не иметь единой точки входа – с этим я столкнулся еще в 2009 когда только начинал свой путь разработчика. Представьте себе что вы разрабатываете какой-нибудь клиент для какой-нибудь социалки и кроме
android.intent.action.MAIN
в вашем манифесте может быть еще с десяток Activity
, через которые можно запустить апп – шаринг картинок, текста, нотификации. И по-хорошему везде нужен сплеш!Тема
Начнем с того, что создадим базовую тему апа с кастомным `android:windowBackground`, в котором будет лежать картинка для нашего сплеша. Базовую тему следует применить ко всем точкам входа как минимум, я же обычно вообще все активити аппа делаю с ней:
<resources>
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="AppTheme.SplashScreen" parent="AppTheme">
<item name="android:windowBackground">@drawable/splash</item>
<item name="android:windowTranslucentStatus">true</item>
<item name="android:windowTranslucentNavigation">true</item>
</style>
...
</resources>
А еще я добавил
android:windowTranslucentStatus
и android:windowTranslucentNavigation
чтоб сплеш выглядел еще круче! Тег android:windowBackground
содержит картинку сплеша которая еще будет фоном и на экране авторизации, я ее стащил с unsplash.com так что упомяну автора kazuend.Активити
У демо-проекта будет 3 активити: главный экран, авторизация и экран через который можно пошарить что-нибудь. Все они, за исключением авторизации, будут наследоваться от
SplashedActivity
для проверки статуса авторизации.private const val ACTIVITY_AUTH = 1000
abstract class SplashedActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
if (!isAuthenticated()) {
startActivityForResult(Intent(this, AuthActivity::class.java), ACTIVITY_AUTH)
}
setTheme(R.style.AppTheme_Base)
super.onCreate(savedInstanceState)
}
private fun isAuthenticated(): Boolean {
return getUser() != null
}
private fun onAuthenticatedCallback(resultCode: Int, data: Intent?) {
when (resultCode) {
Activity.RESULT_CANCELED -> finish()
Activity.RESULT_OK -> recreate()
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
ACTIVITY_AUTH -> onAuthenticatedCallback(resultCode, data)
}
super.onActivityResult(requestCode, resultCode, data)
}
}
Мы просто запускает авторизацию, ждем результата и пересоздаем себя в случае если авторизация прошла успешно. Вообще мне не очень нравится
recreate()
, однако многие разрабы делают инициализацию UI в onCreate()
и после recreate()
она будет вызвана еще раз не потеряв при этом Intent
которым Activity
была запущена.Авторизация
Обычно я пользуют
AccountManager
'ом, но слишком много бойлерплейта нужно чтоб его заюзать, так что хранить все будет в SharedPreferences
.class AuthActivity : AppCompatActivity() {
private val authCardView by lazy { findViewById<CardView>(R.id.authCardView) }
private val okButton by lazy { findViewById<Button>(R.id.okButton) }
private val cancelButton by lazy { findViewById<Button>(R.id.cancelButton) }
private val loginEditText by lazy { findViewById<EditText>(R.id.loginEditText) }
private val passwordEditText by lazy { findViewById<EditText>(R.id.passwordEditText) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_auth)
authCardView.animate()
.setDuration(500L)
.setInterpolator(AccelerateDecelerateInterpolator())
.alpha(1F)
.setListener(object : Animator.AnimatorListener {
override fun onAnimationRepeat(p0: Animator?) {
}
override fun onAnimationCancel(p0: Animator?) {
}
override fun onAnimationStart(p0: Animator?) {
authCardView.alpha = 0F
authCardView.visibility = View.VISIBLE
}
override fun onAnimationEnd(p0: Animator?) {
authCardView.visibility = View.VISIBLE
}
})
okButton.setOnClickListener {
performInputChecksAndSaveUser { login, password ->
saveUser(User(login, password))
setResult(Activity.RESULT_OK)
finish()
}
}
cancelButton.setOnClickListener {
finish()
}
}
private fun performInputChecksAndSaveUser(successCallback: (String, String) -> Unit) {
if (loginEditText.text.isBlank()) {
loginEditText.error = getText(R.string.errorEmptyLogin)
}
if (passwordEditText.text.isBlank()) {
passwordEditText.error = getText(R.string.errorEmptyPassword)
}
if (loginEditText.text.isNotBlank() && passwordEditText.text.isNotBlank()) {
successCallback.invoke(loginEditText.text.toString(), passwordEditText.text.toString())
}
}
}
Кроме всякой красивой анимации актитвит проверяет ввод юзера, показывает ошибки и собственно сохраняет логин и пароль. Но, если юзер нажмет «Cancel» или кнопку «Назад», активити завершится с резалт-кодом
Activity.RESULT_CANCELLED
, ActivityManager
вернется вверх по стеку и завершит еще и активити которое вызвало авторизацию. Идея этого в том что становится неважно кто вызвал авторизацию – как только любая актитвити, которой нужна авторизация, будет запущена, она проверит, есть ли пользовательские данные в наличии и, если их нет, просто увидит их там же после успешного резульата процесса авторизации.Шаринг
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.nixan.splashscreenexample">
...
<activity android:name=".ShareActivity">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
...
</manifest>
class ShareActivity : SplashedActivity() {
private val helloText by lazy { findViewById<TextView>(R.id.helloText) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
getUser()?.let {
helloText.text = "${it.login}\n${intent.getStringExtra(Intent.EXTRA_TEXT)}"
}
}
}
Как видите, вообще ничего особенного ни в
AndroidManifest.xml
, ни в классе активити – только обычная обработка Intent
. Заслуга в recreate()
– из-за него как раз мы пишем все активити так же как и всегда.Заключение
И что же сейчас произошло вообще?
ActivityManager
обычно требуется некоторое время от момента когда пользователь кликнет на иконку аппа, до того момента как в целевом Activity
будет вызван каллбек onCreate()
. Для того чтоб замаскировать такой временной лаг, система берет android:theme
у активити и рендерит ее без какого-либо контента и только потом контроль передается написанному классу и начинается работа приложения.Мы объявили что все наши активити будут рендерится с темой, которая выглядит как сплеш-скрин и все они будут унаследованы от
SplashedActivity
для выполнения проверок авторизации и запуска каких-либо дополнительных экранов инициализации – в нашем случае это только AuthActivity
, в котором авторизуется юзер. Еще SplashedActivity
будет обрабатывать результат этой авторизации и решать показывать дальше контент аппа пользователю или закрываться.Таким образом, если пользователь зайдет в Google Play, установит наше приложение и сразу же решит расшарить какой-нибудь контент через этот апп, он попадет на экран авторизации, но после нее он не потеряет контент который он хочет расшарить. Знаю по себе, приложения то я устанавливаю, но вот захожу в них не сразу, так что такой паттерн поведения юзера вполне имеет место быть.
Тут ссылка на проект с примером из этой статьи.
juztoss
Поясните зачем, пожалуйста?
По моему нет, абсолютно, никакой необходимости показывать сплеш скрин между нажатием кнопки «пошарить» и выбором пользовтаеля которому пошарить.
Только собъёт юзера с толку, пока будет рассматривать сплеш скрин уже забудет чего хотел.
nixan Автор
Скажи это всем тем кто делает в нем всякую инициализацию синглтонов, так то вообще сплеши не нужны
barbanel
Это явно недочеты проектирования, в данном случае они сами себе злобные буратины.
nixan Автор
Спору нет, я и сам более консервативных взглядов придерживаюсь – раньше же все знали что сплеши в андроиде антипаттерн, но время меняется.
Навскидку помню случай, в QIWI работал, когда надо было уже авторизованным юзерам в зависимости от настроек на серваке врубать ту или иную тему оформления – в итоге привет сплеши – там была загрузка данных с сервачеллы