feature(live-room-like-heart): 롱프레스 왕하트 애니메이션 변경
- 기존: 하트가 33.3dp 부터 커지는 애니메이션 - 변경: 하트가 133.3dp으로 고정되어 있고 물 채우기 애니메이션
This commit is contained in:
@@ -232,6 +232,8 @@ dependencies {
|
|||||||
|
|
||||||
implementation 'io.github.glailton.expandabletextview:expandabletextview:1.0.4'
|
implementation 'io.github.glailton.expandabletextview:expandabletextview:1.0.4'
|
||||||
|
|
||||||
|
implementation 'com.github.orbitalsonic:Sonic-Water-Wave-Animation:2.0.1'
|
||||||
|
|
||||||
// ----- Test dependencies -----
|
// ----- Test dependencies -----
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation 'junit:junit:4.13.2'
|
||||||
testImplementation 'org.mockito:mockito-core:5.20.0'
|
testImplementation 'org.mockito:mockito-core:5.20.0'
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import android.text.method.LinkMovementMethod
|
|||||||
import android.text.style.ClickableSpan
|
import android.text.style.ClickableSpan
|
||||||
import android.text.style.ForegroundColorSpan
|
import android.text.style.ForegroundColorSpan
|
||||||
import android.text.style.StyleSpan
|
import android.text.style.StyleSpan
|
||||||
import android.view.Gravity
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@@ -38,6 +37,7 @@ import androidx.activity.OnBackPressedCallback
|
|||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.widget.PopupMenu
|
import androidx.appcompat.widget.PopupMenu
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.graphics.toColorInt
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
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.RequestListener
|
||||||
import com.bumptech.glide.request.target.Target
|
import com.bumptech.glide.request.target.Target
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
|
import com.orbitalsonic.waterwave.WaterWaveView
|
||||||
import com.orhanobut.logger.Logger
|
import com.orhanobut.logger.Logger
|
||||||
import io.agora.rtc2.ClientRoleOptions
|
import io.agora.rtc2.ClientRoleOptions
|
||||||
import io.agora.rtc2.IRtcEngineEventHandler
|
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
|
private var isLongPressTriggered = false
|
||||||
@@ -1881,18 +1882,13 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
|
|||||||
private val longPressDurationMs = 2000L
|
private val longPressDurationMs = 2000L
|
||||||
private val centerHeartShowDelayMs = 500L
|
private val centerHeartShowDelayMs = 500L
|
||||||
|
|
||||||
private val longPressScaleDurationMs get() = longPressDurationMs
|
// 0.5초 후 중앙 하트 표시 및 물 채우기(progress) 업데이트 시작을 위한 러너블
|
||||||
private val longPressScaleStart = 1.0f
|
|
||||||
private val longPressScaleMax = 4.0f
|
|
||||||
|
|
||||||
// 0.5초 후 중앙 하트 표시 및 스케일 업데이트 시작을 위한 러너블
|
|
||||||
private val showCenterHeartRunnable = Runnable {
|
private val showCenterHeartRunnable = Runnable {
|
||||||
if (!isTrackingLongPress) return@Runnable
|
if (!isTrackingLongPress) return@Runnable
|
||||||
if (isLongPressBlockedByAvailability) return@Runnable
|
if (isLongPressBlockedByAvailability) return@Runnable
|
||||||
if (longPressCenterHeart != null) return@Runnable
|
if (longPressCenterHeart != null) return@Runnable
|
||||||
showCenterHeartForLongPress()
|
showCenterHeartForLongPress()
|
||||||
longPressVisualStartTime = System.currentTimeMillis()
|
longPressVisualStartTime = System.currentTimeMillis()
|
||||||
updateCenterHeartScale(longPressScaleStart)
|
|
||||||
handler.post(longPressScaleUpdateRunnable)
|
handler.post(longPressScaleUpdateRunnable)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1905,18 +1901,29 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
|
|||||||
if (isLongPressBlockedByAvailability) return
|
if (isLongPressBlockedByAvailability) return
|
||||||
val elapsed = System.currentTimeMillis() - pressStartTime
|
val elapsed = System.currentTimeMillis() - pressStartTime
|
||||||
|
|
||||||
// 스케일은 더 빠르게 커지도록 별도의 duration 기준으로 계산(최대치 도달 후 유지)
|
// 경과 시간 기준으로 물 채우기(progress) 계산 (0..100)
|
||||||
val scaleFraction = (elapsed.coerceAtMost(longPressScaleDurationMs)
|
val progressFraction = (elapsed.coerceAtMost(longPressDurationMs)
|
||||||
.toFloat() / longPressScaleDurationMs.toFloat())
|
.toFloat() / longPressDurationMs.toFloat())
|
||||||
val scale =
|
val progress = (progressFraction * 100f).toInt()
|
||||||
longPressScaleStart + (longPressScaleMax - longPressScaleStart) * scaleFraction
|
longPressCenterHeart?.let { heartView ->
|
||||||
updateCenterHeartScale(scale)
|
try {
|
||||||
|
heartView.progress = progress
|
||||||
|
} catch (_: Throwable) {
|
||||||
|
// 일부 버전에서 setter 명이 다를 수 있어 안전 처리
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 2초 유지 시 트리거 실행
|
// 2초 유지 시 트리거 실행
|
||||||
if (elapsed >= longPressDurationMs && !isLongPressTriggered) {
|
if (elapsed >= longPressDurationMs && !isLongPressTriggered) {
|
||||||
isLongPressTriggered = true
|
isLongPressTriggered = true
|
||||||
isTrackingLongPress = false
|
isTrackingLongPress = false
|
||||||
removeCenterHeartForLongPress(withFade = true)
|
// 최종 100% 보정
|
||||||
|
longPressCenterHeart?.let { hv ->
|
||||||
|
try {
|
||||||
|
hv.progress = 100
|
||||||
|
} catch (_: Throwable) {
|
||||||
|
}
|
||||||
|
}
|
||||||
triggerBigHeartDonation()
|
triggerBigHeartDonation()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1994,26 +2001,22 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
|
|||||||
|
|
||||||
private fun showCenterHeartForLongPress() {
|
private fun showCenterHeartForLongPress() {
|
||||||
if (longPressCenterHeart != null) return
|
if (longPressCenterHeart != null) return
|
||||||
val size = 33.3f.dpToPx().toInt()
|
val heart = binding.heartWave
|
||||||
val lp = FrameLayout.LayoutParams(size, size).apply { gravity = Gravity.CENTER }
|
heart.setBorderColor("#ff959a".toColorInt())
|
||||||
val heart = ImageView(this).apply {
|
heart.setFrontWaveColor("#ff959a".toColorInt())
|
||||||
setImageResource(R.drawable.ic_heart_pink)
|
heart.setBehindWaveColor("#ff959a".toColorInt())
|
||||||
layoutParams = lp
|
val elapsed = System.currentTimeMillis() - pressStartTime
|
||||||
scaleX = 1f
|
val progress = ((elapsed.coerceAtMost(longPressDurationMs)
|
||||||
scaleY = 1f
|
.toFloat() / longPressDurationMs.toFloat()) * 100f).toInt()
|
||||||
alpha = 1f
|
try {
|
||||||
|
heart.progress = progress
|
||||||
|
} catch (_: Throwable) {
|
||||||
}
|
}
|
||||||
binding.flRoot.addView(heart)
|
heart.alpha = 1f
|
||||||
|
heart.visibility = View.VISIBLE
|
||||||
longPressCenterHeart = heart
|
longPressCenterHeart = heart
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateCenterHeartScale(scale: Float) {
|
|
||||||
longPressCenterHeart?.let {
|
|
||||||
it.scaleX = scale
|
|
||||||
it.scaleY = scale
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun removeCenterHeartForLongPress(withFade: Boolean = false) {
|
private fun removeCenterHeartForLongPress(withFade: Boolean = false) {
|
||||||
val heart = longPressCenterHeart ?: return
|
val heart = longPressCenterHeart ?: return
|
||||||
longPressCenterHeart = null
|
longPressCenterHeart = null
|
||||||
@@ -2021,10 +2024,21 @@ class LiveRoomActivity : BaseActivity<ActivityLiveRoomBinding>(ActivityLiveRoomB
|
|||||||
heart.animate()
|
heart.animate()
|
||||||
.alpha(0f)
|
.alpha(0f)
|
||||||
.setDuration(150L)
|
.setDuration(150L)
|
||||||
.withEndAction { binding.flRoot.removeView(heart) }
|
.withEndAction {
|
||||||
|
heart.visibility = View.GONE
|
||||||
|
try {
|
||||||
|
heart.progress = 0
|
||||||
|
} catch (_: Throwable) {
|
||||||
|
}
|
||||||
|
}
|
||||||
.start()
|
.start()
|
||||||
} else {
|
} else {
|
||||||
binding.flRoot.removeView(heart)
|
heart.alpha = 1f
|
||||||
|
heart.visibility = View.GONE
|
||||||
|
try {
|
||||||
|
heart.progress = 0
|
||||||
|
} catch (_: Throwable) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -758,4 +758,17 @@
|
|||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</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>
|
</FrameLayout>
|
||||||
|
|||||||
Reference in New Issue
Block a user