文章目录
1、功能分析
1.1、秒表功能界面
1.2、App结构
- 1个Activity :MainActivity
- 1个Layout :activity_main.xml
2、开发视图布局
2.1、activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView
android:id="@+id/time_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:text="0:00:00"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
android:textSize="80sp"/>
<Button
android:id="@+id/button_start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="@string/start"
android:onClick="onClickStart"/>
<Button
android:id="@+id/button_stop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="@string/stop"
android:onClick="onClickStop"/>
<Button
android:id="@+id/button_reset"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="@string/reset"
android:onClick="onClickReset"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
2.2、string.xml
<resources>
<string name="app_name">Stopwatch</string>
<string name="start">start</string>
<string name="stop"> Stop</string>
<string name="reset">Reset</string>
<string name="time">Time</string>
</resources>
3、Activity实现
3.1、MainActivity类
public class MainActivity extends AppCompatActivity {
//计时的秒数
private int seconds = 0;
//计时的状态
private boolean running = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//在Activity启动时,onCreate()方法中启动runTimer()
runTimer();
}
//启动计时
public void onClickStart(View view){
running = true;
}
//停止计时
public void onClickStop(View view){
running = false;
}
//重置秒表
public void onClickReset(View view){
running = false;
seconds = 0;
}
//循环计时方法
private void runTimer(){
final TextView timeView = findViewById(R.id.time_view);
//创建UI线程的handler,用于消息处理
final Handler handler = new Handler();
handler.post(new Runnable() {//立即交一个Runnable,任务在Runnable的run()方法中
@Override
public void run() {
int hours = seconds/3600;
int minutes = (seconds%3600)/60;
int secs = seconds%60;
String time = String.format("%d:%02d:%02d", hours, minutes, secs);
timeView.setText(time);
if(running){
seconds++;
}
//每隔1000ms,重复提交该任务
handler.postDelayed(this,1000);
}
});
}
}
4、生命周期的应用
4.1、问题分析
- 问题一:旋转屏幕,Android检测到屏幕方向变化,计时会重置
- 问题二:App被切换至后台,秒表不能暂停
4.2、Activity运行过程
4.2、屏幕旋转,计时不重置
-
设备配置变化时,如屏幕旋转,需保存状态,重启时恢复
-
在运行后(running),销毁(onDestroy())前会调用 onSaveInstanceState(),保存状态到Bundle
-
保存状态需要覆盖onSaveInstanceState()方法
-
Bundle可存储键值对
bundle.put*(“name”,value)
-
在Bundle中存储running和seconds
@Override public void onSaveInstanceState(Bundle savedInstanceState){ super.onSaveInstanceState(savedInstanceState); savedInstanceState.putInt("seconds",seconds); savedInstanceState.putBoolean("running",running); }
-
-
在onCreate()方法中恢复,Bundle是其参数,进程第一次新建Activity时为null,
之后为onSaveInstanceState()保存的Bundle-
从Bundle取出键值对
bundle.get*(“name”);
-
在Bundle中取出running和seconds
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d("life cycle","onCreate"); setContentView(R.layout.activity_main); if(savedInstanceState!=null){ seconds = savedInstanceState.getInt("seconds"); running = savedInstanceState.getBoolean("running"); } runTimer(); }
-
-
程序运行流程
-
用户启动App,点击start按钮,开始计时
runTimer()方法开始递增seconds,并显示到文本框time_view中 - 旋转手机,Android检测到屏幕方向变化,销毁原Activity前,调用onSaveInstanceState()保存实例变量
- Android销毁Activity,再次新建该Activity再次调用onCreate()方法,将保存的Bundle作为参数传入
- onCreate()方法中,取出Bundle存储的值并恢复到销毁前的状态
- runTimer()方法从旋转前的seconds继续计时
-
用户启动App,点击start按钮,开始计时
4.3、App被切换至后台,秒表可以暂停
-
解决方法
-
覆盖onStop(),在消失前停止计时
@Override protected void onStop(){ super.onStop(); Log.d("life cycle","onStop"); wasRunning = running; // 记录之前是否运行, running = false; //将running设置为false以停止计时 }
-
覆盖生命周期方法前,必须先调用父类的生命周期
super.onStop();
-
覆盖onStart(),在可见前继续计时
@Override //如果之前running==true,则将running设置为true,继续计时 protected void onStart(){ super.onStart(); Log.d("life cycle","onStop"); if(wasRunning){ running = true; } }
-
App被切换至后台,Activity对象仍存在,可以使用实例变量存储状态
//计时的秒数 private int seconds = 0; //计时的状态 private boolean running = false; //新的变量,用于在onStop()中保存消失前running的状态 private boolean wasRunning = false; @Override protected void onStop(){ super.onStop(); Log.d("life cycle","onStop"); //日志并暂停 wasRunning = running; running = false; } @Override protected void onStart(){ super.onStart(); Log.d("life cycle","onStop"); //日志并恢复 if(wasRunning){ running = true; } }
-
Activity销毁前在Bundle保存wasRunning,
Activity重新实例化后从Bundle恢复wasRunning@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d("life cycle","onCreate"); setContentView(R.layout.activity_main); if(savedInstanceState!=null){ seconds = savedInstanceState.getInt("seconds"); running = savedInstanceState.getBoolean("running"); wasRunning = savedInstanceState.getBoolean("wasRunning"); } runTimer(); } @Override public void onSaveInstanceState(Bundle savedInstanceState){ super.onSaveInstanceState(savedInstanceState); savedInstanceState.putInt("seconds",seconds); savedInstanceState.putBoolean("running",running); savedInstanceState.putBoolean("wasRunning",wasRunning); }
-
-
运行流程
- 用户启动App,点击Start按钮,runTimer()开始递增seconds并更新文本框
- 用户点击Home键,Activity消失,Android调用onStop()
- 用户返回秒表App,Activity可见,Android调用onStart()
5、MainActivity完整代码
public class MainActivity extends AppCompatActivity {
//计时的秒数
private int seconds = 0;
//计时的状态
private boolean running = false;
//新的变量,用于在onStop()中保存消失前running的状态
private boolean wasRunning = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("life cycle","onCreate");
setContentView(R.layout.activity_main);
if(savedInstanceState!=null){
seconds = savedInstanceState.getInt("seconds");
running = savedInstanceState.getBoolean("running");
wasRunning = savedInstanceState.getBoolean("wasRunning");
}
//在Activity启动时,onCreate()方法中启动runTimer()
runTimer();
}
@Override
protected void onStart(){
super.onStart();
Log.d("life cycle","onStop");
if(wasRunning){
running = true;
}
}
@Override
protected void onStop(){
super.onStop();
Log.d("life cycle","onStop");
wasRunning = running;
running = false;
}
@Override
public void onSaveInstanceState(Bundle savedInstanceState){
super.onSaveInstanceState(savedInstanceState);
savedInstanceState.putInt("seconds",seconds);
savedInstanceState.putBoolean("running",running);
savedInstanceState.putBoolean("wasRunning",wasRunning);
}
protected void onDestroy(){
super.onDestroy();
Log.d("life cycle","onDestroy");
}
//启动计时
public void onClickStart(View view){
running = true;
}
//停止计时
public void onClickStop(View view){
running = false;
}
//重置秒表
public void onClickReset(View view){
running = false;
seconds = 0;
wasTiming = false;
}
//循环计时方法
private void runTimer(){
final TextView timeView = findViewById(R.id.time_view);
//创建UI线程的handler,用于消息处理
final Handler handler = new Handler();
handler.post(new Runnable() {//立即交一个Runnable,任务在Runnable的run()方法中
@Override
public void run() {
int hours = seconds/3600;
int minutes = (seconds%3600)/60;
int secs = seconds%60;
String time = String.format("%d:%02d:%02d", hours, minutes, secs);
timeView.setText(time);
if(running){
seconds++;
}
//每隔1000ms,重复提交该任务
handler.postDelayed(this,1000);
}
});
}
}