Add folder opening and closing animation

This commit is contained in:
Ensar Sarajčić 2023-08-23 16:48:56 +02:00
parent 7a5c698322
commit 3cc00157cc
1 changed files with 221 additions and 138 deletions

View File

@ -20,9 +20,11 @@ import android.util.AttributeSet
import android.util.Size import android.util.Size
import android.util.SizeF import android.util.SizeF
import android.view.View import android.view.View
import android.view.animation.DecelerateInterpolator
import android.widget.RelativeLayout import android.widget.RelativeLayout
import androidx.core.graphics.drawable.toBitmap import androidx.core.graphics.drawable.toBitmap
import androidx.core.graphics.drawable.toDrawable import androidx.core.graphics.drawable.toDrawable
import androidx.core.graphics.withScale
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
import androidx.customview.widget.ExploreByTouchHelper import androidx.customview.widget.ExploreByTouchHelper
@ -81,7 +83,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel
private var pageChangeEnabled = true private var pageChangeEnabled = true
private var pageChangeIndicatorsAlpha = 0f private var pageChangeIndicatorsAlpha = 0f
private var currentlyOpenFolder: HomeScreenGridItem? = null private var currentlyOpenFolder: HomeScreenFolder? = null
private var draggingLeftFolderAt: Long? = null private var draggingLeftFolderAt: Long? = null
private var draggingEnteredNewFolderAt: Long? = null private var draggingEnteredNewFolderAt: Long? = null
@ -194,7 +196,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel
if (item.type == ITEM_TYPE_ICON) { if (item.type == ITEM_TYPE_ICON) {
item.drawable = context.getDrawableForPackageName(item.packageName) item.drawable = context.getDrawableForPackageName(item.packageName)
} else if (item.type == ITEM_TYPE_FOLDER) { } else if (item.type == ITEM_TYPE_FOLDER) {
item.drawable = item.generateFolderDrawable() item.drawable = item.toFolder().generateDrawable()
} else if (item.type == ITEM_TYPE_SHORTCUT) { } else if (item.type == ITEM_TYPE_SHORTCUT) {
if (item.icon != null) { if (item.icon != null) {
item.drawable = BitmapDrawable(item.icon) item.drawable = BitmapDrawable(item.icon)
@ -270,7 +272,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel
if (draggedItem!!.drawable == null) { if (draggedItem!!.drawable == null) {
if (draggedItem?.type == ITEM_TYPE_FOLDER) { if (draggedItem?.type == ITEM_TYPE_FOLDER) {
draggedItem!!.drawable = draggedGridItem!!.generateFolderDrawable() draggedItem!!.drawable = draggedGridItem.toFolder().generateDrawable()
} else { } else {
draggedItem!!.drawable = context.getDrawableForPackageName(draggedGridItem.packageName) draggedItem!!.drawable = context.getDrawableForPackageName(draggedGridItem.packageName)
} }
@ -285,7 +287,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel
} }
currentlyOpenFolder?.also { folder -> currentlyOpenFolder?.also { folder ->
if (folder.getFolderRect().contains(x.toFloat(), y.toFloat())) { if (folder.getDrawingRect().contains(x.toFloat(), y.toFloat())) {
draggingLeftFolderAt = null draggingLeftFolderAt = null
} else { } else {
draggingLeftFolderAt.also { draggingLeftFolderAt.also {
@ -324,7 +326,9 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel
if (it == null) { if (it == null) {
draggingEnteredNewFolderAt = System.currentTimeMillis() draggingEnteredNewFolderAt = System.currentTimeMillis()
} else if (System.currentTimeMillis() - it > FOLDER_OPEN_HOLD_THRESHOLD) { } else if (System.currentTimeMillis() - it > FOLDER_OPEN_HOLD_THRESHOLD) {
if (coveredFolder.getFolderItems().count() >= HomeScreenGridItem.FOLDER_MAX_CAPACITY && draggedItem?.parentId != coveredFolder.id) { if (coveredFolder.toFolder().getItems()
.count() >= HomeScreenGridItem.FOLDER_MAX_CAPACITY && draggedItem?.parentId != coveredFolder.id
) {
performHapticFeedback() performHapticFeedback()
draggingEnteredNewFolderAt = null draggingEnteredNewFolderAt = null
} else { } else {
@ -518,16 +522,16 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel
var redrawIcons = false var redrawIcons = false
val folder = currentlyOpenFolder val folder = currentlyOpenFolder
if (folder != null && folder.getFolderItemsRect().contains( if (folder != null && folder.getItemsDrawingRect().contains(
(sideMargins.left + draggedItemCurrentCoords.first).toFloat(), (sideMargins.left + draggedItemCurrentCoords.first).toFloat(),
(sideMargins.top + draggedItemCurrentCoords.second).toFloat() (sideMargins.top + draggedItemCurrentCoords.second).toFloat()
) )
) { ) {
val center = folder.getFolderGridCenters().minBy { val center = folder.getItemsGridCenters().minBy {
abs(it.second - draggedItemCurrentCoords.first + sideMargins.left) + abs(it.third - draggedItemCurrentCoords.second + sideMargins.top) abs(it.second - draggedItemCurrentCoords.first + sideMargins.left) + abs(it.third - draggedItemCurrentCoords.second + sideMargins.top)
} }
isDroppingPositionValid = true isDroppingPositionValid = true
potentialParent = folder potentialParent = folder.item
xIndex = center.first xIndex = center.first
yIndex = 0 yIndex = 0
redrawIcons = true redrawIcons = true
@ -568,7 +572,13 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel
if (potentialParent != null) { if (potentialParent != null) {
if (potentialParent?.type == ITEM_TYPE_FOLDER) { if (potentialParent?.type == ITEM_TYPE_FOLDER) {
addAppIconOrShortcut(draggedHomeGridItem, xIndex!!, yIndex!!, potentialParent?.id, toFolderEnd = potentialParent != currentlyOpenFolder) addAppIconOrShortcut(
draggedHomeGridItem,
xIndex!!,
yIndex!!,
potentialParent?.id,
toFolderEnd = potentialParent != currentlyOpenFolder?.item
)
} else { } else {
val parentItem = potentialParent!!.copy( val parentItem = potentialParent!!.copy(
type = ITEM_TYPE_FOLDER, type = ITEM_TYPE_FOLDER,
@ -615,7 +625,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel
) { ) {
if (newParentId != null && newParentId != draggedHomeGridItem?.parentId) { if (newParentId != null && newParentId != draggedHomeGridItem?.parentId) {
gridItems.firstOrNull { it.id == newParentId }?.also { gridItems.firstOrNull { it.id == newParentId }?.also {
if (it.getFolderItems().count() >= HomeScreenGridItem.FOLDER_MAX_CAPACITY) { if (it.toFolder().getItems().count() >= HomeScreenGridItem.FOLDER_MAX_CAPACITY) {
performHapticFeedback() performHapticFeedback()
draggedItem = null draggedItem = null
draggedItemCurrentCoords = Pair(-1, -1) draggedItemCurrentCoords = Pair(-1, -1)
@ -627,9 +637,9 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel
val finalXIndex = if (newParentId != null) { val finalXIndex = if (newParentId != null) {
if (toFolderEnd) { if (toFolderEnd) {
gridItems.firstOrNull { it.id == newParentId }?.getFolderItems()?.maxOf { it.left + 1 } ?: 0 gridItems.firstOrNull { it.id == newParentId }?.toFolder()?.getItems()?.maxOf { it.left + 1 } ?: 0
} else { } else {
min(xIndex, gridItems.firstOrNull { it.id == newParentId }?.getFolderItems()?.maxOf { min(xIndex, gridItems.firstOrNull { it.id == newParentId }?.toFolder()?.getItems()?.maxOf {
if (draggedHomeGridItem?.parentId == newParentId) { if (draggedHomeGridItem?.parentId == newParentId) {
it.left it.left
} else { } else {
@ -654,7 +664,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel
parentId = newParentId parentId = newParentId
val oldParent = gridItems.firstOrNull { it.id == oldParentId } val oldParent = gridItems.firstOrNull { it.id == oldParentId }
val deleteOldParent = if (oldParent?.getFolderItems()?.isEmpty() == true) { val deleteOldParent = if (oldParent?.toFolder()?.getItems()?.isEmpty() == true) {
gridItems.remove(oldParent) gridItems.remove(oldParent)
true true
} else { } else {
@ -983,7 +993,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel
val drawableX = baseItemX + iconMargin + extraXMargin val drawableX = baseItemX + iconMargin + extraXMargin
val drawable = if (item.type == ITEM_TYPE_FOLDER) { val drawable = if (item.type == ITEM_TYPE_FOLDER) {
item.generateFolderDrawable() item.toFolder().generateDrawable()
} else { } else {
item.drawable!! item.drawable!!
} }
@ -1103,45 +1113,53 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel
val folder = currentlyOpenFolder val folder = currentlyOpenFolder
if (folder != null) { if (folder != null) {
val items = folder.getFolderItems() val items = folder.getItems()
val folderRect = folder.getFolderRect() val folderRect = folder.getDrawingRect()
val folderItemsRect = folder.getFolderItemsRect() val folderItemsRect = folder.getItemsDrawingRect()
canvas.drawRoundRect(folderRect, roundedCornerRadius, roundedCornerRadius, folderBackgroundPaint) canvas.drawRoundRect(folderRect, roundedCornerRadius, roundedCornerRadius, folderBackgroundPaint)
val textX = folderRect.left + folderPadding canvas.withScale(folder.scale, folder.scale, folderRect.centerX(), folderRect.centerY()) {
val textY = folderRect.top + folderPadding + folderTitleTextPaint.textSize val textX = folderRect.left + folderPadding
val staticLayout = StaticLayout.Builder val textY = folderRect.top + folderPadding + folderTitleTextPaint.textSize
.obtain(folder.title, 0, folder.title.length, folderTitleTextPaint, (folderRect.width() - 2 * folderPadding).toInt()) val staticLayout = StaticLayout.Builder
.setMaxLines(1) .obtain(
.setEllipsize(TextUtils.TruncateAt.END) folder.item.title,
.setAlignment(Layout.Alignment.ALIGN_CENTER) 0,
.build() folder.item.title.length,
folderTitleTextPaint,
(folderRect.width() - 2 * folderPadding * folder.scale).toInt()
)
.setMaxLines(1)
.setEllipsize(TextUtils.TruncateAt.END)
.setAlignment(Layout.Alignment.ALIGN_CENTER)
.build()
canvas.save() canvas.save()
canvas.translate(textX, textY) canvas.translate(textX, textY)
staticLayout.draw(canvas) staticLayout.draw(canvas)
canvas.restore() canvas.restore()
items.forEach { item -> items.forEach { item ->
val (row, column) = item.getPositionInFolder(folder) val (row, column) = folder.getItemPosition(item)
handleGridItemDrawing( handleGridItemDrawing(
item, item,
(folderItemsRect.left + column * cellWidth).roundToInt(), (folderItemsRect.left + column * cellWidth).roundToInt(),
(folderItemsRect.top + row * cellHeight).roundToInt() (folderItemsRect.top + row * cellHeight).roundToInt()
) )
}
} }
} }
if (draggedItem != null && draggedItemCurrentCoords.first != -1 && draggedItemCurrentCoords.second != -1) { if (draggedItem != null && draggedItemCurrentCoords.first != -1 && draggedItemCurrentCoords.second != -1) {
if (draggedItem!!.type == ITEM_TYPE_ICON || draggedItem!!.type == ITEM_TYPE_SHORTCUT || draggedItem!!.type == ITEM_TYPE_FOLDER) { if (draggedItem!!.type == ITEM_TYPE_ICON || draggedItem!!.type == ITEM_TYPE_SHORTCUT || draggedItem!!.type == ITEM_TYPE_FOLDER) {
if (folder != null && folder.getFolderItemsRect().contains( if (folder != null && folder.getItemsDrawingRect().contains(
(sideMargins.left + draggedItemCurrentCoords.first).toFloat(), (sideMargins.left + draggedItemCurrentCoords.first).toFloat(),
(sideMargins.top + draggedItemCurrentCoords.second).toFloat() (sideMargins.top + draggedItemCurrentCoords.second).toFloat()
) )
) { ) {
val center = folder.getFolderGridCenters().minBy { val center = folder.getItemsGridCenters().minBy {
abs(it.second - draggedItemCurrentCoords.first + sideMargins.left) + abs(it.third - draggedItemCurrentCoords.second + sideMargins.top) abs(it.second - draggedItemCurrentCoords.first + sideMargins.left) + abs(it.third - draggedItemCurrentCoords.second + sideMargins.top)
} }
@ -1261,9 +1279,9 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel
val folder = currentlyOpenFolder val folder = currentlyOpenFolder
val clickableLeft: Int val clickableLeft: Int
val clickableTop: Int val clickableTop: Int
if (folder != null && item.parentId == folder.id) { if (folder != null && item.parentId == folder.item.id) {
val folderRect = folder.getFolderItemsRect() val folderRect = folder.getItemsDrawingRect()
val (row, column) = item.getPositionInFolder(folder) val (row, column) = folder.getItemPosition(item)
clickableLeft = (folderRect.left + column * cellWidth + extraXMargin).toInt() clickableLeft = (folderRect.left + column * cellWidth + extraXMargin).toInt()
clickableTop = (folderRect.top + row * cellHeight - iconMargin + extraYMargin).toInt() clickableTop = (folderRect.top + row * cellHeight - iconMargin + extraYMargin).toInt()
} else { } else {
@ -1305,7 +1323,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel
fun isClickingGridItem(x: Int, y: Int): HomeScreenGridItem? { fun isClickingGridItem(x: Int, y: Int): HomeScreenGridItem? {
currentlyOpenFolder?.also { folder -> currentlyOpenFolder?.also { folder ->
folder.getFolderItems().forEach { gridItem -> folder.getItems().forEach { gridItem ->
val rect = getClickableRect(gridItem) val rect = getClickableRect(gridItem)
if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) { if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) {
return gridItem return gridItem
@ -1454,14 +1472,20 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel
} }
fun openFolder(folder: HomeScreenGridItem) { fun openFolder(folder: HomeScreenGridItem) {
currentlyOpenFolder = folder if (currentlyOpenFolder == null) {
redrawGrid() currentlyOpenFolder = folder.toFolder(animateOpening = true)
redrawGrid()
} else {
closeFolder()
}
} }
fun closeFolder(redraw: Boolean = false) { fun closeFolder(redraw: Boolean = false) {
currentlyOpenFolder = null currentlyOpenFolder?.animateClosing {
if (redraw) { currentlyOpenFolder = null
redrawGrid() if (redraw) {
redrawGrid()
}
} }
} }
@ -1502,101 +1526,158 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel
private fun ArrayList<HomeScreenGridItem>.filterVisibleOnly() = filter { (it.page == currentPage || it.docked) && it.parentId == null } private fun ArrayList<HomeScreenGridItem>.filterVisibleOnly() = filter { (it.page == currentPage || it.docked) && it.parentId == null }
private fun HomeScreenGridItem.getFolderItems() = private fun HomeScreenGridItem.toFolder(animateOpening: Boolean = false) = HomeScreenFolder(this, animateOpening)
gridItems.filter { (it.drawable != null && it.type == ITEM_TYPE_ICON || it.type == ITEM_TYPE_SHORTCUT) && it.parentId == id }
private fun HomeScreenGridItem.generateFolderDrawable(): Drawable? { private inner class HomeScreenFolder(
if (iconSize == 0) { val item: HomeScreenGridItem,
return null animateOpening: Boolean
} ) {
var scale: Float = 1f
val bitmap = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888) init {
val canvas = Canvas(bitmap) if (animateOpening) {
val circlePath = Path().apply { addCircle((iconSize / 2).toFloat(), (iconSize / 2).toFloat(), (iconSize / 2).toFloat(), Path.Direction.CCW) } scale = 0f
canvas.clipPath(circlePath) post {
canvas.drawPaint(folderIconBackgroundPaint) ValueAnimator.ofFloat(0f, 1f)
val items = getFolderItems() .apply {
val itemsCount = getFolderItems().count() interpolator = DecelerateInterpolator()
val folderColumnCount = ceil(sqrt(itemsCount.toDouble())).roundToInt() addUpdateListener {
val folderRowCount = ceil(itemsCount.toFloat() / folderColumnCount).roundToInt() scale = it.animatedValue as Float
val scaledCellSize = (iconSize.toFloat() / folderColumnCount) redrawGrid()
val scaledGap = scaledCellSize / 5f }
val scaledIconSize = (iconSize - (folderColumnCount + 1) * scaledGap) / folderColumnCount addListener(object : AnimatorListenerAdapter() {
val extraYMargin = if (folderRowCount < folderColumnCount) (scaledIconSize + scaledGap) / 2 else 0f override fun onAnimationEnd(animation: Animator) {
items.forEach { super.onAnimationEnd(animation)
val (row, column) = it.getPositionInFolder(this@generateFolderDrawable) scale = 1f
val drawableX = (scaledGap + column * scaledIconSize + column * scaledGap).toInt() redrawGrid()
val drawableY = (extraYMargin + scaledGap + row * scaledIconSize + row * scaledGap).toInt() }
it.drawable?.setBounds(drawableX, drawableY, drawableX + scaledIconSize.toInt(), drawableY + scaledIconSize.toInt()) })
it.drawable?.draw(canvas) duration = FOLDER_ANIMATION_DURATION
} start()
canvas.drawPath(circlePath, folderIconBorderPaint) }
return BitmapDrawable(resources, bitmap) }
}
private fun HomeScreenGridItem.getFolderRect(): RectF {
val count = getFolderItems().count()
if (count == 0) {
return RectF(0f, 0f, 0f, 0f)
}
val columnsCount = ceil(sqrt(count.toDouble())).toInt()
val rowsCount = ceil(count.toFloat() / columnsCount).toInt()
val centerX = cellXCoords[left] + cellWidth / 2 + sideMargins.left
val centerY = cellYCoords[top] + cellHeight / 2 + sideMargins.top
val folderDialogWidth = (columnsCount * cellWidth + 2 * folderPadding)
val folderDialogHeight = (rowsCount * cellHeight + 3 * folderPadding + folderTitleTextPaint.textSize)
var folderDialogTop = centerY - folderDialogHeight / 2
var folderDialogLeft = centerX - folderDialogWidth / 2
if (folderDialogLeft < this@HomeScreenGrid.left + sideMargins.left) {
folderDialogLeft += this@HomeScreenGrid.left + sideMargins.left - folderDialogLeft
}
if (folderDialogLeft + folderDialogWidth > this@HomeScreenGrid.right - sideMargins.right) {
folderDialogLeft -= folderDialogLeft + folderDialogWidth - (this@HomeScreenGrid.right - sideMargins.right)
}
if (folderDialogTop < this@HomeScreenGrid.top + sideMargins.top) {
folderDialogTop += this@HomeScreenGrid.top + sideMargins.top - folderDialogTop
}
if (folderDialogTop + folderDialogHeight > this@HomeScreenGrid.bottom - sideMargins.bottom) {
folderDialogTop -= folderDialogTop + folderDialogHeight - (this@HomeScreenGrid.bottom - sideMargins.bottom)
}
return RectF(folderDialogLeft, folderDialogTop, folderDialogLeft + folderDialogWidth, folderDialogTop + folderDialogHeight)
}
private fun HomeScreenGridItem.getFolderItemsRect(): RectF {
val folderRect = getFolderRect()
return RectF(
folderRect.left + folderPadding,
folderRect.top + folderPadding * 2 + folderTitleTextPaint.textSize,
folderRect.right - folderPadding,
folderRect.bottom - folderPadding
)
}
private fun HomeScreenGridItem.getFolderGridCenters(): List<Triple<Int, Int, Int>> {
val count = getFolderItems().count()
val columnsCount = ceil(sqrt(count.toDouble())).roundToInt()
val rowsCount = ceil(count.toFloat() / columnsCount).roundToInt()
val folderItemsRect = getFolderItemsRect()
return (0 until columnsCount * rowsCount)
.toList()
.map { Pair(it % columnsCount, it / columnsCount) }
.mapIndexed { index, (x, y) ->
Triple(
index,
(folderItemsRect.left + x * cellWidth + cellWidth / 2).toInt(),
(folderItemsRect.top + y * cellHeight + cellHeight / 2).toInt()
)
} }
} }
private fun HomeScreenGridItem.getPositionInFolder(folder: HomeScreenGridItem): Pair<Int, Int> { fun getItems() =
val count = folder.getFolderItems().count() gridItems.filter { (it.drawable != null && it.type == ITEM_TYPE_ICON || it.type == ITEM_TYPE_SHORTCUT) && it.parentId == item.id }
val columnsCount = ceil(sqrt(count.toDouble())).roundToInt()
val column = left % columnsCount fun generateDrawable(): Drawable? {
val row = left / columnsCount if (iconSize == 0) {
return Pair(row, column) return null
}
val bitmap = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
val circlePath = Path().apply { addCircle((iconSize / 2).toFloat(), (iconSize / 2).toFloat(), (iconSize / 2).toFloat(), Path.Direction.CCW) }
canvas.clipPath(circlePath)
canvas.drawPaint(folderIconBackgroundPaint)
val items = getItems()
val itemsCount = getItems().count()
val folderColumnCount = ceil(sqrt(itemsCount.toDouble())).roundToInt()
val folderRowCount = ceil(itemsCount.toFloat() / folderColumnCount).roundToInt()
val scaledCellSize = (iconSize.toFloat() / folderColumnCount)
val scaledGap = scaledCellSize / 5f
val scaledIconSize = (iconSize - (folderColumnCount + 1) * scaledGap) / folderColumnCount
val extraYMargin = if (folderRowCount < folderColumnCount) (scaledIconSize + scaledGap) / 2 else 0f
items.forEach {
val (row, column) = getItemPosition(it)
val drawableX = (scaledGap + column * scaledIconSize + column * scaledGap).toInt()
val drawableY = (extraYMargin + scaledGap + row * scaledIconSize + row * scaledGap).toInt()
it.drawable?.setBounds(drawableX, drawableY, drawableX + scaledIconSize.toInt(), drawableY + scaledIconSize.toInt())
it.drawable?.draw(canvas)
}
canvas.drawPath(circlePath, folderIconBorderPaint)
return BitmapDrawable(resources, bitmap)
}
fun getDrawingRect(overrideScale: Float? = null): RectF {
val finalScale = overrideScale ?: scale
val count = getItems().count()
if (count == 0) {
return RectF(0f, 0f, 0f, 0f)
}
val columnsCount = ceil(sqrt(count.toDouble())).toInt()
val rowsCount = ceil(count.toFloat() / columnsCount).toInt()
val centerX = cellXCoords[item.left] + cellWidth / 2 + sideMargins.left
val centerY = cellYCoords[item.top] + cellHeight / 2 + sideMargins.top
val folderDialogWidth = (columnsCount * cellWidth + 2 * folderPadding) * finalScale
val folderDialogHeight = (rowsCount * cellHeight + 3 * folderPadding + folderTitleTextPaint.textSize) * finalScale
var folderDialogTop = centerY - folderDialogHeight / 2
var folderDialogLeft = centerX - folderDialogWidth / 2
if (folderDialogLeft < left + sideMargins.left) {
folderDialogLeft += left + sideMargins.left - folderDialogLeft
}
if (folderDialogLeft + folderDialogWidth > right - sideMargins.right) {
folderDialogLeft -= folderDialogLeft + folderDialogWidth - (right - sideMargins.right)
}
if (folderDialogTop < top + sideMargins.top) {
folderDialogTop += top + sideMargins.top - folderDialogTop
}
if (folderDialogTop + folderDialogHeight > bottom - sideMargins.bottom) {
folderDialogTop -= folderDialogTop + folderDialogHeight - (bottom - sideMargins.bottom)
}
return RectF(folderDialogLeft, folderDialogTop, folderDialogLeft + folderDialogWidth, folderDialogTop + folderDialogHeight)
}
fun getItemsDrawingRect(): RectF {
val folderRect = getDrawingRect(overrideScale = 1f)
return RectF(
folderRect.left + folderPadding,
folderRect.top + folderPadding * 2 + folderTitleTextPaint.textSize,
folderRect.right - folderPadding,
folderRect.bottom - folderPadding
)
}
fun getItemsGridCenters(): List<Triple<Int, Int, Int>> {
val count = getItems().count()
val columnsCount = ceil(sqrt(count.toDouble())).roundToInt()
val rowsCount = ceil(count.toFloat() / columnsCount).roundToInt()
val folderItemsRect = getItemsDrawingRect()
return (0 until columnsCount * rowsCount)
.toList()
.map { Pair(it % columnsCount, it / columnsCount) }
.mapIndexed { index, (x, y) ->
Triple(
index,
(folderItemsRect.left + x * cellWidth + cellWidth / 2).toInt(),
(folderItemsRect.top + y * cellHeight + cellHeight / 2).toInt()
)
}
}
fun getItemPosition(item: HomeScreenGridItem): Pair<Int, Int> {
val count = getItems().count()
val columnsCount = ceil(sqrt(count.toDouble())).roundToInt()
val column = item.left % columnsCount
val row = item.left / columnsCount
return Pair(row, column)
}
fun animateClosing(callback: () -> Unit) {
post {
ValueAnimator.ofFloat(scale, 0.2f)
.apply {
interpolator = DecelerateInterpolator()
addUpdateListener {
scale = it.animatedValue as Float
redrawGrid()
}
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
super.onAnimationEnd(animation)
scale = 0.2f
callback()
}
})
duration = (FOLDER_ANIMATION_DURATION * (max(0f, scale - 0.2f))).toLong()
start()
}
}
}
} }
companion object { companion object {
@ -1604,6 +1685,7 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel
private const val PAGE_INDICATORS_FADE_DELAY = PAGE_CHANGE_HOLD_THRESHOLD + 300L private const val PAGE_INDICATORS_FADE_DELAY = PAGE_CHANGE_HOLD_THRESHOLD + 300L
private const val FOLDER_OPEN_HOLD_THRESHOLD = 500L private const val FOLDER_OPEN_HOLD_THRESHOLD = 500L
private const val FOLDER_CLOSE_HOLD_THRESHOLD = 300L private const val FOLDER_CLOSE_HOLD_THRESHOLD = 300L
private const val FOLDER_ANIMATION_DURATION = 200L
private enum class PageChangeArea { private enum class PageChangeArea {
LEFT, LEFT,
@ -1612,3 +1694,4 @@ class HomeScreenGrid(context: Context, attrs: AttributeSet, defStyle: Int) : Rel
} }
} }
} }