fix: swipy (#501)

This commit is contained in:
Dawn-used-yeet 2024-10-27 22:56:11 +05:30 committed by GitHub
parent c37fefde73
commit bd1f3388f7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -8,7 +8,7 @@ import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.ViewConfiguration import android.view.ViewConfiguration
import android.widget.FrameLayout import android.widget.FrameLayout
import kotlin.math.absoluteValue import kotlin.math.abs
class Swipy @JvmOverloads constructor( class Swipy @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null context: Context, attrs: AttributeSet? = null
@ -17,7 +17,6 @@ class Swipy @JvmOverloads constructor(
var dragDivider: Int = 5 var dragDivider: Int = 5
var vertical = true var vertical = true
//public, in case a different sub child needs to be considered
var child: View? = getChildAt(0) var child: View? = getChildAt(0)
var topBeingSwiped: ((Float) -> Unit) = {} var topBeingSwiped: ((Float) -> Unit) = {}
@ -30,87 +29,80 @@ class Swipy @JvmOverloads constructor(
var rightBeingSwiped: ((Float) -> Unit) = {} var rightBeingSwiped: ((Float) -> Unit) = {}
companion object { companion object {
private const val DRAG_RATE = .5f private const val DRAG_RATE = 0.5f
private const val INVALID_POINTER = -1 private const val INVALID_POINTER = -1
} }
private var touchSlop = ViewConfiguration.get(context).scaledTouchSlop private val touchSlop = ViewConfiguration.get(context).scaledTouchSlop
private var activePointerId = INVALID_POINTER private var activePointerId = INVALID_POINTER
private var isBeingDragged = false private var isBeingDragged = false
private var initialDown = 0f private var initialDown = 0f
private var initialMotion = 0f private var initialMotion = 0f
enum class ScrollPosition { private enum class VerticalPosition { Top, None, Bottom }
None, private enum class HorizontalPosition { Left, None, Right }
Start,
End,
Both
}
private var scrollPos = ScrollPosition.None private var horizontalPos = HorizontalPosition.None
private var verticalPos = VerticalPosition.None
private fun setScrollPosition() = child?.run { private fun setChildPosition() {
val (top, bottom) = if (vertical) child?.let {
!canScrollVertically(-1) to !canScrollVertically(1) if (vertical) {
else verticalPos = when {
!canScrollHorizontally(-1) to !canScrollHorizontally(1) !it.canScrollVertically(1) && !it.canScrollVertically(-1) -> {
if (initialDown > (Resources.getSystem().displayMetrics.heightPixels / 2))
scrollPos = when { VerticalPosition.Bottom
top && !bottom -> ScrollPosition.Start else
!top && bottom -> ScrollPosition.End VerticalPosition.Top
top && bottom -> ScrollPosition.Both }
else -> ScrollPosition.None !it.canScrollVertically(1) -> VerticalPosition.Bottom
!it.canScrollVertically(-1) -> VerticalPosition.Top
else -> VerticalPosition.None
}
} else {
horizontalPos = when {
!it.canScrollHorizontally(1) && !it.canScrollHorizontally(-1) -> {
if (initialDown > (Resources.getSystem().displayMetrics.widthPixels / 2))
HorizontalPosition.Right
else
HorizontalPosition.Left
}
!it.canScrollHorizontally(1) -> HorizontalPosition.Right
!it.canScrollHorizontally(-1) -> HorizontalPosition.Left
else -> HorizontalPosition.None
}
}
} }
} }
private fun canChildScroll(): Boolean { private fun canChildScroll(): Boolean {
setScrollPosition() setChildPosition()
return scrollPos == ScrollPosition.None return if (vertical) verticalPos == VerticalPosition.None
else horizontalPos == HorizontalPosition.None
} }
private fun onSecondaryPointerUp(ev: MotionEvent) { private fun onSecondaryPointerUp(ev: MotionEvent) {
val pointerIndex = ev.actionIndex val pointerIndex = ev.actionIndex
val pointerId = ev.getPointerId(pointerIndex) if (ev.getPointerId(pointerIndex) == activePointerId) {
if (pointerId == activePointerId) { activePointerId = ev.getPointerId(if (pointerIndex == 0) 1 else 0)
val newPointerIndex = if (pointerIndex == 0) 1 else 0
activePointerId = ev.getPointerId(newPointerIndex)
} }
} }
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
val action = ev.actionMasked if (!isEnabled || canChildScroll()) return false
val pointerIndex: Int
if (!isEnabled || canChildScroll()) {
return false
}
when (action) { when (ev.actionMasked) {
MotionEvent.ACTION_DOWN -> { MotionEvent.ACTION_DOWN -> {
activePointerId = ev.getPointerId(0) activePointerId = ev.getPointerId(0)
initialDown = if (vertical) ev.getY(0) else ev.getX(0)
isBeingDragged = false isBeingDragged = false
pointerIndex = ev.findPointerIndex(activePointerId)
if (pointerIndex < 0) {
return false
}
initialDown = if (vertical) ev.getY(pointerIndex) else ev.getX(pointerIndex)
} }
MotionEvent.ACTION_MOVE -> { MotionEvent.ACTION_MOVE -> {
if (activePointerId == INVALID_POINTER) { val pointerIndex = ev.findPointerIndex(activePointerId)
//("Got ACTION_MOVE event but don't have an active pointer id.") if (pointerIndex >= 0) {
return false startDragging(if (vertical) ev.getY(pointerIndex) else ev.getX(pointerIndex))
} }
pointerIndex = ev.findPointerIndex(activePointerId)
if (pointerIndex < 0) {
return false
}
val pos = if (vertical) ev.getY(pointerIndex) else ev.getX(pointerIndex)
startDragging(pos)
} }
MotionEvent.ACTION_POINTER_UP -> onSecondaryPointerUp(ev) MotionEvent.ACTION_POINTER_UP -> onSecondaryPointerUp(ev)
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
isBeingDragged = false isBeingDragged = false
@ -122,141 +114,99 @@ class Swipy @JvmOverloads constructor(
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(ev: MotionEvent): Boolean { override fun onTouchEvent(ev: MotionEvent): Boolean {
val action = ev.actionMasked if (!isEnabled || canChildScroll()) return false
val pointerIndex: Int val pointerIndex: Int
if (!isEnabled || canChildScroll()) { when (ev.actionMasked) {
return false
}
when (action) {
MotionEvent.ACTION_DOWN -> { MotionEvent.ACTION_DOWN -> {
activePointerId = ev.getPointerId(0) activePointerId = ev.getPointerId(0)
isBeingDragged = false isBeingDragged = false
} }
MotionEvent.ACTION_MOVE -> { MotionEvent.ACTION_MOVE -> {
pointerIndex = ev.findPointerIndex(activePointerId) pointerIndex = ev.findPointerIndex(activePointerId)
if (pointerIndex < 0) return false if (pointerIndex >= 0) {
val pos = if (vertical) ev.getY(pointerIndex) else ev.getX(pointerIndex)
val pos = if (vertical) ev.getY(pointerIndex) else ev.getX(pointerIndex) startDragging(pos)
startDragging(pos) if (isBeingDragged) handleDrag(pos)
if (!isBeingDragged) return false
val overscroll = getDiff(pos) * DRAG_RATE
if (overscroll.absoluteValue <= 0) return false
parent.requestDisallowInterceptTouchEvent(true)
if (vertical) {
val dragDistance =
Resources.getSystem().displayMetrics.heightPixels / dragDivider
performSwiping(overscroll, dragDistance, topBeingSwiped, bottomBeingSwiped)
} else {
val dragDistance =
Resources.getSystem().displayMetrics.widthPixels / dragDivider
performSwiping(overscroll, dragDistance, leftBeingSwiped, rightBeingSwiped)
} }
} }
MotionEvent.ACTION_POINTER_DOWN -> { MotionEvent.ACTION_POINTER_DOWN -> {
pointerIndex = ev.actionIndex pointerIndex = ev.actionIndex
if (pointerIndex < 0) { if (pointerIndex >= 0) activePointerId = ev.getPointerId(pointerIndex)
//("Got ACTION_POINTER_DOWN event but have an invalid action index.")
return false
}
activePointerId = ev.getPointerId(pointerIndex)
} }
MotionEvent.ACTION_POINTER_UP -> onSecondaryPointerUp(ev) MotionEvent.ACTION_POINTER_UP -> onSecondaryPointerUp(ev)
MotionEvent.ACTION_UP -> { MotionEvent.ACTION_UP -> {
if (vertical) { resetSwipes()
topBeingSwiped(0f)
bottomBeingSwiped(0f)
} else {
rightBeingSwiped(0f)
leftBeingSwiped(0f)
}
pointerIndex = ev.findPointerIndex(activePointerId) pointerIndex = ev.findPointerIndex(activePointerId)
if (pointerIndex < 0) { if (pointerIndex >= 0) finishSpinner(if (vertical) ev.getY(pointerIndex) else ev.getX(pointerIndex))
//("Got ACTION_UP event but don't have an active pointer id.")
return false
}
if (isBeingDragged) {
val pos = if (vertical) ev.getY(pointerIndex) else ev.getX(pointerIndex)
val overscroll = getDiff(pos) * DRAG_RATE
isBeingDragged = false
finishSpinner(overscroll)
}
activePointerId = INVALID_POINTER activePointerId = INVALID_POINTER
return false return false
} }
MotionEvent.ACTION_CANCEL -> return false MotionEvent.ACTION_CANCEL -> return false
} }
return true return true
} }
private fun getDiff(pos: Float) = when (scrollPos) {
ScrollPosition.None -> 0f
ScrollPosition.Start, ScrollPosition.Both -> pos - initialMotion
ScrollPosition.End -> initialMotion - pos
}
private fun startDragging(pos: Float) { private fun startDragging(pos: Float) {
val posDiff = getDiff(pos).absoluteValue val posDiff = if ((vertical && verticalPos == VerticalPosition.Top) || (!vertical && horizontalPos == HorizontalPosition.Left))
pos - initialDown
else
initialDown - pos
if (posDiff > touchSlop && !isBeingDragged) { if (posDiff > touchSlop && !isBeingDragged) {
initialMotion = initialDown + touchSlop initialMotion = initialDown + touchSlop
isBeingDragged = true isBeingDragged = true
} }
} }
private fun performSwiping( private fun handleDrag(pos: Float) {
overscrollDistance: Float, val overscroll = (pos - initialMotion) * DRAG_RATE
totalDragDistance: Int, if (overscroll > 0) { // Keep this check
startBlock: (Float) -> Unit, parent.requestDisallowInterceptTouchEvent(true)
endBlock: (Float) -> Unit if (vertical) {
) { val totalDragDistance = Resources.getSystem().displayMetrics.heightPixels / dragDivider
val distance = overscrollDistance * 2 / totalDragDistance if (verticalPos == VerticalPosition.Top)
when (scrollPos) { topBeingSwiped.invoke(overscroll * 2 / totalDragDistance)
ScrollPosition.Start -> startBlock(distance) else
ScrollPosition.End -> endBlock(distance) bottomBeingSwiped.invoke(overscroll * 2 / totalDragDistance)
ScrollPosition.Both -> { } else {
startBlock(distance) val totalDragDistance = Resources.getSystem().displayMetrics.widthPixels / dragDivider
endBlock(-distance) if (horizontalPos == HorizontalPosition.Left)
leftBeingSwiped.invoke(overscroll / totalDragDistance)
else
rightBeingSwiped.invoke(overscroll / totalDragDistance)
} }
else -> {}
} }
} }
private fun performSwipe( private fun resetSwipes() {
overscrollDistance: Float, if (vertical) {
totalDragDistance: Int, topBeingSwiped.invoke(0f)
startBlock: () -> Unit, bottomBeingSwiped.invoke(0f)
endBlock: () -> Unit } else {
) { rightBeingSwiped.invoke( 0f)
fun check(distance: Float, block: () -> Unit) { leftBeingSwiped.invoke(0f)
if (distance * 2 > totalDragDistance)
block.invoke()
}
when (scrollPos) {
ScrollPosition.Start -> check(overscrollDistance) { startBlock() }
ScrollPosition.End -> check(overscrollDistance) { endBlock() }
ScrollPosition.Both -> {
check(overscrollDistance) { startBlock() }
check(-overscrollDistance) { endBlock() }
}
else -> {}
} }
} }
private fun finishSpinner(overscrollDistance: Float) { private fun finishSpinner(overscrollDistance: Float) {
if (vertical) { if (vertical) {
val totalDragDistance = Resources.getSystem().displayMetrics.heightPixels / dragDivider val totalDragDistance = Resources.getSystem().displayMetrics.heightPixels / dragDivider
performSwipe(overscrollDistance, totalDragDistance, onTopSwiped, onBottomSwiped) val swipeDistance = abs(overscrollDistance - initialMotion)
if (swipeDistance > totalDragDistance) {
if (verticalPos == VerticalPosition.Top)
onTopSwiped.invoke()
else
onBottomSwiped.invoke()
}
} else { } else {
val totalDragDistance = Resources.getSystem().displayMetrics.widthPixels / dragDivider val totalDragDistance = Resources.getSystem().displayMetrics.widthPixels / dragDivider
performSwipe(overscrollDistance, totalDragDistance, onLeftSwiped, onRightSwiped) val swipeDistance = abs(overscrollDistance - initialMotion)
if (swipeDistance > totalDragDistance) {
if (horizontalPos == HorizontalPosition.Left)
onLeftSwiped.invoke()
else
onRightSwiped.invoke()
}
} }
} }
} }