Make pager a completely separate class for easier reuse

This commit is contained in:
Ensar Sarajčić 2023-08-23 12:56:36 +02:00
parent 53efffde51
commit 59975c77f4

View file

@ -11,6 +11,7 @@ import android.content.Context
import android.graphics.* import android.graphics.*
import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.BitmapDrawable
import android.os.Bundle import android.os.Bundle
import android.os.Handler
import android.text.Layout import android.text.Layout
import android.text.StaticLayout import android.text.StaticLayout
import android.text.TextPaint import android.text.TextPaint
@ -67,7 +68,14 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel
private var redrawWidgets = false private var redrawWidgets = false
private var iconSize = 0 private var iconSize = 0
private val pager = Pager() private val pager = AnimatedGridPager(
getMaxPage = ::getMaxPage,
redrawGrid = ::redrawGrid,
getWidth = { width },
getHandler = { handler },
getNextPageBound = { right - sideMargins.right - cellWidth / 2 },
getPrevPageBound = { left + sideMargins.left + cellWidth / 2 }
)
// apply fake margins at the home screen. Real ones would cause the icons be cut at dragging at screen sides // apply fake margins at the home screen. Real ones would cause the icons be cut at dragging at screen sides
var sideMargins = Rect() var sideMargins = Rect()
@ -203,7 +211,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel
return return
} }
if (draggedItemCurrentCoords.first == -1 && draggedItemCurrentCoords.second == -1 && draggedItem != null) { if (draggedItemCurrentCoords.first == -1 && draggedItemCurrentCoords.second == -1) {
if (draggedItem!!.type == ITEM_TYPE_WIDGET) { if (draggedItem!!.type == ITEM_TYPE_WIDGET) {
val draggedWidgetView = widgetViews.firstOrNull { it.tag == draggedItem?.widgetId } val draggedWidgetView = widgetViews.firstOrNull { it.tag == draggedItem?.widgetId }
if (draggedWidgetView != null) { if (draggedWidgetView != null) {
@ -965,278 +973,28 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel
fun setSwipeMovement(diffX: Float) { fun setSwipeMovement(diffX: Float) {
pager.setSwipeMovement(diffX) if (draggedItem != null) {
pager.setSwipeMovement(diffX)
}
} }
fun finalizeSwipe() { fun finalizeSwipe() {
pager.finalizeSwipe() pager.finalizeSwipe()
} }
}
private inner class Pager { /**
private var lastPage = 0 * Helper class responsible for managing current page and providing utilities for animating page changes,
private var currentPage = 0 * as well as partial dragigng between pages
private var pageChangeLastArea = PageChangeArea.MIDDLE */
private var pageChangeLastAreaEntryTime = 0L private class AnimatedGridPager(
private var pageChangeAnimLeftPercentage = 0f private val getMaxPage: () -> Int,
private var pageChangeEnabled = true private val redrawGrid: () -> Unit,
private var pageChangeIndicatorsAlpha = 0f private val getWidth: () -> Int,
private var pageChangeSwipedPercentage = 0f private val getHandler: () -> Handler,
private val getNextPageBound: () -> Int,
fun getCurrentPage() = currentPage private val getPrevPageBound: () -> Int,
) {
fun isItemOnCurrentPage(item: HomeScreenGridItem) = item.page == currentPage
fun isItemOnLastPage(item: HomeScreenGridItem) = item.page == lastPage
fun getPageCount() = max(getMaxPage(), pager.currentPage) + 1
fun isOutsideOfPageRange() = currentPage > getMaxPage()
fun isItemInSwipeRange(item: HomeScreenGridItem) =
((pageChangeSwipedPercentage > 0f && item.page == currentPage - 1) || (pageChangeSwipedPercentage < 0f && item.page == currentPage + 1))
fun isSwiped() = abs(pageChangeSwipedPercentage) > 0f
fun isAnimatingPageChange() = pager.pageChangeAnimLeftPercentage > 0f
fun shouldDisplayPageChangeIndicator() = isSwiped() || isAnimatingPageChange() || pageChangeIndicatorsAlpha > 0f
fun getPageChangeIndicatorsAlpha() = if (pageChangeIndicatorsAlpha != 0f) {
(pager.pageChangeIndicatorsAlpha * 255.0f).toInt()
} else {
255
}
fun getXFactorForCurrentPage(): Float {
return if (abs(pageChangeSwipedPercentage) > 0f) {
pageChangeSwipedPercentage
} else {
if (currentPage > lastPage) {
pageChangeAnimLeftPercentage
} else {
-pageChangeAnimLeftPercentage
}
}
}
fun getXFactorForLastPage(): Float {
return if (abs(pageChangeSwipedPercentage) > 0f) {
(1 - abs(pageChangeSwipedPercentage)) * -sign(pageChangeSwipedPercentage)
} else {
if (currentPage > lastPage) {
pageChangeAnimLeftPercentage - 1
} else {
1 - pageChangeAnimLeftPercentage
}
}
}
fun getCurrentViewPositionInFullPageSpace(): Float {
val rangeStart = lastPage.toFloat()
val rangeEndPage = if (abs(pageChangeSwipedPercentage) > 0f) {
if (pageChangeSwipedPercentage > 0f) {
currentPage - 1
} else {
currentPage + 1
}
} else {
currentPage
}
val rangeEnd = rangeEndPage.toFloat()
val lerpAmount = if (pageChangeAnimLeftPercentage > 0f) {
1 - pageChangeAnimLeftPercentage
} else {
abs(pageChangeSwipedPercentage)
}
return MathUtils.lerp(rangeStart, rangeEnd, lerpAmount)
}
fun setSwipeMovement(diffX: Float) {
if (!pageChangeEnabled || draggedItem != null) {
return
}
if (currentPage < getMaxPage() && diffX > 0f || currentPage > 0 && diffX < 0f) {
pageChangeSwipedPercentage = (-diffX / width.toFloat()).coerceIn(-1f, 1f)
redrawGrid()
}
}
fun finalizeSwipe() {
if (abs(pageChangeSwipedPercentage) == 0f) {
return
}
if (abs(pageChangeSwipedPercentage) > 0.5f) {
lastPage = currentPage
currentPage = if (pageChangeSwipedPercentage > 0f) {
currentPage - 1
} else {
currentPage + 1
}
handlePageChange(true)
} else {
lastPage = if (pageChangeSwipedPercentage > 0f) {
currentPage - 1
} else {
currentPage + 1
}
pageChangeSwipedPercentage = sign(pageChangeSwipedPercentage) * (1 - abs(pageChangeSwipedPercentage))
handlePageChange(true)
}
}
fun handleItemMovement(x: Int, y: Int) {
showIndicators()
if (x > right - sideMargins.right - cellWidth / 2) {
doWithPageChangeDelay(PageChangeArea.RIGHT) {
nextOrAdditionalPage()
}
} else if (x < left + sideMargins.left + cellWidth / 2) {
doWithPageChangeDelay(PageChangeArea.LEFT) {
prevPage()
}
} else {
clearPageChangeFlags()
}
}
fun itemMovementStopped() {
scheduleIndicatorsFade()
}
fun nextPage(redraw: Boolean = false): Boolean {
if (currentPage < getMaxPage() && pageChangeEnabled) {
lastPage = currentPage
currentPage++
handlePageChange(redraw)
return true
}
return false
}
fun prevPage(redraw: Boolean = false): Boolean {
if (currentPage > 0 && pageChangeEnabled) {
lastPage = currentPage
currentPage--
handlePageChange(redraw)
return true
}
return false
}
fun skipToPage(targetPage: Int): Boolean {
if (currentPage != targetPage && targetPage < getMaxPage() + 1) {
lastPage = currentPage
currentPage = targetPage
handlePageChange()
return true
}
return false
}
private val checkAndExecuteDelayedPageChange: Runnable = Runnable {
if (System.currentTimeMillis() - pageChangeLastAreaEntryTime > PAGE_CHANGE_HOLD_THRESHOLD) {
when (pageChangeLastArea) {
PageChangeArea.RIGHT -> nextOrAdditionalPage(true)
PageChangeArea.LEFT -> prevPage(true)
else -> clearPageChangeFlags()
}
}
}
private val startFadingIndicators: Runnable = Runnable {
ValueAnimator.ofFloat(1f, 0f)
.apply {
addUpdateListener {
pageChangeIndicatorsAlpha = it.animatedValue as Float
redrawGrid()
}
start()
}
}
private fun showIndicators() {
pageChangeIndicatorsAlpha = 1f
removeCallbacks(startFadingIndicators)
}
private fun clearPageChangeFlags() {
pageChangeLastArea = PageChangeArea.MIDDLE
pageChangeLastAreaEntryTime = 0
removeCallbacks(checkAndExecuteDelayedPageChange)
}
private fun schedulePageChange() {
pageChangeLastAreaEntryTime = System.currentTimeMillis()
postDelayed(checkAndExecuteDelayedPageChange, PAGE_CHANGE_HOLD_THRESHOLD)
}
private fun scheduleIndicatorsFade() {
pageChangeIndicatorsAlpha = 1f
postDelayed(startFadingIndicators, PAGE_INDICATORS_FADE_DELAY)
}
private fun doWithPageChangeDelay(needed: PageChangeArea, pageChangeFunction: () -> Boolean) {
if (pageChangeLastArea != needed) {
pageChangeLastArea = needed
schedulePageChange()
} else if (System.currentTimeMillis() - pageChangeLastAreaEntryTime > PAGE_CHANGE_HOLD_THRESHOLD) {
if (pageChangeFunction()) {
clearPageChangeFlags()
}
}
}
private fun nextOrAdditionalPage(redraw: Boolean = false): Boolean {
if (currentPage < getMaxPage() + 1 && pageChangeEnabled) {
lastPage = currentPage
currentPage++
handlePageChange(redraw)
return true
}
return false
}
private fun handlePageChange(redraw: Boolean = false) {
pageChangeEnabled = false
pageChangeIndicatorsAlpha = 0f
val startingAt = 1 - abs(pageChangeSwipedPercentage)
pageChangeSwipedPercentage = 0f
removeCallbacks(startFadingIndicators)
if (redraw) {
redrawGrid()
}
ValueAnimator.ofFloat(startingAt, 0f)
.apply {
addUpdateListener {
pageChangeAnimLeftPercentage = it.animatedValue as Float
redrawGrid()
}
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
super.onAnimationEnd(animation)
pageChangeAnimLeftPercentage = 0f
pageChangeEnabled = true
lastPage = currentPage
if (draggedItem != null) {
schedulePageChange()
} else {
clearPageChangeFlags()
}
scheduleIndicatorsFade()
redrawGrid()
}
})
start()
}
}
}
companion object { companion object {
private const val PAGE_CHANGE_HOLD_THRESHOLD = 500L private const val PAGE_CHANGE_HOLD_THRESHOLD = 500L
@ -1249,4 +1007,263 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel
} }
} }
private var lastPage = 0
private var currentPage = 0
private var pageChangeLastArea = PageChangeArea.MIDDLE
private var pageChangeLastAreaEntryTime = 0L
private var pageChangeAnimLeftPercentage = 0f
private var pageChangeEnabled = true
private var pageChangeIndicatorsAlpha = 0f
private var pageChangeSwipedPercentage = 0f
fun getCurrentPage() = currentPage
fun isItemOnCurrentPage(item: HomeScreenGridItem) = item.page == currentPage
fun isItemOnLastPage(item: HomeScreenGridItem) = item.page == lastPage
fun getPageCount() = max(getMaxPage(), currentPage) + 1
fun isOutsideOfPageRange() = currentPage > getMaxPage()
fun isItemInSwipeRange(item: HomeScreenGridItem) =
((pageChangeSwipedPercentage > 0f && item.page == currentPage - 1) || (pageChangeSwipedPercentage < 0f && item.page == currentPage + 1))
fun isSwiped() = abs(pageChangeSwipedPercentage) > 0f
fun isAnimatingPageChange() = pageChangeAnimLeftPercentage > 0f
fun shouldDisplayPageChangeIndicator() = isSwiped() || isAnimatingPageChange() || pageChangeIndicatorsAlpha > 0f
fun getPageChangeIndicatorsAlpha() = if (pageChangeIndicatorsAlpha != 0f) {
(pageChangeIndicatorsAlpha * 255.0f).toInt()
} else {
255
}
fun getXFactorForCurrentPage(): Float {
return if (abs(pageChangeSwipedPercentage) > 0f) {
pageChangeSwipedPercentage
} else {
if (currentPage > lastPage) {
pageChangeAnimLeftPercentage
} else {
-pageChangeAnimLeftPercentage
}
}
}
fun getXFactorForLastPage(): Float {
return if (abs(pageChangeSwipedPercentage) > 0f) {
(1 - abs(pageChangeSwipedPercentage)) * -sign(pageChangeSwipedPercentage)
} else {
if (currentPage > lastPage) {
pageChangeAnimLeftPercentage - 1
} else {
1 - pageChangeAnimLeftPercentage
}
}
}
fun getCurrentViewPositionInFullPageSpace(): Float {
val rangeStart = lastPage.toFloat()
val rangeEndPage = if (abs(pageChangeSwipedPercentage) > 0f) {
if (pageChangeSwipedPercentage > 0f) {
currentPage - 1
} else {
currentPage + 1
}
} else {
currentPage
}
val rangeEnd = rangeEndPage.toFloat()
val lerpAmount = if (pageChangeAnimLeftPercentage > 0f) {
1 - pageChangeAnimLeftPercentage
} else {
abs(pageChangeSwipedPercentage)
}
return MathUtils.lerp(rangeStart, rangeEnd, lerpAmount)
}
fun setSwipeMovement(diffX: Float) {
if (!pageChangeEnabled) {
return
}
if (currentPage < getMaxPage() && diffX > 0f || currentPage > 0 && diffX < 0f) {
pageChangeSwipedPercentage = (-diffX / getWidth().toFloat()).coerceIn(-1f, 1f)
redrawGrid()
}
}
fun finalizeSwipe() {
if (abs(pageChangeSwipedPercentage) == 0f) {
return
}
if (abs(pageChangeSwipedPercentage) > 0.5f) {
lastPage = currentPage
currentPage = if (pageChangeSwipedPercentage > 0f) {
currentPage - 1
} else {
currentPage + 1
}
handlePageChange(true)
} else {
lastPage = if (pageChangeSwipedPercentage > 0f) {
currentPage - 1
} else {
currentPage + 1
}
pageChangeSwipedPercentage = sign(pageChangeSwipedPercentage) * (1 - abs(pageChangeSwipedPercentage))
handlePageChange(true)
}
}
fun handleItemMovement(x: Int, y: Int) {
showIndicators()
if (x > getNextPageBound()) {
doWithPageChangeDelay(PageChangeArea.RIGHT) {
nextOrAdditionalPage()
}
} else if (x < getPrevPageBound()) {
doWithPageChangeDelay(PageChangeArea.LEFT) {
prevPage()
}
} else {
clearPageChangeFlags()
}
}
fun itemMovementStopped() {
scheduleIndicatorsFade()
}
fun nextPage(redraw: Boolean = false): Boolean {
if (currentPage < getMaxPage() && pageChangeEnabled) {
lastPage = currentPage
currentPage++
handlePageChange(redraw)
return true
}
return false
}
fun prevPage(redraw: Boolean = false): Boolean {
if (currentPage > 0 && pageChangeEnabled) {
lastPage = currentPage
currentPage--
handlePageChange(redraw)
return true
}
return false
}
fun skipToPage(targetPage: Int): Boolean {
if (currentPage != targetPage && targetPage < getMaxPage() + 1) {
lastPage = currentPage
currentPage = targetPage
handlePageChange()
return true
}
return false
}
private val checkAndExecuteDelayedPageChange: Runnable = Runnable {
if (System.currentTimeMillis() - pageChangeLastAreaEntryTime > PAGE_CHANGE_HOLD_THRESHOLD) {
when (pageChangeLastArea) {
PageChangeArea.RIGHT -> nextOrAdditionalPage(true)
PageChangeArea.LEFT -> prevPage(true)
else -> clearPageChangeFlags()
}
}
}
private val startFadingIndicators: Runnable = Runnable {
ValueAnimator.ofFloat(1f, 0f)
.apply {
addUpdateListener {
pageChangeIndicatorsAlpha = it.animatedValue as Float
redrawGrid()
}
start()
}
}
private fun showIndicators() {
pageChangeIndicatorsAlpha = 1f
getHandler().removeCallbacks(startFadingIndicators)
}
private fun clearPageChangeFlags() {
pageChangeLastArea = PageChangeArea.MIDDLE
pageChangeLastAreaEntryTime = 0
getHandler().removeCallbacks(checkAndExecuteDelayedPageChange)
}
private fun schedulePageChange() {
pageChangeLastAreaEntryTime = System.currentTimeMillis()
getHandler().postDelayed(checkAndExecuteDelayedPageChange, PAGE_CHANGE_HOLD_THRESHOLD)
}
private fun scheduleIndicatorsFade() {
pageChangeIndicatorsAlpha = 1f
getHandler().postDelayed(startFadingIndicators, PAGE_INDICATORS_FADE_DELAY)
}
private fun doWithPageChangeDelay(needed: PageChangeArea, pageChangeFunction: () -> Boolean) {
if (pageChangeLastArea != needed) {
pageChangeLastArea = needed
schedulePageChange()
} else if (System.currentTimeMillis() - pageChangeLastAreaEntryTime > PAGE_CHANGE_HOLD_THRESHOLD) {
if (pageChangeFunction()) {
clearPageChangeFlags()
}
}
}
private fun nextOrAdditionalPage(redraw: Boolean = false): Boolean {
if (currentPage < getMaxPage() + 1 && pageChangeEnabled) {
lastPage = currentPage
currentPage++
handlePageChange(redraw)
return true
}
return false
}
private fun handlePageChange(redraw: Boolean = false) {
pageChangeEnabled = false
pageChangeIndicatorsAlpha = 0f
val startingAt = 1 - abs(pageChangeSwipedPercentage)
pageChangeSwipedPercentage = 0f
getHandler().removeCallbacks(startFadingIndicators)
if (redraw) {
redrawGrid()
}
ValueAnimator.ofFloat(startingAt, 0f)
.apply {
addUpdateListener {
pageChangeAnimLeftPercentage = it.animatedValue as Float
redrawGrid()
}
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
super.onAnimationEnd(animation)
pageChangeAnimLeftPercentage = 0f
pageChangeEnabled = true
lastPage = currentPage
clearPageChangeFlags()
scheduleIndicatorsFade()
redrawGrid()
}
})
start()
}
}
} }