Skip to content

Commit ed6926f

Browse files
zielinskimzfacebook-github-bot
authored andcommitted
Convert CustomSpeedLinearSnapHelper to Kotlin
Summary: As per title Reviewed By: astreet Differential Revision: D73426165 fbshipit-source-id: 391d509c2c1639d707c6b56ef5e7da2b3146278f
1 parent a453494 commit ed6926f

2 files changed

Lines changed: 197 additions & 187 deletions

File tree

litho-widget/src/main/java/com/facebook/litho/widget/CustomSpeedLinearSnapHelper.java

Lines changed: 0 additions & 187 deletions
This file was deleted.
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.facebook.litho.widget
18+
19+
import android.view.View
20+
import androidx.recyclerview.widget.LinearLayoutManager
21+
import androidx.recyclerview.widget.LinearSnapHelper
22+
import androidx.recyclerview.widget.OrientationHelper
23+
import androidx.recyclerview.widget.RecyclerView
24+
import kotlin.math.abs
25+
import kotlin.math.max
26+
import kotlin.math.min
27+
28+
/**
29+
* Implementation of the [LinearSnapHelper] supporting hscroll custom target child view
30+
*
31+
* The implementation will snap the center of the custom target child view to the center of the
32+
* attached [RecyclerView]. If you intend to change this behavior then override
33+
* [SnapHelper.findTargetSnapPosition].
34+
*/
35+
internal class CustomSpeedLinearSnapHelper
36+
@JvmOverloads
37+
constructor(private val deltaJumpThreshold: Int, private val isStrictMode: Boolean = false) :
38+
LinearSnapHelper() {
39+
40+
override fun findTargetSnapPosition(
41+
layoutManager: RecyclerView.LayoutManager,
42+
velocityX: Int,
43+
velocityY: Int
44+
): Int {
45+
if (layoutManager !is RecyclerView.SmoothScroller.ScrollVectorProvider) {
46+
return RecyclerView.NO_POSITION
47+
}
48+
49+
val itemCount = layoutManager.itemCount
50+
if (itemCount == 0) {
51+
return RecyclerView.NO_POSITION
52+
}
53+
54+
val currentView = findSnapView(layoutManager) ?: return RecyclerView.NO_POSITION
55+
val currentPosition =
56+
if (isStrictMode) {
57+
if (velocityX > 0 || velocityY > 0) {
58+
(layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
59+
} else {
60+
(layoutManager as LinearLayoutManager).findLastVisibleItemPosition()
61+
}
62+
} else {
63+
layoutManager.getPosition(currentView)
64+
}
65+
66+
if (currentPosition == RecyclerView.NO_POSITION) {
67+
return RecyclerView.NO_POSITION
68+
}
69+
70+
val vectorProvider = layoutManager as RecyclerView.SmoothScroller.ScrollVectorProvider
71+
// deltaJumps sign comes from the velocity which may not match the order of children in
72+
// the LayoutManager. To overcome this, we ask for a vector from the LayoutManager to
73+
// get the direction.
74+
val vectorForEnd =
75+
vectorProvider.computeScrollVectorForPosition(itemCount - 1)
76+
?: // cannot get a vector for the given position.
77+
return RecyclerView.NO_POSITION
78+
79+
var vDeltaJump: Int
80+
var hDeltaJump: Int
81+
if (layoutManager.canScrollHorizontally()) {
82+
hDeltaJump =
83+
estimateNextPositionDiffForFling(
84+
layoutManager, OrientationHelper.createHorizontalHelper(layoutManager), velocityX, 0)
85+
// set a threshold to the jump
86+
if (hDeltaJump > deltaJumpThreshold) {
87+
hDeltaJump = deltaJumpThreshold
88+
}
89+
if (hDeltaJump < -deltaJumpThreshold) {
90+
hDeltaJump = -deltaJumpThreshold
91+
}
92+
if (vectorForEnd.x < 0) {
93+
hDeltaJump = -hDeltaJump
94+
}
95+
} else {
96+
hDeltaJump = 0
97+
}
98+
if (layoutManager.canScrollVertically()) {
99+
vDeltaJump =
100+
estimateNextPositionDiffForFling(
101+
layoutManager, OrientationHelper.createVerticalHelper(layoutManager), 0, velocityY)
102+
if (vectorForEnd.y < 0) {
103+
vDeltaJump = -vDeltaJump
104+
}
105+
} else {
106+
vDeltaJump = 0
107+
}
108+
109+
val deltaJump = if (layoutManager.canScrollVertically()) vDeltaJump else hDeltaJump
110+
if (deltaJump == 0) {
111+
return RecyclerView.NO_POSITION
112+
}
113+
114+
var targetPos = currentPosition + deltaJump
115+
if (targetPos < 0) {
116+
targetPos = 0
117+
}
118+
if (targetPos >= itemCount) {
119+
targetPos = itemCount - 1
120+
}
121+
return targetPos
122+
}
123+
124+
private fun estimateNextPositionDiffForFling(
125+
layoutManager: RecyclerView.LayoutManager,
126+
helper: OrientationHelper,
127+
velocityX: Int,
128+
velocityY: Int
129+
): Int {
130+
val distances = calculateScrollDistance(velocityX, velocityY)
131+
val distancePerChild = computeDistancePerChild(layoutManager, helper)
132+
if (distancePerChild <= 0) {
133+
return 0
134+
}
135+
val distance =
136+
if (abs(distances[0].toDouble()) > abs(distances[1].toDouble())) distances[0]
137+
else distances[1]
138+
return Math.round(distance / distancePerChild)
139+
}
140+
141+
companion object {
142+
private const val INVALID_DISTANCE = 1f
143+
144+
private fun computeDistancePerChild(
145+
layoutManager: RecyclerView.LayoutManager,
146+
helper: OrientationHelper
147+
): Float {
148+
var minPosView: View? = null
149+
var maxPosView: View? = null
150+
var minPos = Int.MAX_VALUE
151+
var maxPos = Int.MIN_VALUE
152+
val childCount = layoutManager.childCount
153+
if (childCount == 0) {
154+
return INVALID_DISTANCE
155+
}
156+
157+
for (i in 0 until childCount) {
158+
val child = layoutManager.getChildAt(i)
159+
val pos =
160+
if (child != null) {
161+
layoutManager.getPosition(child)
162+
} else {
163+
RecyclerView.NO_POSITION
164+
}
165+
if (pos == RecyclerView.NO_POSITION) {
166+
continue
167+
}
168+
if (pos < minPos) {
169+
minPos = pos
170+
minPosView = child
171+
}
172+
if (pos > maxPos) {
173+
maxPos = pos
174+
maxPosView = child
175+
}
176+
}
177+
if (minPosView == null || maxPosView == null) {
178+
return INVALID_DISTANCE
179+
}
180+
val start =
181+
min(
182+
helper.getDecoratedStart(minPosView).toDouble(),
183+
helper.getDecoratedStart(maxPosView).toDouble())
184+
.toInt()
185+
val end =
186+
max(
187+
helper.getDecoratedEnd(minPosView).toDouble(),
188+
helper.getDecoratedEnd(maxPosView).toDouble())
189+
.toInt()
190+
val distance = end - start
191+
if (distance == 0) {
192+
return INVALID_DISTANCE
193+
}
194+
return 1f * distance / ((maxPos - minPos) + 1)
195+
}
196+
}
197+
}

0 commit comments

Comments
 (0)