Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Давайте обращать внимание на детали? (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
170
Improving Video Playback with ExoPlayer
nonews
0
1.2k
Gradle build: The time is now
nonews
2
960
Where and how to run UI tests (Droidcon London, 2021)
nonews
0
690
Where and how to run UI tests (Droidcon Lisbon & Android Makers, Paris)
nonews
0
360
RxJava in a nutshell
nonews
0
370
Где и как прогонять UI тесты
nonews
0
430
Featured
See All Featured
Groundhog Day: Seeking Process in Gaming for Health
codingconduct
0
220
The SEO identity crisis: Don't let AI make you average
varn
0
500
Hiding What from Whom? A Critical Review of the History of Programming languages for Music
tomoyanonymous
2
870
Claude Code どこまでも/ Claude Code Everywhere
nwiizo
65
56k
Building Adaptive Systems
keathley
44
3.1k
Bioeconomy Workshop: Dr. Julius Ecuru, Opportunities for a Bioeconomy in West Africa
akademiya2063
PRO
1
150
Leveraging Curiosity to Care for An Aging Population
cassininazir
1
270
Design of three-dimensional binary manipulators for pick-and-place task avoiding obstacles (IECON2024)
konakalab
0
470
Exploring the relationship between traditional SERPs and Gen AI search
raygrieselhuber
PRO
2
4k
Lightning talk: Run Django tests with GitHub Actions
sabderemane
0
200
Believing is Seeing
oripsolob
1
150
Lightning Talk: Beautiful Slides for Beginners
inesmontani
PRO
2
580
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]