mirror of
https://github.com/cmclark00/mintris.git
synced 2025-05-18 07:45:21 +01:00
fix: Improve XP bar animation smoothness and text sync
This commit is contained in:
parent
eb962268ca
commit
501e5b37fc
1 changed files with 86 additions and 49 deletions
|
@ -1,5 +1,7 @@
|
||||||
package com.pixelmintdrop.ui
|
package com.pixelmintdrop.ui
|
||||||
|
|
||||||
|
import android.animation.Animator
|
||||||
|
import android.animation.AnimatorListenerAdapter
|
||||||
import android.animation.ValueAnimator
|
import android.animation.ValueAnimator
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
|
@ -55,17 +57,19 @@ class XPProgressBar @JvmOverloads constructor(
|
||||||
private val backgroundRect = RectF()
|
private val backgroundRect = RectF()
|
||||||
private val cornerRadius = 25f
|
private val cornerRadius = 25f
|
||||||
|
|
||||||
// Progress animation
|
// Progress animation state
|
||||||
private var progressAnimator: ValueAnimator? = null
|
private var progressAnimator: ValueAnimator? = null
|
||||||
private var currentProgress = 0f
|
private var currentProgress = 0f // Current visual progress percentage (0-100)
|
||||||
private var targetProgress = 0f
|
private var startProgress = 0f // Progress percentage at animation start
|
||||||
|
private var targetProgress = 0f // Progress percentage at animation end
|
||||||
|
|
||||||
// XP values
|
// XP values state
|
||||||
private var currentXP = 0L
|
private var currentXP = 0L // Final XP value for the current level after gain
|
||||||
private var xpForNextLevel = 100L
|
private var xpForNextLevel = 100L // XP needed for the next level (can change on level up)
|
||||||
private var playerLevel = 1
|
private var playerLevel = 1 // Final player level after gain
|
||||||
|
private var animatingStartXP = 0L // XP value when animation started
|
||||||
|
|
||||||
// Level up animation
|
// Level up animation state
|
||||||
private var isLevelingUp = false
|
private var isLevelingUp = false
|
||||||
private var levelUpAnimator: ValueAnimator? = null
|
private var levelUpAnimator: ValueAnimator? = null
|
||||||
private var levelBadgeScale = 1f
|
private var levelBadgeScale = 1f
|
||||||
|
@ -74,19 +78,21 @@ class XPProgressBar @JvmOverloads constructor(
|
||||||
private var themeColor = Color.WHITE
|
private var themeColor = Color.WHITE
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the player's current level and XP values
|
* Set the player's current level and XP values (typically called for initial state)
|
||||||
*/
|
*/
|
||||||
fun setXPValues(level: Int, currentXP: Long, xpForNextLevel: Long) {
|
fun setXPValues(level: Int, currentXP: Long, xpForNextLevel: Long) {
|
||||||
this.playerLevel = level
|
this.playerLevel = level
|
||||||
this.currentXP = currentXP
|
this.currentXP = currentXP
|
||||||
this.xpForNextLevel = xpForNextLevel
|
this.xpForNextLevel = xpForNextLevel
|
||||||
|
this.animatingStartXP = currentXP // Assume start XP is current XP if not animating
|
||||||
|
|
||||||
// Update progress value
|
// Calculate progress based on these initial values
|
||||||
targetProgress = calculateProgressPercentage()
|
this.targetProgress = calculateProgressPercentage(currentXP, xpForNextLevel)
|
||||||
|
|
||||||
// If not animating, set current progress immediately
|
// If not animating, set current progress immediately
|
||||||
if (progressAnimator == null || !progressAnimator!!.isRunning) {
|
if (progressAnimator == null || !progressAnimator!!.isRunning) {
|
||||||
currentProgress = targetProgress
|
this.currentProgress = this.targetProgress
|
||||||
|
this.startProgress = this.targetProgress // Ensure start=target if not animating
|
||||||
}
|
}
|
||||||
|
|
||||||
invalidate()
|
invalidate()
|
||||||
|
@ -107,46 +113,61 @@ class XPProgressBar @JvmOverloads constructor(
|
||||||
* Animate adding XP to the bar
|
* Animate adding XP to the bar
|
||||||
*/
|
*/
|
||||||
fun animateXPGain(xpGained: Long, newLevel: Int, newCurrentXP: Long, newXPForNextLevel: Long) {
|
fun animateXPGain(xpGained: Long, newLevel: Int, newCurrentXP: Long, newXPForNextLevel: Long) {
|
||||||
// Store original values before animation
|
// Store values *before* the gain for animation reference
|
||||||
val startXP = currentXP
|
val initialXP = this.currentXP
|
||||||
val startLevel = playerLevel
|
val initialXPForNext = this.xpForNextLevel
|
||||||
|
val initialLevel = this.playerLevel
|
||||||
|
|
||||||
// Calculate percentage before XP gain
|
// Calculate start and target percentages
|
||||||
val startProgress = calculateProgressPercentage()
|
this.startProgress = calculateProgressPercentage(initialXP, initialXPForNext)
|
||||||
|
this.targetProgress = calculateProgressPercentage(newCurrentXP, newXPForNextLevel)
|
||||||
|
|
||||||
// Update to new values
|
// Store the starting XP for text interpolation
|
||||||
playerLevel = newLevel
|
this.animatingStartXP = initialXP
|
||||||
currentXP = newCurrentXP
|
|
||||||
xpForNextLevel = newXPForNextLevel
|
|
||||||
|
|
||||||
// Calculate new target progress
|
// Update member variables to the *final* state
|
||||||
targetProgress = calculateProgressPercentage()
|
this.playerLevel = newLevel
|
||||||
|
this.currentXP = newCurrentXP
|
||||||
|
this.xpForNextLevel = newXPForNextLevel
|
||||||
|
|
||||||
// Determine if level up occurred
|
// Determine if level up occurred
|
||||||
isLevelingUp = startLevel < newLevel
|
isLevelingUp = initialLevel < newLevel
|
||||||
|
|
||||||
// Animate progress bar
|
// Animate progress bar percentage
|
||||||
progressAnimator?.cancel()
|
progressAnimator?.cancel()
|
||||||
progressAnimator = ValueAnimator.ofFloat(startProgress, targetProgress).apply {
|
progressAnimator = ValueAnimator.ofFloat(startProgress, targetProgress).apply {
|
||||||
duration = 1500 // 1.5 seconds animation
|
duration = 1500 // 1.5 seconds animation
|
||||||
interpolator = AccelerateDecelerateInterpolator()
|
interpolator = AccelerateDecelerateInterpolator()
|
||||||
|
|
||||||
addUpdateListener { animation ->
|
addUpdateListener { animation ->
|
||||||
|
// Update the visual progress percentage
|
||||||
currentProgress = animation.animatedValue as Float
|
currentProgress = animation.animatedValue as Float
|
||||||
invalidate()
|
invalidate() // Trigger redraw on each frame
|
||||||
}
|
}
|
||||||
|
|
||||||
// When animation completes, trigger level up animation if needed
|
// Add listener to handle animation end and level up effect
|
||||||
|
addListener(object : AnimatorListenerAdapter() {
|
||||||
|
override fun onAnimationEnd(animation: Animator) {
|
||||||
|
// Ensure final state is precise
|
||||||
|
currentProgress = targetProgress
|
||||||
|
// Trigger level up visual pulse *after* bar animation completes
|
||||||
if (isLevelingUp) {
|
if (isLevelingUp) {
|
||||||
levelUpAnimation()
|
levelUpAnimation()
|
||||||
}
|
}
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
start()
|
start()
|
||||||
}
|
}
|
||||||
|
// Set the currentProgress to startProgress before starting animation
|
||||||
|
// (ValueAnimator starts from its current value, ensure it starts from startProgress)
|
||||||
|
// currentProgress = startProgress // REMOVED
|
||||||
|
// invalidate() // Initial draw at start state // REMOVED
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a level up animation effect
|
* Create a level up animation effect (pulse the level badge)
|
||||||
*/
|
*/
|
||||||
private fun levelUpAnimation() {
|
private fun levelUpAnimation() {
|
||||||
levelUpAnimator?.cancel()
|
levelUpAnimator?.cancel()
|
||||||
|
@ -158,20 +179,19 @@ class XPProgressBar @JvmOverloads constructor(
|
||||||
levelBadgeScale = animation.animatedValue as Float
|
levelBadgeScale = animation.animatedValue as Float
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
start()
|
start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate the current progress percentage
|
* Calculate the progress percentage given specific XP values
|
||||||
*/
|
*/
|
||||||
private fun calculateProgressPercentage(): Float {
|
private fun calculateProgressPercentage(xp: Long, maxXP: Long): Float {
|
||||||
return if (xpForNextLevel > 0) {
|
return if (maxXP > 0) {
|
||||||
(currentXP.toFloat() / xpForNextLevel.toFloat()) * 100f
|
(xp.toFloat() / maxXP.toFloat()) * 100f
|
||||||
} else {
|
} else {
|
||||||
0f
|
0f // Avoid division by zero, return 0%
|
||||||
}
|
}.coerceIn(0f, 100f) // Ensure percentage is always between 0 and 100
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
||||||
|
@ -179,9 +199,8 @@ class XPProgressBar @JvmOverloads constructor(
|
||||||
|
|
||||||
// Update progress bar dimensions based on view size
|
// Update progress bar dimensions based on view size
|
||||||
val verticalPadding = h * 0.2f
|
val verticalPadding = h * 0.2f
|
||||||
// Increase left margin to prevent level badge from being cut off
|
|
||||||
backgroundRect.set(
|
backgroundRect.set(
|
||||||
h * 0.6f, // Increased from 0.5f to 0.6f for more space
|
h * 0.6f,
|
||||||
verticalPadding,
|
verticalPadding,
|
||||||
w - paddingRight.toFloat(),
|
w - paddingRight.toFloat(),
|
||||||
h - verticalPadding
|
h - verticalPadding
|
||||||
|
@ -195,14 +214,14 @@ class XPProgressBar @JvmOverloads constructor(
|
||||||
override fun onDraw(canvas: Canvas) {
|
override fun onDraw(canvas: Canvas) {
|
||||||
super.onDraw(canvas)
|
super.onDraw(canvas)
|
||||||
|
|
||||||
// Draw level badge with adjusted position
|
// Draw level badge (pulses on level up)
|
||||||
val badgeRadius = height * 0.3f * levelBadgeScale
|
val badgeRadius = height * 0.3f * levelBadgeScale
|
||||||
val badgeCenterX = height * 0.35f // Adjusted from 0.25f to 0.35f to match new position
|
val badgeCenterX = height * 0.35f
|
||||||
val badgeCenterY = height * 0.5f
|
val badgeCenterY = height * 0.5f
|
||||||
|
|
||||||
canvas.drawCircle(badgeCenterX, badgeCenterY, badgeRadius, levelBadgePaint)
|
canvas.drawCircle(badgeCenterX, badgeCenterY, badgeRadius, levelBadgePaint)
|
||||||
canvas.drawText(
|
canvas.drawText(
|
||||||
playerLevel.toString(),
|
playerLevel.toString(), // Always show the final level
|
||||||
badgeCenterX,
|
badgeCenterX,
|
||||||
badgeCenterY + (levelBadgeTextPaint.textSize / 3),
|
badgeCenterY + (levelBadgeTextPaint.textSize / 3),
|
||||||
levelBadgeTextPaint
|
levelBadgeTextPaint
|
||||||
|
@ -211,32 +230,50 @@ class XPProgressBar @JvmOverloads constructor(
|
||||||
// Draw background bar
|
// Draw background bar
|
||||||
canvas.drawRoundRect(backgroundRect, cornerRadius, cornerRadius, backgroundPaint)
|
canvas.drawRoundRect(backgroundRect, cornerRadius, cornerRadius, backgroundPaint)
|
||||||
|
|
||||||
// Draw progress bar
|
// Calculate progress bar width based on animated currentProgress
|
||||||
|
val progressBarWidth = backgroundRect.width() * currentProgress / 100f
|
||||||
progressRect.set(
|
progressRect.set(
|
||||||
backgroundRect.left,
|
backgroundRect.left,
|
||||||
backgroundRect.top,
|
backgroundRect.top,
|
||||||
backgroundRect.left + (backgroundRect.width() * currentProgress / 100f),
|
backgroundRect.left + progressBarWidth,
|
||||||
backgroundRect.bottom
|
backgroundRect.bottom
|
||||||
)
|
)
|
||||||
|
|
||||||
// Only draw if there is progress to show
|
// Draw the actual progress portion
|
||||||
if (progressRect.width() > 0) {
|
if (progressRect.width() > 0) {
|
||||||
// Draw actual progress bar
|
|
||||||
canvas.drawRoundRect(progressRect, cornerRadius, cornerRadius, progressPaint)
|
canvas.drawRoundRect(progressRect, cornerRadius, cornerRadius, progressPaint)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw progress text
|
// --- Draw Progress Text ---
|
||||||
val progressText = "${currentXP}/${xpForNextLevel} XP"
|
val isAnimating = progressAnimator?.isRunning ?: false
|
||||||
|
val displayedXP: Long
|
||||||
|
val displayedMaxXP = this.xpForNextLevel // Denominator always shows the target max XP
|
||||||
|
|
||||||
|
if (isAnimating) {
|
||||||
|
// Calculate displayed XP based on the visual progress percentage (0-100)
|
||||||
|
// relative to the max XP for the current/new level.
|
||||||
|
displayedXP = (currentProgress / 100.0 * displayedMaxXP).toLong()
|
||||||
|
} else {
|
||||||
|
// When not animating, use the final accurate values
|
||||||
|
displayedXP = this.currentXP
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clamp displayed XP to be between 0 and the max for the level visually
|
||||||
|
val safeDisplayedXP = displayedXP.coerceIn(0L, displayedMaxXP)
|
||||||
|
|
||||||
|
val progressText = "${safeDisplayedXP}/${displayedMaxXP} XP"
|
||||||
canvas.drawText(
|
canvas.drawText(
|
||||||
progressText,
|
progressText,
|
||||||
backgroundRect.centerX(),
|
backgroundRect.centerX(),
|
||||||
backgroundRect.centerY() + (textPaint.textSize / 3),
|
backgroundRect.centerY() + (textPaint.textSize / 3),
|
||||||
textPaint
|
textPaint
|
||||||
)
|
)
|
||||||
|
// --- End Progress Text ---
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDetachedFromWindow() {
|
override fun onDetachedFromWindow() {
|
||||||
super.onDetachedFromWindow()
|
super.onDetachedFromWindow()
|
||||||
|
// Clean up animators to prevent leaks
|
||||||
progressAnimator?.cancel()
|
progressAnimator?.cancel()
|
||||||
levelUpAnimator?.cancel()
|
levelUpAnimator?.cancel()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue