More work on animation views

This commit is contained in:
fanchao 2024-05-27 13:18:51 +10:00
parent c0128b88de
commit 2d7f23a2fb
3 changed files with 67 additions and 45 deletions

View File

@ -1024,7 +1024,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
} }
override fun showVoiceMessageUI() { override fun showVoiceMessageUI() {
binding?.inputBarRecordingView?.show() binding?.inputBarRecordingView?.show(lifecycleScope)
binding?.inputBar?.alpha = 0.0f binding?.inputBar?.alpha = 0.0f
val animation = ValueAnimator.ofObject(FloatEvaluator(), 1.0f, 0.0f) val animation = ValueAnimator.ofObject(FloatEvaluator(), 1.0f, 0.0f)
animation.duration = 250L animation.duration = 250L

View File

@ -12,6 +12,11 @@ import android.widget.RelativeLayout
import android.widget.TextView import android.widget.TextView
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import network.loki.messenger.R import network.loki.messenger.R
import network.loki.messenger.databinding.ViewInputBarRecordingBinding import network.loki.messenger.databinding.ViewInputBarRecordingBinding
import org.thoughtcrime.securesms.util.DateUtils import org.thoughtcrime.securesms.util.DateUtils
@ -26,9 +31,7 @@ class InputBarRecordingView : RelativeLayout {
private var dotViewAnimation: ValueAnimator? = null private var dotViewAnimation: ValueAnimator? = null
private var pulseAnimation: ValueAnimator? = null private var pulseAnimation: ValueAnimator? = null
var delegate: InputBarRecordingViewDelegate? = null var delegate: InputBarRecordingViewDelegate? = null
private val updateTimerRunnable = Runnable { private var timerJob: Job? = null
updateTimer()
}
val lockView: LinearLayout val lockView: LinearLayout
get() = binding.lockView get() = binding.lockView
@ -50,9 +53,10 @@ class InputBarRecordingView : RelativeLayout {
binding = ViewInputBarRecordingBinding.inflate(LayoutInflater.from(context), this, true) binding = ViewInputBarRecordingBinding.inflate(LayoutInflater.from(context), this, true)
binding.inputBarMiddleContentContainer.disableClipping() binding.inputBarMiddleContentContainer.disableClipping()
binding.inputBarCancelButton.setOnClickListener { hide() } binding.inputBarCancelButton.setOnClickListener { hide() }
} }
fun show() { fun show(scope: CoroutineScope) {
startTimestamp = Date().time startTimestamp = Date().time
binding.recordButtonOverlayImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ic_microphone, context.theme)) binding.recordButtonOverlayImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ic_microphone, context.theme))
binding.inputBarCancelButton.alpha = 0.0f binding.inputBarCancelButton.alpha = 0.0f
@ -69,7 +73,7 @@ class InputBarRecordingView : RelativeLayout {
animateDotView() animateDotView()
pulse() pulse()
animateLockViewUp() animateLockViewUp()
updateTimer() startTimer(scope)
} }
fun hide() { fun hide() {
@ -86,6 +90,24 @@ class InputBarRecordingView : RelativeLayout {
} }
animation.start() animation.start()
delegate?.handleVoiceMessageUIHidden() delegate?.handleVoiceMessageUIHidden()
stopTimer()
}
private fun startTimer(scope: CoroutineScope) {
timerJob?.cancel()
timerJob = scope.launch {
while (isActive) {
val duration = (Date().time - startTimestamp) / 1000L
binding.recordingViewDurationTextView.text = DateUtils.formatElapsedTime(duration)
delay(500)
}
}
}
private fun stopTimer() {
timerJob?.cancel()
timerJob = null
} }
private fun animateDotView() { private fun animateDotView() {
@ -129,29 +151,6 @@ class InputBarRecordingView : RelativeLayout {
animation.start() animation.start()
} }
private fun updateTimer() {
val duration = (Date().time - startTimestamp) / 1000L
binding.recordingViewDurationTextView.text = DateUtils.formatElapsedTime(duration)
if (isAttachedToWindow) {
// Make sure there's only one runnable in the handler at a time.
removeCallbacks(updateTimerRunnable)
// Should only update the timer if the view is still attached to the window.
// Otherwise, the timer will keep running even after the view is detached.
postDelayed(updateTimerRunnable, 500)
}
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
if (isVisible) {
// If the view was visible (i.e. recording) when it was detached, start the timer again.
updateTimer()
}
}
fun lock() { fun lock() {
val fadeOutAnimation = ValueAnimator.ofObject(FloatEvaluator(), 1.0f, 0.0f) val fadeOutAnimation = ValueAnimator.ofObject(FloatEvaluator(), 1.0f, 0.0f)
fadeOutAnimation.duration = 250L fadeOutAnimation.duration = 250L

View File

@ -16,6 +16,13 @@ import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.annotation.ColorRes import androidx.annotation.ColorRes
import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.localbroadcastmanager.content.LocalBroadcastManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import network.loki.messenger.R import network.loki.messenger.R
import network.loki.messenger.databinding.ActivityPathBinding import network.loki.messenger.databinding.ActivityPathBinding
import org.session.libsession.snode.OnionRequestAPI import org.session.libsession.snode.OnionRequestAPI
@ -182,6 +189,7 @@ class PathActivity : PassphraseRequiredActionBarActivity() {
private lateinit var location: Location private lateinit var location: Location
private var dotAnimationStartDelay: Long = 0 private var dotAnimationStartDelay: Long = 0
private var dotAnimationRepeatInterval: Long = 0 private var dotAnimationRepeatInterval: Long = 0
private var job: Job? = null
private val dotView by lazy { private val dotView by lazy {
val result = PathDotView(context) val result = PathDotView(context)
@ -240,25 +248,36 @@ class PathActivity : PassphraseRequiredActionBarActivity() {
addView(dotView) addView(dotView)
} }
private fun performAnimation() {
if (isAttachedToWindow) {
expand()
postDelayed({
collapse()
postDelayed({
performAnimation()
}, dotAnimationRepeatInterval)
}, 1000)
}
}
override fun onAttachedToWindow() { override fun onAttachedToWindow() {
super.onAttachedToWindow() super.onAttachedToWindow()
postDelayed({ startAnimation()
performAnimation() }
}, dotAnimationStartDelay)
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
stopAnimation()
}
private fun startAnimation() {
job?.cancel()
job = GlobalScope.launch {
withContext(Dispatchers.Main) {
while (isActive) {
delay(dotAnimationStartDelay)
expand()
delay(EXPAND_ANIM_DELAY_MILLS)
collapse()
delay(dotAnimationRepeatInterval)
}
}
}
}
private fun stopAnimation() {
job?.cancel()
job = null
} }
private fun expand() { private fun expand() {
@ -276,6 +295,10 @@ class PathActivity : PassphraseRequiredActionBarActivity() {
val endColor = context.resources.getColorWithID(endColorID, context.theme) val endColor = context.resources.getColorWithID(endColorID, context.theme)
GlowViewUtilities.animateShadowColorChange(dotView, startColor, endColor) GlowViewUtilities.animateShadowColorChange(dotView, startColor, endColor)
} }
companion object {
private const val EXPAND_ANIM_DELAY_MILLS = 1000L
}
} }
// endregion // endregion
} }