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
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
世界の人気アプリ100個を分析して見えたペイウォール設計の心得
akihiro_kokubo
PRO
72
40k
Rails Girls Zürich Keynote
gr2m
96
14k
How to Create Impact in a Changing Tech Landscape [PerfNow 2023]
tammyeverts
55
3.4k
Principles of Awesome APIs and How to Build Them.
keavy
128
18k
Music & Morning Musume
bryan
47
7.2k
Agile that works and the tools we love
rasmusluckow
331
22k
Chasing Engaging Ingredients in Design
codingconduct
0
230
"I'm Feeling Lucky" - Building Great Search Experiences for Today's Users (#IAC19)
danielanewman
230
23k
How to Align SEO within the Product Triangle To Get Buy-In & Support - #RIMC
aleyda
2
1.6k
Building Better People: How to give real-time feedback that sticks.
wjessup
370
20k
Helping Users Find Their Own Way: Creating Modern Search Experiences
danielanewman
31
3.2k
The Curious Case for Waylosing
cassininazir
1
400
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]