fix: swipy (#501)
This commit is contained in:
parent
c37fefde73
commit
bd1f3388f7
1 changed files with 98 additions and 148 deletions
|
@ -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) {
|
||||||
|
verticalPos = when {
|
||||||
|
!it.canScrollVertically(1) && !it.canScrollVertically(-1) -> {
|
||||||
|
if (initialDown > (Resources.getSystem().displayMetrics.heightPixels / 2))
|
||||||
|
VerticalPosition.Bottom
|
||||||
else
|
else
|
||||||
!canScrollHorizontally(-1) to !canScrollHorizontally(1)
|
VerticalPosition.Top
|
||||||
|
}
|
||||||
scrollPos = when {
|
!it.canScrollVertically(1) -> VerticalPosition.Bottom
|
||||||
top && !bottom -> ScrollPosition.Start
|
!it.canScrollVertically(-1) -> VerticalPosition.Top
|
||||||
!top && bottom -> ScrollPosition.End
|
else -> VerticalPosition.None
|
||||||
top && bottom -> ScrollPosition.Both
|
}
|
||||||
else -> ScrollPosition.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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Add table
Add a link
Reference in a new issue