Android动态片段

之前创建的片段都是静态的。一旦显示片段,片段的内容就不能改变了。尽管可以用一个新实例完全取代所显示的片段,但是并不能更新片段本身的内容。

之前已经创建过一个基础秒表应用,具体代码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 复制代码
<?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的秒表片段,鉴于片段和活动的生命周期有所不同,要对之前的秒表活动稍作修改使其成为片段。

java 复制代码
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 复制代码
<?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>

增加三个字符串资源:

xml 复制代码
    <string name="start">Start</string>
    <string name="stop">Stop</string>
    <string name="reset">Reset</string>

修改activity_temp.xml:

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:

java 复制代码
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);
            }
        });
    }
}

现在可以试试应用了。

相关推荐
张拭心4 分钟前
春节后,有些公司明确要求 AI 经验了
android·前端·人工智能
张拭心15 分钟前
Android 17 来了!新特性介绍与适配建议
android·前端
Kapaseker2 小时前
Compose 进阶—巧用 GraphicsLayer
android·kotlin
黄林晴3 小时前
Android17 为什么重写 MessageQueue
android
阿巴斯甜1 天前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker1 天前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq95271 天前
Andorid Google 登录接入文档
android
黄林晴1 天前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab2 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿2 天前
Android MediaPlayer 笔记
android