之前创建的片段都是静态的。一旦显示片段,片段的内容就不能改变了。尽管可以用一个新实例完全取代所显示的片段,但是并不能更新片段本身的内容。
之前已经创建过一个基础秒表应用,具体代码https://github.com/MADMAX110/Stopwatch。我们将这个应用增加到WorkoutDetailFragment,把它显示在训练项目的详细信息之下。
修改步骤
1、把StopwatchActivity转换为StopwatchFragment。
将其改为片段代码,还会在一个新的临时活动TempActivity中显示这个片段,以便检查它的具体工作。这里会暂时修改应用,使应用运行时启动TempActivity。
2、测试StopwatchFragment。
StopwatchActivity包含Start、Stop、Reset按钮。我们要检查将秒表代码放在一个片段中时这些按钮仍然能正常工作。
3、把StopwatchFragment增加到WorkoutDetailFragment。
让StopwatchFragment在一个新的临时活动TempActivity中工作。这样我们就能先确认StopwatchFragement能正常工作,然后再把它增加到WorkoutDetailFragment。
修改代码
回到之前创建的workout工程,在com.hfad.workout中创建一个新的活动TempActivity,布局名为activity_temp。
修改AndroidManifest.xml使得TempActivity为主活动:
<?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" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".DetailActivity"
android:exported="false" />
<activity
android:name=".MainActivity"
android:exported="true">
</activity>
</application>
</manifest>
我们要新增一个StopwatchFragment的秒表片段,鉴于片段和活动的生命周期有所不同,要对之前的秒表活动稍作修改使其成为片段。
package com.hfad.workout;
import android.app.Activity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.Locale;
import android.os.Handler;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
public class StopwatchFragment extends Fragment {
private int seconds = 0;//记录已经过去的秒数
private boolean running;//秒表是否正常运行
//记录onStop之前秒表是否在运行,这样就知道是否需要恢复运行
private boolean wasRunning;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
seconds = savedInstanceState.getInt("seconds");
running = savedInstanceState.getBoolean("running");
//保存wasRunning变量的状态
wasRunning = savedInstanceState.getBoolean("wasRunning");
}
}
@Nullable
@Override
//片段改为在onCreateView()方法中设置片段的布局
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View layout = inflater.inflate(R.layout.fragment_stopwatch, container, false);
runTimer(layout);
return layout;
}
@Override
public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
savedInstanceState.putInt("seconds", seconds);
savedInstanceState.putBoolean("running", running);
}
@Override
public void onResume() {
super.onResume();
if (wasRunning) {
running = true;
}
}
@Override
public void onPause() {
super.onPause();
wasRunning = running;
running = false;
}
//启动秒表
public void onClickStart(View view) {
running = true;
}
//停止秒表
public void onClickStop(View view) {
running = false;
}
//单击reset按钮时会调用这个方法
public void onClickReset(View view) {
running = false;
seconds = 0;
}
private void runTimer(View view) {
//得到文本视图(片段不能为直接使用findViewById,使用view参数调用findViewById)
final TextView timeView = (TextView) view.findViewById(R.id.time_view);
//创建一个新地Handler
final Handler handler = new Handler();
//调用post()方法,传入一个新的Runnable。post()方法会立即运行代码
handler.post(new Runnable() {
public void run() {
int hours = seconds / 3600;
int minutes = (seconds%3600)/60;
int secs = seconds % 60;
//设置显示格式
String time = String.format(Locale.getDefault(), "%d:%02d%02d", hours, minutes, secs);
//设置文本视图
timeView.setText(time);
if (running) {
++seconds;
}
//在1000ms后再次提交并运行Runnable中的代码,会反复调用
handler.postDelayed(this, 1000);
}
});
}
}
StopwatchFragment仍然使用Stopwatch原来的布局:
<?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:padding="16dp"
>
<TextView
android:id="@+id/time_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textAppearance="@android:style/TextAppearance.Large"
android:textSize="56sp"
/>
<Button
android:id="@+id/start_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="20dp"
android:onClick="onClickStart"
android:text="@string/start"
/>
<Button
android:id="@+id/stop_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="8dp"
android:onClick="onClickStop"
android:text="@string/stop"
/>
<Button
android:id="@+id/reset_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="8dp"
android:onClick="onClickReset"
android:text="@string/reset"
/>
</LinearLayout>
增加三个字符串资源:
<string name="start">Start</string>
<string name="stop">Stop</string>
<string name="reset">Reset</string>
修改activity_temp.xml:
<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
android:name = "com.hfad.workout.StopwatchFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
</fragment>
在单击按钮时调用片段中的方法
此时尝试应用单击按钮时将会崩溃,其原因在于onClick调用的是活动中的方法,而不是片段中的方法。 Android不会调用片段中的方法,而只会调用父活动中的方法。如果在这个活动中无法找到相应的方法,应用就会崩溃。
如何在单击按钮时调用片段中的方法:
1、从片段布局中删除android:onClick的引用。
使用android:onCLick属性时,按钮就会调用活动中的方法,所以要从片段布局中将它们删除。
2、修改onClick方法签名(可选)
创建onClickStart、onClickStop和onCLickReset方法,声明它们是公共方法,并提供一个View参数。这样当用户单击一个按钮时就会调用这些方法。由于我们不在布局中使用android:onCLick属性,所以可以把这些方法设置为私有。
3、实现一个onCLickListener,将按钮绑定到片段中的方法。
完整的StopwatvhFragment:
package com.hfad.workout;
import android.app.Activity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import java.util.Locale;
import android.os.Handler;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
public class StopwatchFragment extends Fragment implements View.OnClickListener {
private int seconds = 0;//记录已经过去的秒数
private boolean running;//秒表是否正常运行
//记录onStop之前秒表是否在运行,这样就知道是否需要恢复运行
private boolean wasRunning;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
seconds = savedInstanceState.getInt("seconds");
running = savedInstanceState.getBoolean("running");
//保存wasRunning变量的状态
wasRunning = savedInstanceState.getBoolean("wasRunning");
}
}
@Nullable
@Override
//片段改为在onCreateView()方法中设置片段的布局
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View layout = inflater.inflate(R.layout.fragment_stopwatch, container, false);
runTimer(layout);
Button startButton = (Button)layout.findViewById(R.id.start_button);
startButton.setOnClickListener(this);
Button stopButton = (Button)layout.findViewById(R.id.stop_button);
startButton.setOnClickListener(this);
Button resetButton = (Button)layout.findViewById(R.id.reset_button);
startButton.setOnClickListener(this);
return layout;
}
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.start_button:
onClickStart();
break;
case R.id.stop_button:
onClickStop();
break;
case R.id.reset_button:
onClickReset();
break;
}
}
@Override
public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
savedInstanceState.putInt("seconds", seconds);
savedInstanceState.putBoolean("running", running);
}
@Override
public void onResume() {
super.onResume();
if (wasRunning) {
running = true;
}
}
@Override
public void onPause() {
super.onPause();
wasRunning = running;
running = false;
}
//启动秒表
public void onClickStart() {
running = true;
}
//停止秒表
public void onClickStop() {
running = false;
}
//单击reset按钮时会调用这个方法
public void onClickReset() {
running = false;
seconds = 0;
}
private void runTimer(View view) {
//得到文本视图(片段不能为直接使用findViewById,使用view参数调用findViewById)
final TextView timeView = (TextView) view.findViewById(R.id.time_view);
//创建一个新地Handler
final Handler handler = new Handler();
//调用post()方法,传入一个新的Runnable。post()方法会立即运行代码
handler.post(new Runnable() {
public void run() {
int hours = seconds / 3600;
int minutes = (seconds%3600)/60;
int secs = seconds % 60;
//设置显示格式
String time = String.format(Locale.getDefault(), "%d:%02d%02d", hours, minutes, secs);
//设置文本视图
timeView.setText(time);
if (running) {
++seconds;
}
//在1000ms后再次提交并运行Runnable中的代码,会反复调用
handler.postDelayed(this, 1000);
}
});
}
}
现在可以试试应用了。