Add folder opening and closing animation
This commit is contained in:
parent
7a5c698322
commit
3cc00157cc
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue