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);
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);
final Handler handler = new Handler();
handler.post(new Runnable() {
@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++;
}
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继续计时
4.3、App被切换至后台,秒表可以暂停
-
解决方法
-
覆盖onStop(),在消失前停止计时 @Override
protected void onStop(){
super.onStop();
Log.d("life cycle","onStop");
wasRunning = running;
running = false;
}
-
覆盖生命周期方法前,必须先调用父类的生命周期
super.onStop();
-
覆盖onStart(),在可见前继续计时 @Override
protected void onStart(){
super.onStart();
Log.d("life cycle","onStop");
if(wasRunning){
running = true;
}
}
-
App被切换至后台,Activity对象仍存在,可以使用实例变量存储状态
private int seconds = 0;
private boolean running = false;
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;
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");
}
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);
final Handler handler = new Handler();
handler.post(new Runnable() {
@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++;
}
handler.postDelayed(this,1000);
}
});
}
}
|