题目
相比5.1节的任务,本任务在功能逻辑上要复杂一些。点击START按钮后,启动后台线程,每隔0.01s更新1次计数值,通过LiveData的实现类MutableLiveData感知数据并在UI中更新。START按钮被点击后会变成PAUSE按钮,同时使能STOP按钮。PAUSE按钮有两种行为,具体如下所述。(1)在PAUSE按钮状态下,点击STOP按钮,定时器结束,PAUSE按钮变成START按钮,STOP按钮禁止。(2)点击PAUSE按钮,该按钮变成RESUME按钮,并且STOP按钮禁止,定时器暂停。点击RESUME按钮,定时器工作,并且继续在上一次计数值的基础上计数,RESUME按钮变成PAUSE按钮,STOP按钮使能。
实现效果
LiveData:实现后台线程与前台UI的数据共享以及数据感知,一般不单独使用,而是和视图模型ViewModel结合在一起。
代码+注释
CounterViewModel.java
package com.example.zdl_task5_3;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
//创建自定义视图模型继承标准ViewModel
public class CounterViewModel extends ViewModel {
//一般用MutableLiveData<T>定义LiveData数据,T是泛型,只接受类,float对应类为Float
private MutableLiveData<Float> counter;
//构造方法:初始化LiveData的值
public CounterViewModel(){
counter=new MutableLiveData<>();//对counter实例化(即给counter创建实例)
counter.setValue(0.0f);//对counter初始化
}
//生成get方法返回LiveData的值。Code->generate->Getter
public MutableLiveData<Float> getCounter(){
return counter;
}
}
CounterThread.java
package com.example.zdl_task5_3;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelStoreOwner;
import java.util.concurrent.atomic.AtomicBoolean;
public class CounterThread extends Thread{
private ViewModelStoreOwner owner;//创建拥有者,通过拥有者创建视图模型,也可以定义为Run方法中的局部变量
private AtomicBoolean isRunning;//原子变量,控制线程进行和结束
private MutableLiveData<Float> counter;//自定义LiveData计数器
private int mode;//控制后台线程进行的模式
//mode的种类,设置为常量供外部引用
public static final int MODE_RESTART=0;//restart模式,把计数器值清零
public static final int MODE_RESUME=1;//resume模式,在上次计数器值的基础上技术
//两种构造方法
//构造方法一:除owner外传mode进行模式控制
public CounterThread(ViewModelStoreOwner owner,int mode){
//可以不传owner,而直接传ViewModel对象
//使用owner传参,可以验证后台线程与前台UI使用同一个owner获得的视图模型是否相同
//相同视图模型实例对应的LiveData是一个对象
this.owner=owner;
this.mode=mode;
}
//构造方法二:只传owner,使用默认restart模式
public CounterThread(ViewModelStoreOwner owner){
this.owner=owner;
mode=MODE_RESTART;
}
@Override
public void run() {
//原子变量初始化为true
isRunning=new AtomicBoolean(true);
// 使用视图模型方法:
// 创建并获得具体视图模型,new ViewModelProvider创建,参数是视图模型的拥有者
// ViewModelProvider是一个辅助类,允许在Activity或Fragment中获取ViewModel实例,而无需自己直接实例化ViewModel
// get()方法:通过传递ViewModel类的类型(Class)来获取ViewModel实例
CounterViewModel counterViewModel=new ViewModelProvider(owner).get(CounterViewModel.class);
counter=counterViewModel.getCounter();//获得自定义LiveData的值,counter不会随着线程消亡,而是在owner的生命周期一直存在
//如果是restart模式,重置计数器的值
if (mode==MODE_RESTART){
counter.postValue(0.0f);//在线程中使用postValue()改值,在主UI中,setValue()和postValue()均可
}
//如果是resume模式,不用管,直接照下面流程走
while (isRunning.get()){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//线程每睡0.01s,更新一次counter的值
counter.postValue(counter.getValue()+0.01f);
//在主UI中通过counter的Observer接口和回调方法取出更新值,更新UI
}
}
public void stopCounter(){
isRunning.set(false);
}
}
MainActivity.java
package com.example.zdl_task5_3;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
CounterThread thread;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_main);
//通过视图模型的辅助类ViewModelProvider来new一个视图模型,get方法获取ViewModel实例
CounterViewModel counterViewModel=new ViewModelProvider(this).get(CounterViewModel.class);
//从counterViewModel中取得视图模型中的自定义的LiveData数据,生命周期同MainActivity
MutableLiveData<Float> counter = counterViewModel.getCounter();
TextView tv = findViewById(R.id.tv_result);
//LiveData具有侦听功能,在Observer接口中感知数据的变动
counter.observe(this, new Observer<Float>() {
@Override
public void onChanged(Float aFloat) {//数据有变动则调用onChanged方法,把变动的值aFloat传过来
String s = String.format("%.2f",aFloat);
tv.setText(s);//把变动的数据更新到TextView上
}
});
//按钮
Button bt_start = findViewById(R.id.bt_start);
Button bt_stop = findViewById(R.id.bt_stop);
bt_start.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//状态:Start->Pause->Resume->Pause->Resume->……
//或者Start->Stop->Start
String start_tag = bt_start.getText().toString();//获取当前按钮状态
if(start_tag.equalsIgnoreCase("Start")){//若为Start(equalsIgnoreCase忽略大小写判断是否相等)
thread = new CounterThread(MainActivity.this);//单参数构造方法,restart,计数器值清零
bt_stop.setEnabled(true);//恢复stop按钮
bt_start.setText("Pause");//Start按钮按下后设置为Pause
thread.start();//启动线程
}else if(start_tag.equalsIgnoreCase("Pause")){
thread.stopCounter();//暂停计数,停止线程,线程变为空
bt_stop.setEnabled(false);//禁用stop按钮(线程终止,不禁用也没用了)
bt_start.setText("Resume");//Pause按钮按下后设置为Resume
}else{//Resume:在上次计数基础上计数,需要用两参数构造方法
thread = new CounterThread(MainActivity.this,CounterThread.MODE_RESUME);
bt_start.setText("Pause");//Resume按钮按下后设置为Pause
bt_stop.setEnabled(true);//恢复stop按钮
thread.start();//启动线程
}
}
});
bt_stop.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
bt_start.setText("Start");//bt_start恢复成start按钮
bt_stop.setEnabled(false);//禁用bt_stop按钮
thread.stopCounter();
}
});
}
}
my_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="21211870234周冬玲" />
<TextView
android:id="@+id/tv_result"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="0.00" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/bt_start"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Start" />
<Button
android:id="@+id/bt_stop"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:enabled="false"
android:text="Stop" />
</LinearLayout>
</LinearLayout>
重点总结
LiveData的值如何更新?数据变动的侦听如何实现?LiveData的生命周期(依赖于视图模型,视图模型生命周期通过其拥有者控制,拥有者可以是MainActivity,也可以是后台线程,随着视图模型的拥有者消亡而消亡,根据自己应用需求决定视图模型的应用有着是谁,进而控制其生命周期)
之后再总结,先放张PPT压压惊
虽然可以使用静态变量(全局)来解这道题,将计数器设置为静态变量,但是当遇到两个Activity使用的同一个线程的类的话,因为只有1个静态变量,那这两个Activity计数器的值就不能做到完全独立,因为他们都共享同一个静态变量。用LiveData来解决就是两个Activity有不同的视图模型,不会有干扰。