创建项目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
}
}