创建项目Stopwatch

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center_horizontal"
tools:context=".MainActivity">
<Chronometer
android:id="@+id/stopwatch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="56sp" />
<Button
android:id="@+id/start_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/start" />
<Button
android:id="@+id/pause_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pause" />
<Button
android:id="@+id/reset_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/reset" />
</LinearLayout>
strings.xml
<resources>
<string name="app_name">Stopwatch</string>
<string name="start">Start</string>
<string name="pause">Pause</string>
<string name="reset">Reset</string>
</resources>
MainActivity.kt
package com.demo.stopwatch
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.SystemClock
import android.widget.Button
import android.widget.Chronometer
class MainActivity : AppCompatActivity() {
lateinit var stopwatch: Chronometer //The stopwatch
var running = false //Is the stopwatch running?
var offset: Long = 0 //The base offset for the stopwatch
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//Get a reference to the stopwatch
stopwatch = findViewById<Chronometer>(R.id.stopwatch)
//The start button starts the stopwatch if it's not running
val startButton = findViewById<Button>(R.id.start_button)
startButton.setOnClickListener {
if (!running) {
setBaseTime()
stopwatch.start()
running = true
}
}
//The pause button pauses the stopwatch if it’s running
val pauseButton = findViewById<Button>(R.id.pause_button)
pauseButton.setOnClickListener {
if (running) {
saveOffset()
stopwatch.stop()
running = false
}
}
//The reset button sets the offset and stopwatch to 0
val resetButton = findViewById<Button>(R.id.reset_button)
resetButton.setOnClickListener {
offset = 0
setBaseTime()
}
}
//Update the stopwatch base time, allowing for any offset
fun setBaseTime() {
stopwatch.base = SystemClock.elapsedRealtime() - offset
}
//Record the offset
fun saveOffset() {
offset = SystemClock.elapsedRealtime() - stopwatch.base
}
}
运行查看效果
代码说明
1.应用运行,MainActivity启动
初始化running和offset属性
2.调用MainActivity的onCreate方法
将activity_main.xml布局链接到活动,为stopwatch属性指定视图的一个引用
3.点击“Start”按键
秒表开始运行
4.点击“Pause”按钮
更新offset属性,并调用秒表的stop方法,running更新为false,秒表暂停
5.再次点击“Start”按钮
使用offset的值来调整stopwatch.base属性,调用start方法,更新running为true,表秒再次运行
6.点击“Reset”按钮
offset更新为0,stopwatch.base属性更新为SystemClock.elapsedRealtime()
但是这里有个问题,当旋转屏幕时,秒表会重置为0,并停止运行

旋转时发生了什么?
当屏幕方向有变化时,Android会撤销MainActivity,所以MainActivity的属性值会丢失
然后MainActivity会重启,它的所有属性都会重新初始化,并且再次运行onCreate()方法
活动从启动状态变成撤销状态时,会触发一些活动生命周期的方法:onCreate()和onDestroy()。这些是活动继承的生命周期方法,不过可以覆盖这些方法。
解决方案:使用Bundle
Bundle是一种保存键值对的对象。在活动撤销之前,Android允许你把键值对放在Bundle里面,然后在活动重建时,活动的新实例通过Bundle恢复属性值
活动撤销前都会调用onSaveInstanceState方法,所以这里需要覆盖onSaveInstanceState方法
活动重建时会调用onCreate()方法,这里通过检查savedInstanceState != null来判断是否需要恢复属性值
MainActivity.kt最终代码
package com.demo.stopwatch
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.SystemClock
import android.widget.Button
import android.widget.Chronometer
class MainActivity : AppCompatActivity() {
lateinit var stopwatch: Chronometer //The stopwatch
var running = false //Is the stopwatch running?
var offset: Long = 0 //The base offset for the stopwatch
//Add key Strings for use with the Bundle
val OFFSET_KEY = "offset"
val RUNNING_KEY = "running"
val BASE_KEY = "base"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//Get a reference to the stopwatch
stopwatch = findViewById<Chronometer>(R.id.stopwatch)
//Restore the previous state
if (savedInstanceState != null) {
offset = savedInstanceState.getLong(OFFSET_KEY)
running = savedInstanceState.getBoolean(RUNNING_KEY)
if (running) {
stopwatch.base = savedInstanceState.getLong(BASE_KEY)
stopwatch.start()
} else setBaseTime()
}
//The start button starts the stopwatch if it's not running
val startButton = findViewById<Button>(R.id.start_button)
startButton.setOnClickListener {
if (!running) {
setBaseTime()
stopwatch.start()
running = true
}
}
//The pause button pauses the stopwatch if it’s running
val pauseButton = findViewById<Button>(R.id.pause_button)
pauseButton.setOnClickListener {
if (running) {
saveOffset()
stopwatch.stop()
running = false
}
}
//The reset button sets the offset and stopwatch to 0
val resetButton = findViewById<Button>(R.id.reset_button)
resetButton.setOnClickListener {
offset = 0
setBaseTime()
}
}
override fun onPause() {
super.onPause()
if (running) {
saveOffset()
stopwatch.stop()
}
}
override fun onResume() {
super.onResume()
if (running) {
setBaseTime()
stopwatch.start()
offset = 0
}
}
override fun onSaveInstanceState(savedInstanceState: Bundle) {
savedInstanceState.putLong(OFFSET_KEY, offset)
savedInstanceState.putBoolean(RUNNING_KEY, running)
savedInstanceState.putLong(BASE_KEY, stopwatch.base)
super.onSaveInstanceState(savedInstanceState)
}
//Update the stopwatch base time, allowing for any offset
fun setBaseTime() {
stopwatch.base = SystemClock.elapsedRealtime() - offset
}
//Record the offset
fun saveOffset() {
offset = SystemClock.elapsedRealtime() - stopwatch.base
}
}



















