feature(live-room-like-heart): 롱프레스 왕하트 애니메이션 변경

- 기존: 하트가 33.3dp 부터 커지는 애니메이션
- 변경: 하트가 133.3dp으로 고정되어 있고 물 채우기 애니메이션
This commit is contained in:
2025-11-04 20:20:58 +09:00
parent 332bf3256c
commit 601405349e
3 changed files with 62 additions and 33 deletions

View File

@@ -232,6 +232,8 @@ dependencies {
implementation 'io.github.glailton.expandabletextview:expandabletextview:1.0.4'
implementation 'com.github.orbitalsonic:Sonic-Water-Wave-Animation:2.0.1'
// ----- Test dependencies -----
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito:mockito-core:5.20.0'

View File

@@ -23,7 +23,6 @@ import android.text.method.LinkMovementMethod
import android.text.style.ClickableSpan
import android.text.style.ForegroundColorSpan
import android.text.style.StyleSpan
import android.view.Gravity
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
@@ -38,6 +37,7 @@ import androidx.activity.OnBackPressedCallback
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.widget.PopupMenu
import androidx.core.content.ContextCompat
import androidx.core.graphics.toColorInt
import androidx.core.net.toUri
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
@@ -49,6 +49,7 @@ import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import com.google.gson.Gson
import com.orbitalsonic.waterwave.WaterWaveView
import com.orhanobut.logger.Logger
import io.agora.rtc2.ClientRoleOptions
import io.agora.rtc2.IRtcEngineEventHandler
@@ -1869,7 +1870,7 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
}
}
private var longPressCenterHeart: ImageView? = null
private var longPressCenterHeart: WaterWaveView? = null
// 롱프레스 상태/파라미터
private var isLongPressTriggered = false
@@ -1881,18 +1882,13 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
private val longPressDurationMs = 2000L
private val centerHeartShowDelayMs = 500L
private val longPressScaleDurationMs get() = longPressDurationMs
private val longPressScaleStart = 1.0f
private val longPressScaleMax = 4.0f
// 0.5초 후 중앙 하트 표시 및 스케일 업데이트 시작을 위한 러너블
// 0.5초 후 중앙 하트 표시 및 물 채우기(progress) 업데이트 시작을 위한 러너블
private val showCenterHeartRunnable = Runnable {
if (!isTrackingLongPress) return@Runnable
if (isLongPressBlockedByAvailability) return@Runnable
if (longPressCenterHeart != null) return@Runnable
showCenterHeartForLongPress()
longPressVisualStartTime = System.currentTimeMillis()
updateCenterHeartScale(longPressScaleStart)
handler.post(longPressScaleUpdateRunnable)
}
@@ -1905,18 +1901,29 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
if (isLongPressBlockedByAvailability) return
val elapsed = System.currentTimeMillis() - pressStartTime
// 스케일은 더 빠르게 커지도록 별도의 duration 기준으로 계산(최대치 도달 후 유지)
val scaleFraction = (elapsed.coerceAtMost(longPressScaleDurationMs)
.toFloat() / longPressScaleDurationMs.toFloat())
val scale =
longPressScaleStart + (longPressScaleMax - longPressScaleStart) * scaleFraction
updateCenterHeartScale(scale)
// 경과 시간 기준으로 물 채우기(progress) 계산 (0..100)
val progressFraction = (elapsed.coerceAtMost(longPressDurationMs)
.toFloat() / longPressDurationMs.toFloat())
val progress = (progressFraction * 100f).toInt()
longPressCenterHeart?.let { heartView ->
try {
heartView.progress = progress
} catch (_: Throwable) {
// 일부 버전에서 setter 명이 다를 수 있어 안전 처리
}
}
// 2초 유지 시 트리거 실행
if (elapsed >= longPressDurationMs && !isLongPressTriggered) {
isLongPressTriggered = true
isTrackingLongPress = false
removeCenterHeartForLongPress(withFade = true)
// 최종 100% 보정
longPressCenterHeart?.let { hv ->
try {
hv.progress = 100
} catch (_: Throwable) {
}
}
triggerBigHeartDonation()
return
}
@@ -1994,26 +2001,22 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
private fun showCenterHeartForLongPress() {
if (longPressCenterHeart != null) return
val size = 33.3f.dpToPx().toInt()
val lp = FrameLayout.LayoutParams(size, size).apply { gravity = Gravity.CENTER }
val heart = ImageView(this).apply {
setImageResource(R.drawable.ic_heart_pink)
layoutParams = lp
scaleX = 1f
scaleY = 1f
alpha = 1f
val heart = binding.heartWave
heart.setBorderColor("#ff959a".toColorInt())
heart.setFrontWaveColor("#ff959a".toColorInt())
heart.setBehindWaveColor("#ff959a".toColorInt())
val elapsed = System.currentTimeMillis() - pressStartTime
val progress = ((elapsed.coerceAtMost(longPressDurationMs)
.toFloat() / longPressDurationMs.toFloat()) * 100f).toInt()
try {
heart.progress = progress
} catch (_: Throwable) {
}
binding.flRoot.addView(heart)
heart.alpha = 1f
heart.visibility = View.VISIBLE
longPressCenterHeart = heart
}
private fun updateCenterHeartScale(scale: Float) {
longPressCenterHeart?.let {
it.scaleX = scale
it.scaleY = scale
}
}
private fun removeCenterHeartForLongPress(withFade: Boolean = false) {
val heart = longPressCenterHeart ?: return
longPressCenterHeart = null
@@ -2021,10 +2024,21 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
heart.animate()
.alpha(0f)
.setDuration(150L)
.withEndAction { binding.flRoot.removeView(heart) }
.withEndAction {
heart.visibility = View.GONE
try {
heart.progress = 0
} catch (_: Throwable) {
}
}
.start()
} else {
binding.flRoot.removeView(heart)
heart.alpha = 1f
heart.visibility = View.GONE
try {
heart.progress = 0
} catch (_: Throwable) {
}
}
}

View File

@@ -758,4 +758,17 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<com.orbitalsonic.waterwave.WaterWaveView
android:id="@+id/heart_wave"
android:layout_width="133.3dp"
android:layout_height="133.3dp"
android:layout_gravity="center"
android:visibility="gone"
app:max="100"
app:progress="0"
app:shapeType="heart"
app:textHidden="true"
app:textColor="@color/white" />
</FrameLayout>