这时候旋转设备还是会重置秒表。旋转设备时Android会重新创建活动。如果你的活动包含一个 < fragment >元素,每次重新创建活动时,它会重新插入片段的一个新版本。老片段被丢掉,所有实例变量会设置其初始值。在这个特定的例子中,这意味着秒表会设置回到0。
所以动态片段需要一个片段事务,片段元素对于显示静态数据的片段很适用,但是如果有一个动态片段,就需要使用片段事务来增加片段。
修改activity_temp.xml来使用FrameLayout。
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id = "@+id/stopwatch_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
</FrameLayout>
为TempActivity.java增加一个片段事务。
它将StopwatchFragment增加到TempActivity。
package com.hfad.workout;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.FragmentTransaction;
import android.os.Bundle;
public class TempActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_temp);
if (savedInstanceState == null) {
StopwatchFragment stopwatch = new StopwatchFragment();
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.add(R.id.stopwatch_container, stopwatch);
ft.addToBackStack(null);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
}
}
}
如此运行应用后秒表就能像之前一样正常工作。
为WorkoutDetailFragment增加秒表
修改AndroidManifest.xml的启动应用:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Workout"
tools:targetApi="31">
<activity
android:name=".TempActivity"
android:exported="true" >
</activity>
<activity
android:name=".DetailActivity"
android:exported="false" />
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
在显示片段的地方fragment_workout_detail.xml增加一个FrameLayout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:id="@+id/textTitle" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/textDescription" />
<FrameLayout
android:id="@+id/stopwatch_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
要在一个片段中使用片段事务时使用的代码基本相同,有一个重要的区别就是:
片段没有一个名为getSupportFragmentManager的方法,下面这行代码需要修改:
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
创建getFragmentManager在后退堆栈创建额外的事务。
getFragmentManager方法会得到与片段父活动相关联的片段管理器。使用这个片段管理器创建的所有事务片段事务会分别作为一个单独的事务增加到后退堆栈。
假设用户单击了一个训练项目,会显示这个训练项目的详细信息以及秒表,如果用户再单击后退按钮,他们可能希望屏幕回到选择训练项目之前的状态。但是后退按钮只是弹出后退堆栈中的最后一个事务。这说明,如果我们创建两个事务来增加训练项目个秒表,用户单击后退按钮时,只会删除秒表。他们必须再次单击后退按钮才能删除训练项目的详细信息。
使用getChildFragmentManager()创建嵌套事务:
getChildFragmentManager方法会得到与片段的父片段关联的片段管理器。使用这个片段管理器创建的所有片段事务都会增加到父片段事务的后退堆栈,而不是增加为一个单独的事务。
用户单击一个训练项目时还是会显示WorkoutDetailFragment和StopwatchFragment,不过用户单击后退按钮时,行为会有所不同。由于这两个事务是嵌套的,所以用户按下后退按钮时两个事务都会从后退堆栈弹出。用户按下一次后退按钮,训练项目详细信息和秒表都将删除。
把getChildFragmentManager()片段事务代码增加到WorkoutDetailFragment.java
package com.hfad.workout;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
import android.os.PersistableBundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
public class WorkoutDetailFragment extends Fragment {
//用来表示用户选择的训练项目的ID
private long workoutId;
@Override
//Android需要这个片段的布局时会调用这个方法
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// 这会告诉Android这个片段使用哪个布局
return inflater.inflate(R.layout.fragment_workout_detail, container, false);
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null){
workoutId = savedInstanceState.getLong("workoutId");
}else{
StopwatchFragment stopwatch = new StopwatchFragment();
FragmentTransaction ft = getChildFragmentManager().beginTransaction();
ft.add(R.id.stopwatch_container, stopwatch);
ft.addToBackStack(null);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
}
}
public void onStart() {
super.onStart();
//得到片段的根视图,然后使用这个根视图得到两个文本视图的引用
View view = getView();
if (view != null) {
TextView title = (TextView) view.findViewById(R.id.textTitle);
Workout workout = Workout.workouts[(int)workoutId];
title.setText(workout.getName());
TextView description = (TextView) view.findViewById(R.id.textDescription);
description.setText(workout.getDescription());
}
}
public void setWorkoutId(long id) {
this.workoutId = id;
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putLong("workoutId", workoutId);
}
}
如此大功告成。