Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Давайте обращать внимание на детали? (Gorod.it,...
Search
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
Alexey Bykov
November 10, 2018
130
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Давайте обращать внимание на детали? (Gorod.it, Томск, 10.11.19)
Alexey Bykov
November 10, 2018
More Decks by Alexey Bykov
See All by Alexey Bykov
How we boosted ExoPlayer performance by 30%
nonews
0
190
Improving Video Playback with ExoPlayer
nonews
0
1.2k
Gradle build: The time is now
nonews
2
970
Where and how to run UI tests (Droidcon London, 2021)
nonews
0
710
Where and how to run UI tests (Droidcon Lisbon & Android Makers, Paris)
nonews
0
370
RxJava in a nutshell
nonews
0
380
Где и как прогонять UI тесты
nonews
0
430
Featured
See All Featured
SERP Conf. Vienna - Web Accessibility: Optimizing for Inclusivity and SEO
sarafernandez
2
1.6k
Exploring anti-patterns in Rails
aemeredith
4
520
16th Malabo Montpellier Forum Presentation
akademiya2063
PRO
0
390
Unsuck your backbone
ammeep
672
58k
技術選定の審美眼(2025年版) / Understanding the Spiral of Technologies 2025 edition
twada
PRO
120
120k
Building Applications with DynamoDB
mza
96
7.2k
RailsConf 2023
tenderlove
30
1.6k
Connecting the Dots Between Site Speed, User Experience & Your Business [WebExpo 2025]
tammyeverts
11
1k
Paper Plane
katiecoart
PRO
4
53k
Utilizing Notion as your number one productivity tool
mfonobong
4
600
What does AI have to do with Human Rights?
axbom
PRO
1
2.4k
Abbi's Birthday
coloredviolet
4
10k
Transcript
Давайте обращать внимание на детали? Алексей Быков 10.11.18
๏ Android Developer at KasperskyLab ๏ Mosdroid Speaker ๏ Android
Academy MSK Co-orgaznizer ๏ Passionate about mobile UX About 2
!3
!4
!5 Don’t do that
Старт приложения 6
Старт приложения 7 Cold start Hot start Warm start
Cold start 8
Cold start 9 Первый запуск приложения Создание процесса приложения Пользователь
видит белое окно
Hot start 10
Hot start 11 Приложение уже запускалось Пересоздание процесса (Возможно) Пользователь
всё ещё видит белое окно Task с Activities жив (Возможно)
Warm start 12
Warm start 13 Приложение уже запускалось Процесс приложения жив Пользователь
не видит белое окно
Проблема: Белое окно 14
Белое окно: А точно ли проблема? 15
Material Design 16
Material Design 17 Placeholder Branded
Placeholder 18 Привязан к пользовательскому интерфейсу Минимализм Не надоедает пользователю
Грузится быстрее Нет «вау» эффекта
Branded 19 Грузится дольше Подходит для крупных компаний Больше возможности
с анимациями Может надоесть пользователю
Реализация 20
Manifest 21 <activity android:name=".activities.SplashActivity" android:theme="@style/SplashScreen"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category
android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>
22 <activity android:name=".activities.SplashActivity" android:theme="@style/SplashScreen"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER"
/> </intent-filter> </activity> Manifest
23 Style <style name="SplashScreen" parent="Theme.AppCompat.Light.NoActionBar"> <item name="android:windowBackground">@drawable/background_splash</item> <item name="colorAccent">@color/light_background</item> <item
name="windowActionBar">false</item> <item name="windowNoTitle">true</item> <item name="android:windowTranslucentStatus">true</item> </style>
24 Style <style name="SplashScreen" parent="Theme.AppCompat.Light.NoActionBar"> <item name="android:windowBackground">@drawable/background_splash</item> <item name="colorAccent">@color/light_background</item> <item
name="windowActionBar">false</item> <item name="windowNoTitle">true</item> <item name="android:windowTranslucentStatus">true</item> </style>
25 Style: API 26+ <style name="Splashscreen" parent="Theme.AppCompat.NoActionBar"> <item name="android:windowSplashscreenContent">@drawable/splashscreen</item> </style>
26 Drawable (Branded) <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item> <color android:color="@color/colorAccent"/> </item> <item
android:drawable="@drawable/car3" android:gravity="center_horizontal|center_vertical"/> </layer-list>
27 Drawable (Branded) <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item> <color android:color="@color/colorAccent"/> </item> <item
android:drawable="@drawable/car3" android:gravity="center_horizontal|center_vertical"/> </layer-list>
28 Drawable (Placeholder) <?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity=«opaque" >
<item> <shape> <solid android:color="@color/grey"/> </shape> </item> <item android:height="?actionBarSize" android:gravity="top"> <shape android:shape="rectangle"> <solid android:color="?colorPrimary"/> </shape> </item> </layer-list>
29 Drawable (Placeholder) <?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity=«opaque" >
<item> <shape> <solid android:color="@color/grey"/> </shape> </item> <item android:height="?actionBarSize" android:gravity="top"> <shape android:shape="rectangle"> <solid android:color="?colorPrimary"/> </shape> </item> </layer-list>
Как делать не надо 30
Как делать не надо 31 Http-запросы в SplashScreen Использовать таймеры
Использовать текст в Branded Screen
!32
Авторизация 33
Don’t do that
OnKeyboardStateChangeListener 35
OnKeyboardStateChangeListener 36 ¯ \ _ (ツ) _ / ¯
Manifest 37 <activity android:name=".MainActivity" android:windowSoftInputMode="adjustResize"/>
Manifest 38 <activity android:name=".MainActivity" android:windowSoftInputMode="adjustResize"/>
ViewGroup 39 fun ViewGroup.onKeyboardStateChanged(activity: Activity, listener: (Boolean) -> Unit) {
viewTreeObserver.addOnGlobalLayoutListener { val screenSize = Point() activity.windowManager.defaultDisplay.getSize(screenSize) val rect = Rect() getWindowVisibleDisplayFrame(rect) val diff = screenSize.y - rect.bottom listener.invoke(diff != 0) } }
ViewGroup 40 fun ViewGroup.onKeyboardStateChanged(activity: Activity, listener: (Boolean) -> Unit) {
viewTreeObserver.addOnGlobalLayoutListener { val screenSize = Point() activity.windowManager.defaultDisplay.getSize(screenSize) val rect = Rect() getWindowVisibleDisplayFrame(rect) val diff = screenSize.y - rect.bottom listener.invoke(diff != 0) } }
ViewGroup 41 fun ViewGroup.onKeyboardStateChanged(activity: Activity, listener: (Boolean) -> Unit) {
viewTreeObserver.addOnGlobalLayoutListener { val screenSize = Point() activity.windowManager.defaultDisplay.getSize(screenSize) val rect = Rect() getWindowVisibleDisplayFrame(rect) val diff = screenSize.y - rect.bottom listener.invoke(diff != 0) } }
ViewGroup 42 fun ViewGroup.onKeyboardStateChanged(activity: Activity, listener: (Boolean) -> Unit) {
viewTreeObserver.addOnGlobalLayoutListener { val screenSize = Point() activity.windowManager.defaultDisplay.getSize(screenSize) val rect = Rect() getWindowVisibleDisplayFrame(rect) val diff = screenSize.y - rect.bottom listener.invoke(diff != 0) } }
Fragment/Activity 43 ltRoot.onKeyboardStateChanged(activity, { isShown -> ivHeader.show(!isShown) })
!44
!45 Don’t do that
EditText 46 <EditText android:inputType="textEmailAddress" android:id="@+id/et_email" android:layout_width="match_parent" android:layout_height="wrap_content" />
EditText 47 <EditText android:inputType="textEmailAddress" android:id="@+id/et_email" android:layout_width="match_parent" android:layout_height="wrap_content" />
!48
Обработка ошибок 49
WTF? 50 fun handleError(e: Throwable) { viewState.showError("Какие-то проблемы..") }
Какие-то проблемы..
52
53 Don’t do that
54
55 Don’t do that
Try again later — no more! 56 https://bit.ly/2ALQF8D
Валидация 57
Валидация 58 Отображайте ошибки в TextInputLayout Убирайте ошибки в afterTextChanged
!59
NavigationView 60
Don’t do that
Start Activity 62 handler.postDelayed({ startActivity(Intent(this, TestActivity::class.java)) overridePendingTransition(R.anim.move_right_in_activity, R.anim.move_left_out_activity) }, 300)
Start Activity 63 override fun finish() { super.finish() overridePendingTransition(R.anim.move_left_in_activity, R.anim.move_right_out_activity)
}
!64
!65
!66
RecyclerView 67
Don’t do that
Scrollbar 69 <android.support.v7.widget.RecyclerView android:scrollbars="vertical" android:id="@+id/rv_test" android:layout_width="match_parent" android:layout_height="wrap_content"/>
Scrollbar 70 <android.support.v7.widget.RecyclerView android:scrollbars="vertical" android:id="@+id/rv_test" android:layout_width="match_parent" android:layout_height="wrap_content"/>
Fab animation 71 class RecyclerViewWithFloatingActionButton : RecyclerView { var mFloatingActionButton:
FloatingActionButton? = null constructor(context: Context) : super(context) {} constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {} constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) {} override fun onScrolled(dx: Int, dy: Int) { super.onScrolled(dx, dy) if (dy > 0) { // Scrolling up mFloatingActionButton?.hide() } else { // Scrolling down mFloatingActionButton?.show() } } }
Fab animation 72 class RecyclerViewWithFloatingActionButton : RecyclerView { var mFloatingActionButton:
FloatingActionButton? = null constructor(context: Context) : super(context) {} constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {} constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) {} override fun onScrolled(dx: Int, dy: Int) { super.onScrolled(dx, dy) if (dy > 0) { // Scrolling up mFloatingActionButton?.hide() } else { // Scrolling down mFloatingActionButton?.show() } } }
Fab animation 73 class RecyclerViewWithFloatingActionButton : RecyclerView { var mFloatingActionButton:
FloatingActionButton? = null constructor(context: Context) : super(context) {} constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {} constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) {} override fun onScrolled(dx: Int, dy: Int) { super.onScrolled(dx, dy) if (dy > 0) { // Scrolling up mFloatingActionButton?.hide() } else { // Scrolling down mFloatingActionButton?.show() } } }
!74
!75 Don’t do that
Debounce Click 76 <ru.alexbykov.sampleapplication.RecyclerViewWithFloatingActionButton android:splitMotionEvents="false" android:scrollbars="vertical" android:id="@+id/rv_test" android:layout_width="match_parent" android:layout_height="wrap_content"/>
Debounce Click 77 <ru.alexbykov.sampleapplication.RecyclerViewWithFloatingActionButton android:splitMotionEvents="false" android:scrollbars="vertical" android:id="@+id/rv_test" android:layout_width="match_parent" android:layout_height="wrap_content"/>
Debounce Click 78 abstract class DebounceOnClickListener() : View.OnClickListener { companion
object { const private val CLICK_DELAY = 500L private var sIsClickEnabled = true } private val mClickLockRunnable = Runnable { sIsClickEnabled = true } override fun onClick(view: View?) { if (sIsClickEnabled) { sIsClickEnabled = false onDelayedClick(view) view?.postDelayed(mClickLockRunnable, CLICK_DELAY) } } abstract fun onDelayedClick(view: View?) }
Extension 79 fun View.setDebounceOnClickListener(listener: (view: View?) -> Unit) { setOnClickListener(object
: DebounceClickListener() { override fun onDelayedClick(view: View?) { listener.invoke(view) } }) }
Adapter 80 itemView.setDebounceOnClickListener { mListener?.invoke(id) }
Пагинация 81
Пагинация 82 Делайте подгрузку с отступом Показывайте ошибку в конце
списка
!83
!84
NoPaginate 85
NoPaginate 86 val noPaginate = NoPaginate.with(recyclerView) .setOnLoadMoreListener { //http or
db request } .build()
NoPaginate 87 val noPaginate = NoPaginate.with(recyclerView) .setOnLoadMoreListener { //http or
db request } .setLoadingTriggerThreshold(5) //0 by default .setCustomErrorItem(CustomErrorItem()) .setCustomLoadingItem(CustomLoadingItem()) .build()
NoPaginate 88 implementation 'ru.alexbykov:nopaginate:0.9.9' https://github.com/NoNews/NoPaginate
!89
Animation 90 TransitionManager.beginDelayedTransition(rootView)
!91
Давайте обращать внимание на детали? 92
Давайте обращать внимание на детали! 93
Let’s talk @NoNews @NoNews
[email protected]