1
1
package fulguris.extensions
2
2
3
- import fulguris.R
4
- import fulguris.utils.getFilteredColor
5
3
import android.annotation.SuppressLint
6
4
import android.content.Context
7
5
import android.content.res.Configuration
8
6
import android.graphics.*
9
7
import android.os.SystemClock
8
+ import android.util.DisplayMetrics
10
9
import android.view.*
11
10
import android.view.inputmethod.InputMethodManager
12
11
import android.widget.ImageView
@@ -19,10 +18,14 @@ import androidx.core.view.isVisible
19
18
import androidx.databinding.BindingAdapter
20
19
import androidx.drawerlayout.widget.DrawerLayout
21
20
import androidx.palette.graphics.Palette
21
+ import androidx.recyclerview.widget.LinearLayoutManager
22
+ import androidx.recyclerview.widget.LinearSmoothScroller
22
23
import androidx.recyclerview.widget.RecyclerView
23
24
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
25
+ import fulguris.R
26
+ import fulguris.utils.getFilteredColor
27
+ import timber.log.Timber
24
28
import java.lang.reflect.Method
25
- import java.util.ArrayList
26
29
27
30
28
31
/* *
@@ -214,6 +217,96 @@ inline fun RecyclerView?.onceOnScrollStateIdle(crossinline runnable: () -> Unit)
214
217
})
215
218
}
216
219
220
+ /* *
221
+ * If needed it triggers a scroll animation to show the specified item in this [RecyclerView].
222
+ * Unfortunately [LinearSmoothScroller] won't let us define the exact duration of the animation.
223
+ *
224
+ * [aPosition] The index of the item you want to scroll to.
225
+ * [aDurationInMs] The rough duration of the scroll animation in milliseconds.
226
+ * [aOvershot] Specify if you want your scroll animation to overshot in order to put the target item roughly in the middle of this view, as opposed than on the edge.
227
+ * [aSnapMode] See [LinearSmoothScroller].
228
+ *
229
+ * Returns true if a scroll was triggered, false otherwise.
230
+ * Improved from: https://stackoverflow.com/a/65489113/3969362
231
+ */
232
+ fun RecyclerView.smoothScrollToPositionEx (aPosition : Int , aDurationInMs : Int = 1000) : Boolean {
233
+
234
+ // First of all, check if we should be scrolling at all
235
+ if (scrollState != RecyclerView .SCROLL_STATE_IDLE ) {
236
+ Timber .d(" Already scrolling, skip it for now" )
237
+ return false
238
+ }
239
+
240
+ // Can't do it without adapter
241
+ adapter?.let { adaptor ->
242
+
243
+ val count = adaptor.itemCount
244
+ var index = aPosition
245
+
246
+
247
+ // adaptor.getItemViewType()
248
+
249
+ val lm = layoutManager as LinearLayoutManager
250
+ // Check if current item is currently visible
251
+ val minIndex = lm.findFirstCompletelyVisibleItemPosition()
252
+ val maxIndex = lm.findLastCompletelyVisibleItemPosition()
253
+
254
+ // Check if our item is already visible
255
+ if ( minIndex <= index && index <= maxIndex) {
256
+ Timber .d(" No need to scroll" )
257
+ return false
258
+ }
259
+
260
+ val scrollDown = (index< minIndex) // && !configPrefs.toolbarsBottom
261
+ val scrollFrom = if (scrollDown) minIndex else maxIndex
262
+
263
+ val scrollRange = if (scrollFrom> index) scrollFrom - index else index - scrollFrom
264
+ Timber .d(" Scroll range: $scrollRange " )
265
+
266
+ // Trigger our scroll animation
267
+ val smoothScroller = object : LinearSmoothScroller (this .context) {
268
+
269
+ // Center on our target item
270
+ // See: https://stackoverflow.com/a/53756296/3969362
271
+ override fun calculateDtToFit (viewStart : Int , viewEnd : Int , boxStart : Int , boxEnd : Int , snapPreference : Int ): Int {
272
+ return (boxStart + (boxEnd - boxStart) / 2 ) - (viewStart + (viewEnd - viewStart) / 2 )
273
+ }
274
+
275
+ // We disabled our speed tweak as it is really tricky to get it right for various use cases
276
+ // Various tabs list variant, number of tabs or screen DPI...
277
+ // The default implementation appears to be a good compromise after all.
278
+ /*
279
+ override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics?): Float {
280
+ // Compute our speed as function of the distance we need to scroll
281
+ var speed = if ((layoutManager as? LinearLayoutManager)?.orientation == LinearLayoutManager.VERTICAL) {
282
+ aDurationInMs.toFloat() / ((computeVerticalScrollRange().toFloat() / count) * scrollRange)
283
+ } else {
284
+ aDurationInMs.toFloat() / ((computeHorizontalScrollRange().toFloat() / count) * scrollRange)
285
+ }
286
+
287
+ Timber.d("Scroll speed: $speed ms/pixel")
288
+
289
+ // Speed is expressed in ms/pixel so in fact min speed is the fastest one and max speed is the slowest one
290
+ val minSpeed = 0.001f // Fastest
291
+ val maxSpeed = 0.05f // Slowest
292
+ // Make sure we don't go too fast or too slow, going too fast can break the LinearSmoothScroller and cause endless animation jitter
293
+ if (speed<minSpeed) speed = minSpeed
294
+ if (speed>maxSpeed) speed = maxSpeed
295
+
296
+ return speed
297
+ }
298
+ */
299
+
300
+ }
301
+ smoothScroller.targetPosition = index
302
+ layoutManager?.startSmoothScroll(smoothScroller)
303
+
304
+ return true
305
+ }
306
+
307
+ return false
308
+ }
309
+
217
310
/* *
218
311
* Reset Swipe Refresh Layout target.
219
312
* This is needed if you are changing the child scrollable view during the lifetime of your layout.
@@ -441,4 +534,5 @@ fun View.onConfigurationChange(aRunnable: () -> Unit) {
441
534
// Could be useful to help understand what's going on when inspecting our views
442
535
id = R .id.onConfigurationChange
443
536
}) }
444
- }
537
+ }
538
+
0 commit comments