visual polish: add pulsing start button and color-shifting progress ring
- Countdown ring interpolates green → yellow → red as timer progresses - Start button pulses continuously to draw attention - Pulse stops while dialog is shown and restarts on resume
This commit is contained in:
@@ -47,6 +47,13 @@ class CountdownCanvasView @JvmOverloads constructor(
|
||||
invalidate()
|
||||
}
|
||||
|
||||
fun setColor(color: Int) {
|
||||
if (ringPaint.color != color) {
|
||||
ringPaint.color = color
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
||||
super.onSizeChanged(w, h, oldw, oldh)
|
||||
val inset = ringPaint.strokeWidth / 2f + 12f
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.example.countdowntimer
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.ColorUtils
|
||||
import android.media.AudioManager
|
||||
import android.media.ToneGenerator
|
||||
import android.os.Bundle
|
||||
@@ -56,11 +58,28 @@ class CountdownDialog(
|
||||
|
||||
private fun startTimer() {
|
||||
cvCountdown.setProgress(1f)
|
||||
cvCountdown.setColor(ContextCompat.getColor(context, R.color.md_color_ring_progress))
|
||||
timer = object : CountDownTimer(durationMillis, 1000) {
|
||||
override fun onTick(millisUntilFinished: Long) {
|
||||
val progress = millisUntilFinished.toFloat() / durationMillis
|
||||
cvCountdown.setProgress(progress)
|
||||
|
||||
// Interpolate ring color: green → yellow at 50% → red at end
|
||||
val green = android.graphics.Color.parseColor("#2E7D32")
|
||||
val yellow = android.graphics.Color.parseColor("#FFC107")
|
||||
val red = android.graphics.Color.parseColor("#F44336")
|
||||
val ringColor = when {
|
||||
progress >= 0.5f -> {
|
||||
val t = (progress - 0.5f) * 2f
|
||||
ColorUtils.blendARGB(green, yellow, t)
|
||||
}
|
||||
else -> {
|
||||
val t = progress * 2f
|
||||
ColorUtils.blendARGB(yellow, red, t)
|
||||
}
|
||||
}
|
||||
cvCountdown.setColor(ringColor)
|
||||
|
||||
val secondsLeft = (millisUntilFinished / 1000).toInt() + 1
|
||||
cvCountdown.setText(secondsLeft.toString())
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.Button
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.google.android.material.chip.ChipGroup
|
||||
import android.animation.ValueAnimator
|
||||
import android.view.animation.AnticipateOvershootInterpolator
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
@@ -24,9 +26,11 @@ class MainActivity : AppCompatActivity() {
|
||||
private lateinit var btnDurationLabel: Button
|
||||
private lateinit var hsvDurationOptions: View
|
||||
private lateinit var chipGroup: ChipGroup
|
||||
private lateinit var btnStart: View
|
||||
private lateinit var vibrator: Vibrator
|
||||
private var selectedDuration: Long = DEFAULT_DURATION
|
||||
private var lastSelectedChipId: Int = -1
|
||||
private var pulseAnimator: ValueAnimator? = null
|
||||
|
||||
private fun durationToChipId(duration: Long): Int = when (duration) {
|
||||
5000L -> R.id.chip5s
|
||||
@@ -57,6 +61,30 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun startPulseAnimation() {
|
||||
pulseAnimator = ValueAnimator.ofFloat(1f, 0.78f, 1f).apply {
|
||||
duration = 1500
|
||||
repeatMode = ValueAnimator.REVERSE
|
||||
repeatCount = ValueAnimator.INFINITE
|
||||
interpolator = AnticipateOvershootInterpolator(1f)
|
||||
addUpdateListener { animation ->
|
||||
val fraction = animation.animatedValue as Float
|
||||
btnStart.scaleX = fraction
|
||||
btnStart.scaleY = fraction
|
||||
btnStart.alpha = fraction
|
||||
}
|
||||
}
|
||||
pulseAnimator?.start()
|
||||
}
|
||||
|
||||
private fun stopPulseAnimation() {
|
||||
pulseAnimator?.cancel()
|
||||
pulseAnimator = null
|
||||
btnStart.scaleX = 1f
|
||||
btnStart.scaleY = 1f
|
||||
btnStart.alpha = 1f
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
@@ -67,6 +95,7 @@ class MainActivity : AppCompatActivity() {
|
||||
btnDurationLabel = findViewById(R.id.btnDurationLabel)
|
||||
hsvDurationOptions = findViewById(R.id.hsvDurationOptions)
|
||||
chipGroup = findViewById(R.id.chipGroup)
|
||||
btnStart = findViewById(R.id.btnStart)
|
||||
val vibratorManager = getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
|
||||
vibrator = vibratorManager.defaultVibrator
|
||||
|
||||
@@ -111,7 +140,22 @@ class MainActivity : AppCompatActivity() {
|
||||
findViewById<View>(R.id.chip30s).setOnClickListener { onChipClicked(R.id.chip30s) }
|
||||
|
||||
findViewById<View>(R.id.btnStart).setOnClickListener {
|
||||
stopPulseAnimation()
|
||||
CountdownDialog(this, selectedDuration, prefs).show()
|
||||
}
|
||||
|
||||
startPulseAnimation()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
if (!isFinishing && !isDestroyed) {
|
||||
startPulseAnimation()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
stopPulseAnimation()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user