Хабрахабр

[Перевод] Самая правильная имлементация сплеш-скрина

Вообще, это перевод моей статьи с medium.com, оригинал лежит тут, подписывайтесь, ставьте лайки, как говорится.

В целом, я видел уже достаточно много статеек на тему как же правильно запилить сплеш на 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, установит наше приложение и сразу же решит расшарить какой-нибудь контент через этот апп, он попадет на экран авторизации, но после нее он не потеряет контент который он хочет расшарить. Знаю по себе, приложения то я устанавливаю, но вот захожу в них не сразу, так что такой паттерн поведения юзера вполне имеет место быть.

Тут ссылка на проект с примером из этой статьи.

Теги
Показать больше

Похожие статьи

Кнопка «Наверх»
Закрыть