diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 0000000..cec0517
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+android_2026_1
\ No newline at end of file
diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml
new file mode 100644
index 0000000..4a53bee
--- /dev/null
+++ b/.idea/AndroidProjectSystem.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..b86273d
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
new file mode 100644
index 0000000..b268ef3
--- /dev/null
+++ b/.idea/deploymentTargetSelector.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/deviceManager.xml b/.idea/deviceManager.xml
new file mode 100644
index 0000000..91f9558
--- /dev/null
+++ b/.idea/deviceManager.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 0000000..639c779
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/migrations.xml b/.idea/migrations.xml
new file mode 100644
index 0000000..f8051a6
--- /dev/null
+++ b/.idea/migrations.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..c902d2f
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100644
index 0000000..16660f1
--- /dev/null
+++ b/.idea/runConfigurations.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index cc8ee59..c7f45c2 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -3,6 +3,9 @@ plugins {
}
android {
+ viewBinding {
+ enable = true
+ }
namespace = "com.example.android_2026_1"
compileSdk = 36
@@ -37,6 +40,7 @@ dependencies {
implementation(libs.material)
implementation(libs.androidx.activity)
implementation(libs.androidx.constraintlayout)
+ implementation(libs.androidx.recyclerview)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
diff --git a/app/src/main/java/com/example/android_2026_1/LapAdapter.kt b/app/src/main/java/com/example/android_2026_1/LapAdapter.kt
new file mode 100644
index 0000000..b2f6bd7
--- /dev/null
+++ b/app/src/main/java/com/example/android_2026_1/LapAdapter.kt
@@ -0,0 +1,26 @@
+package com.example.android_2026_1
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+
+class LapAdapter(private val lapList: List) : RecyclerView.Adapter() {
+
+ class LapViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ val textViewLap: TextView = itemView as TextView
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LapViewHolder {
+ val view = LayoutInflater.from(parent.context)
+ .inflate(android.R.layout.simple_list_item_1, parent, false)
+ return LapViewHolder(view)
+ }
+
+ override fun onBindViewHolder(holder: LapViewHolder, position: Int) {
+ holder.textViewLap.text = lapList[position]
+ }
+
+ override fun getItemCount(): Int = lapList.size
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/android_2026_1/MainActivity.kt b/app/src/main/java/com/example/android_2026_1/MainActivity.kt
index ad2a6ca..9166fd5 100644
--- a/app/src/main/java/com/example/android_2026_1/MainActivity.kt
+++ b/app/src/main/java/com/example/android_2026_1/MainActivity.kt
@@ -1,20 +1,121 @@
package com.example.android_2026_1
+import android.annotation.SuppressLint
import android.os.Bundle
+import android.os.SystemClock
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
+import androidx.lifecycle.lifecycleScope
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.example.android_2026_1.databinding.ActivityMainBinding
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.isActive
+import kotlinx.coroutines.launch
class MainActivity : AppCompatActivity() {
+
+ private lateinit var binding: ActivityMainBinding
+
+ private var timeCentiseconds = 0
+ private var timeJob: Job? = null
+
+ private var startTime = 0L
+ private var pausedTime = 0L
+
+ private lateinit var lapAdapter: LapAdapter
+ private val lapList = mutableListOf()
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
- setContentView(R.layout.activity_main)
- ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
+
+ binding = ActivityMainBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ ViewCompat.setOnApplyWindowInsetsListener(binding.main) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
+
+ lapAdapter = LapAdapter(lapList)
+ binding.recyclerViewLaps.apply {
+ adapter = lapAdapter
+ layoutManager = LinearLayoutManager(this@MainActivity)
+ }
+
+ binding.buttonStartPause.setOnClickListener {
+ if (timeJob?.isActive == true) pauseTimer() else startTimer()
+ }
+
+ binding.buttonLap.setOnClickListener {
+ val currentText = getFormattedTime()
+ val secondsElapsed = timeCentiseconds / 100
+ val lapRecord = getString(R.string.lap_record_format, secondsElapsed, currentText)
+
+ lapList.add(0, lapRecord)
+ lapAdapter.notifyItemInserted(0)
+ binding.recyclerViewLaps.scrollToPosition(0)
+ }
+
+ binding.buttonStop.setOnClickListener {
+ resetTimer()
+ clearLapRecords()
+ }
+ }
+
+ private fun startTimer() {
+ startTime = SystemClock.elapsedRealtime() - pausedTime
+
+ timeJob = lifecycleScope.launch {
+ binding.buttonStartPause.text = getString(R.string.btn_pause)
+
+ while (isActive) {
+ val elapsedMillis = SystemClock.elapsedRealtime() - startTime
+ timeCentiseconds = (elapsedMillis / 10).toInt()
+
+ updateTimerText()
+ delay(10L)
+ }
+ }
+ }
+
+ private fun pauseTimer() {
+ timeJob?.cancel()
+ pausedTime = SystemClock.elapsedRealtime() - startTime
+ binding.buttonStartPause.text = getString(R.string.btn_start)
+ }
+
+ private fun resetTimer() {
+ timeJob?.cancel()
+ timeCentiseconds = 0
+ pausedTime = 0L
+ updateTimerText()
+ binding.buttonStartPause.text = getString(R.string.btn_start)
+ }
+
+ private fun getFormattedTime(): String {
+ val m = (timeCentiseconds / 6000).toString().padStart(2, '0')
+ val s = ((timeCentiseconds % 6000) / 100).toString().padStart(2, '0')
+ val ms = (timeCentiseconds % 100).toString().padStart(2, '0')
+
+ return "$m:$s:$ms"
+ }
+
+ private fun updateTimerText() {
+ val m = (timeCentiseconds / 6000).toString().padStart(2, '0')
+ val s = ((timeCentiseconds % 6000) / 100).toString().padStart(2, '0')
+ val ms = (timeCentiseconds % 100).toString().padStart(2, '0')
+
+ binding.textViewTime.text = "$m : $s : $ms"
+ }
+
+ @SuppressLint("NotifyDataSetChanged")
+ private fun clearLapRecords() {
+ lapList.clear()
+ lapAdapter.notifyDataSetChanged()
}
}
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 9affce0..6b65f1f 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -7,4 +7,63 @@
android:layout_height="match_parent"
tools:context=".MainActivity">
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index c4a5053..e055159 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,3 +1,12 @@
- android_2026_1
+ 스톱워치
+ Start
+ Pause
+ Stop
+ Lap
+ 00 : 00 : 00
+
+ %1$d초 경과 (%2$s)
+ %02d : %02d : %02d
+ %02d:%02d:%02d
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index dd29a05..ee1ed4f 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -9,6 +9,7 @@ junitVersion = "1.3.0"
espressoCore = "3.7.0"
appcompat = "1.7.1"
material = "1.14.0"
+recyclerview = "1.4.0"
[libraries]
androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintlayout" }
@@ -19,6 +20,7 @@ androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "j
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
material = { module = "com.google.android.material:material", version.ref = "material" }
+androidx-recyclerview = { group = "androidx.recyclerview", name = "recyclerview", version.ref = "recyclerview" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }