-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSlidingHeader.kt
237 lines (221 loc) · 9.28 KB
/
SlidingHeader.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
package [some package]
import android.view.View
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.recyclerview.widget.RecyclerView
import java.lang.Exception
import java.lang.NullPointerException
import kotlin.math.abs
/**
HOW TO USE:
Scenario, to use in Fragment.
Step 1. override the variables constraintSet, maxHeaderTranslateUp, maxHeaderTranslateDown,
and headerView with your desired values
Step 2: override the function initSlidingHeader(...), first thing is to call super.initConstraintSet(...)
inside it
class YourFragmentClass : Fragment(), SlidingHeader {
override val constraintSet: ConstraintSet = ConstraintSet()
override var maxHeaderTranslateUp: Float = 0f
override var maxHeaderTranslateDown: Float = 0f
override var headerView: View? = [SOME VALUE]
.
.
.
override fun initSlidingHeader(hv: View, cl: ConstraintLayout, rv: RecyclerView, sl: RecyclerView.OnScrollListener?) {
/** must be called before everything else */
super.initConstraintSet(hv, cl, rv, sl)
headerView?.let {
it.post {
maxHeaderTranslateDown = it.translationY
val headerTopMargin = it.marginTop
val headerHeight = it.measuredHeight.toFloat()
val headerZValue = it.elevation + it.translationZ
maxHeaderTranslateUp = 0 - (headerTopMargin + headerHeight + headerZValue)
}
}
}
.
.
.
// then in some function or somewhere in your code, your call the initSlidingHeader function you overrided
fun someFunction(){
initSlidingHeader(yourHeaderView, yourConstraintLayout, yourRecyclerView, null)
}
}
*/
/**
* @property SlidingHeader
* Used to make a header that slides up and down as the user scrolls in a [androidx.recyclerview.widget.RecyclerView]
* Only works with a [View] inside a [ConstraintLayout]
*/
interface SlidingHeader {
/**
* @property headerView The view that will slide up and down
* @property constraintSet ConstraintLayout constraint set
* @property maxHeaderTranslateDown How far the [headerView] can slide down relative to its original position when the user scrolls down
* @property maxHeaderTranslateUp How far the [headerView] can slide up relative to its original position when the user scrolls up
*/
val constraintSet: ConstraintSet
var headerView: View?
var maxHeaderTranslateDown: Float
var maxHeaderTranslateUp: Float
/**
* @property initSlidingHeader
*
* The super [initSlidingHeader] method must be called before everything else
*
* @param hv The "Header View", this is the view that scroll out of view when user scrolls up
* @param cl The ConstraintLayout that serves as the "root View", is this view that contains the [hv], it MUST be of type [ConstraintLayout]
* @param rv The RecyclerView on which we shall listen for scroll events
* @param sl If provided, it is associated onScroll listener that will provide scroll events. If not provided then the default will be used
*/
fun initSlidingHeader(hv: View, cl: ConstraintLayout, rv: RecyclerView, sl: RecyclerView.OnScrollListener?) {
if(testViewIsConstraintLayout(cl)) {
headerView = hv
hv.post {
setScrollListener(rv, sl)
constraintSet.clone(cl)
}
}
}
/**
* @property testViewIsConstraintLayout
* Return true if [headerView]'s parent is of type [ConstraintLayout]
*/
private fun testViewIsConstraintLayout(v: View): Boolean {
if(v !is ConstraintLayout){
throw Exception("Parent of view must be of type ConstraintLayout, current parent is ${v::class.simpleName}")
}
return true
}
/**
* @property translateHeaderUp
* Translate the [headerView] up by distance relative to [speed]
*
* @param speed The speed at which to translate [headerView] by
*/
private fun translateHeaderUp(speed: Float = 1f) {
headerView?.let {
val viewParent = it.parent
if (testViewIsConstraintLayout(viewParent as View)) {
val translateAmount: Float = it.translationY - speed
translateHeader(
viewParent = viewParent as ConstraintLayout,
translateAmount = translateAmount,
up = true
)
}
} ?: throw Exception(NullPointerException("headerView is null, did you call super.initConstraintSet()?"))
}
/**
* @property translateHeaderDown
* Translate the [headerView] down by distance relative to [speed]
*
* @param speed The speed at which to translate [headerView] by
*/
private fun translateHeaderDown(speed: Float = 1f) {
headerView?.let {
val viewParent = it.parent
if (testViewIsConstraintLayout(viewParent as View)) {
val translateAmount: Float = it.translationY + speed
translateHeader(
viewParent = viewParent as ConstraintLayout,
translateAmount = translateAmount,
up = false
)
}
} ?: throw Exception(NullPointerException("headerView is null, did you call super?"))
}
/**
* @property translateHeader
* Translate the [headerView] down or up by distance relative [translateAmount] relative to the [headerView]'s original position
*
* @param viewParent The view that contains [headerView]
* @param translateAmount The distance to move [headerView]
* @param up If true then translate upward, downward if false
*/
private fun translateHeader(viewParent: ConstraintLayout, translateAmount: Float, up: Boolean) {
headerView?.let {
if ((testViewIsConstraintLayout(viewParent))) {
if (up) {
if (translateAmount <= maxHeaderTranslateUp) {
applyTranslation(
viewId = it.id,
viewParent = viewParent,
translateAmount = maxHeaderTranslateUp
)
return
}
} else {
if (translateAmount >= maxHeaderTranslateDown) {
applyTranslation(
viewId = it.id,
viewParent = viewParent,
translateAmount = maxHeaderTranslateDown
)
return
}
}
applyTranslation(
viewId = it.id,
viewParent = viewParent,
translateAmount = translateAmount
)
}
} ?: throw Exception(NullPointerException("headerView is null, did you call super.initConstraintSet()?"))
}
/**
* @property applyTranslation
* Apply translation to the constraint layout
*/
private fun applyTranslation(viewId: Int, viewParent: ConstraintLayout, translateAmount: Float) {
val constraintSetTemp = ConstraintSet()
constraintSetTemp.clone(constraintSet)
constraintSetTemp.setTranslationY(viewId, translateAmount)
constraintSetTemp.applyTo(viewParent)
}
/**
* @property setScrollListener
* Sets the on scroll listener to the associated [RecyclerView]
*
* @param rv The RecyclerView to set the [RecyclerView.OnScrollListener] to
* @param sl The onScrollListener to attach to [rv], if null then the default is attached
*/
private fun setScrollListener(rv: RecyclerView, sl: RecyclerView.OnScrollListener?) {
sl?.let {
rv.addOnScrollListener((it))
} ?: rv.addOnScrollListener( object : RecyclerView.OnScrollListener() {
/**
* Callback method to be invoked when RecyclerView's scroll state changes.
*
* @param recyclerView The RecyclerView whose scroll state has changed.
* @param newState The updated scroll state. One of [.SCROLL_STATE_IDLE],
* [.SCROLL_STATE_DRAGGING] or [.SCROLL_STATE_SETTLING].
*/
override fun onScrollStateChanged(
recyclerView: RecyclerView,
newState: Int
) {
}
/**
* Callback method to be invoked when the RecyclerView has been scrolled. This will be
* called after the scroll has completed.
*
*
* This callback will also be called if visible item range changes after a layout
* calculation. In that case, dx and dy will be 0.
*
* @param recyclerView The RecyclerView which scrolled.
* @param dx The amount of horizontal scroll.
* @param dy The amount of vertical scroll.
*/
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
val speed = abs(dy).toFloat()
when {
dy < 0 -> translateHeaderDown(speed)
dy > 0 -> translateHeaderUp(speed)
}
}
})
}
}