计分器(ViewModel、Databinding实现数据的保存和控件操作)
Android中的ViewModel是一个可以用来存储UI相关的数据的类。ViewModel的生命周期会比创建它的Activity、Fragment的生命周期长。
可以看到重建的时候,ViewModel中的数据是不会被清理的
Activity进行重建的时候,ViewModel的数据不会被回收调用。这时候我们就可以不用通过onSaveInstanceState()方法来进行数据的存储了。而且用onSaveInstanceState()方法为了使Activity能够尽快的重建还只能存储少量的数据进行恢复。
Activity中通常会有有那种在其创建的时候获取数据,然后在其销毁的时候释放数据的方法。如果这些放在Activity中的话,在Activity进行重建的时候,会很浪费资源。但是如果方法在ViewModel中的话,Activity的重建将不会导致数据的重复获取。
DataBinding 是谷歌官方发布的一个框架,顾名思义即为数据绑定,用于降低布局和逻辑的耦合性,使代码逻辑更加清晰。其实就是将 Presenter 层替换成了 ViewModel 层。DataBinding 能够省去我们一直以来的 findViewById() 步骤,大量减少 Activity 内的代码,数据能够单向或双向绑定到 layout 文件中,有助于防止内存泄漏,而且能自动进行空检测以避免空指针异常。另外的好处就是由于降低了控件与代码的相关联性,所以在对页面布局进行删减或修改时,基本不需要对程序进行大的修改,非常便于程序的维护和升级。
布局如下:
这里引入ViewModel,整个Activity对应一个ViewModel对象。里面包含所有要保存和操作的界面值
package com.example.score; import android.util.Log; import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.ViewModel; public class MyViewModel extends ViewModel { String TAG="output"; private MutableLiveData<Integer> ATeamScore; private MutableLiveData<Integer> BTeamScore; private int aBack,bBack; //作为存储上一步操作的比分值,用于撤销函数实现 public MutableLiveData<Integer> getATeamScore() { //作为dadabinding中TextView(TeamA)的绑定 if(ATeamScore==null) { ATeamScore=new MutableLiveData<>(); ATeamScore.setValue(0); } return ATeamScore; } public MutableLiveData<Integer> getBTeamScore() { //作为dadabinding中TextView(TeamB)的绑定 if(BTeamScore==null) { BTeamScore=new MutableLiveData<>(); BTeamScore.setValue(0); } return BTeamScore; } public void aTeamAdd(int p) { aBack=ATeamScore.getValue(); bBack=BTeamScore.getValue(); ATeamScore.setValue(ATeamScore.getValue()+p); //aTeam的加分控件函数,会绑定到按键的xml文件中的Onclick对应函数上 } public void bTeamAdd(int p) { aBack=ATeamScore.getValue(); bBack=BTeamScore.getValue(); BTeamScore.setValue(BTeamScore.getValue()+p); //bTeam的加分控件函数,会绑定到按键的xml文件中的Onclick对应函数上 } public void reset() //复位函数,将比分全清零 { aBack=ATeamScore.getValue(); bBack=BTeamScore.getValue(); ATeamScore.setValue(0); BTeamScore.setValue(0); Log.d(TAG, "reset: ");//测试函数是否调用 } public void withdraw() //撤销函数,返回上一步的比分值 { Log.d(TAG, "withdraw: ");//测试函数是否调用 ATeamScore.setValue(aBack); BTeamScore.setValue(bBack); } }
databing的使用
ViewModel实现了数据的保存,保证了在屏幕发送旋转或返回主菜单时丢失数据,可是控件与代码的联系之前一直通过函数findViewByID()函数实现绑定,databing可是简化这步操作:
首先需要在gradle,script->build.gradle(Module:app)中添加如下代码:
defaultConfig { applicationId "com.example.score" minSdkVersion 19 targetSdkVersion 30 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" android.defaultConfig.vectorDrawables.useSupportLibrary = true
//添加代码如下:
dataBinding{ enabled true }
然后切换到布局文件,将布局文件由design模式切换为Code模式,如下图所示:
到Code文件中找到提示Convert to data binding layout:单击整即可
然后添加如下代码
<data> <variable name="data" //作为ViewModel的变量名,后续进行调用实现绑定 type="com.example.score.MyViewModel" /> </data>
design布局下这个控件进行信息绑定:右键单击控件->go to xml
例如:
就会跳转到控件对应的代码段
对TextView(分数A)的绑定如下:
<TextView android:id="@+id/textView3" android:layout_width="140dp" android:layout_height="107dp" android:text="@{String.valueOf(data.getATeamScore())}" //修改主要在这,将文字显示绑定到ViewModel中的 android:textAlignment="center" android:textColor="#F30606" android:textSize="@dimen/ScoreTextSize" app:layout_constraintBottom_toTopOf="@+id/guideline3" app:layout_constraintEnd_toStartOf="@+id/guideline11" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/guideline2" app:layout_constraintVertical_bias="0.682" tools:text="120" /> <TextView
所有Code代码如下所示:按上面方式操作即可
<?xml version="1.0" encoding="utf-8"?> <layout 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"> <data> <variable name="data" type="com.example.score.MyViewModel" /> </data> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="386dp" android:layout_height="match_parent" tools:context=".MainActivity"> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_percent="0.06" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_percent="0.15" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_percent="0.33" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline5" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_percent="0.57326734" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline6" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_percent="0.71049595" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline7" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_percent="0.8535179" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_percent="0.4475202" /> <TextView android:id="@+id/textView" android:layout_width="83dp" android:layout_height="32dp" android:text="@string/textview1" android:textAlignment="center" android:textSize="24sp" app:layout_constraintBottom_toTopOf="@+id/guideline2" app:layout_constraintEnd_toStartOf="@+id/guideline8" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/guideline" /> <TextView android:id="@+id/textView2" android:layout_width="77dp" android:layout_height="35dp" android:text="@string/textview2" android:textAlignment="center" android:textSize="@dimen/TeamTextSize" app:layout_constraintBottom_toTopOf="@+id/guideline2" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="@+id/guideline8" app:layout_constraintTop_toTopOf="@+id/guideline" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline8" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" app:layout_constraintGuide_percent="0.5" /> <TextView android:id="@+id/textView3" android:layout_width="140dp" android:layout_height="107dp" android:text="@{String.valueOf(data.getATeamScore())}" android:textAlignment="center" android:textColor="#F30606" android:textSize="@dimen/ScoreTextSize" app:layout_constraintBottom_toTopOf="@+id/guideline3" app:layout_constraintEnd_toStartOf="@+id/guideline11" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/guideline2" app:layout_constraintVertical_bias="0.682" tools:text="120" /> <TextView android:id="@+id/textView4" android:layout_width="138dp" android:layout_height="100dp" android:text="@{String.valueOf(data.getBTeamScore())}" android:textAlignment="center" android:textColor="#0CD681" android:textSize="@dimen/ScoreTextSize" app:layout_constraintBottom_toTopOf="@+id/guideline3" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="@+id/guideline12" app:layout_constraintTop_toTopOf="@+id/guideline2" app:layout_constraintVertical_bias="0.666" tools:text="0" /> <Button android:id="@+id/button2" android:layout_width="0dp" android:layout_height="wrap_content" android:background="#8BC34A" android:onClick="@{()->data.bTeamAdd(1)}" android:text="@string/button1" android:textColor="#FFFFFF" android:textSize="@dimen/ButtonTextSzie" app:layout_constraintBottom_toTopOf="@+id/guideline4" app:layout_constraintEnd_toStartOf="@+id/guideline10" app:layout_constraintStart_toStartOf="@+id/guideline12" app:layout_constraintTop_toTopOf="@+id/guideline3" app:layout_constraintVertical_bias="0.407" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline9" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" app:layout_constraintGuide_percent="0.05" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline10" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" app:layout_constraintGuide_percent="0.96" /> <Button android:id="@+id/button3" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="20dp" android:background="#F44336" android:onClick="@{()->data.aTeamAdd(2)}" android:text="@string/button2" android:textColor="#FFFFFF" android:textSize="@dimen/ButtonTextSzie" app:layout_constraintBottom_toTopOf="@+id/guideline5" app:layout_constraintEnd_toStartOf="@+id/guideline11" app:layout_constraintStart_toStartOf="@+id/guideline9" app:layout_constraintTop_toTopOf="@+id/guideline4" app:layout_constraintVertical_bias="0.268" /> <Button android:id="@+id/button4" android:layout_width="0dp" android:layout_height="wrap_content" android:background="#8BC34A" android:onClick="@{()->data.bTeamAdd(2)}" android:text="@string/button2" android:textColor="#FFFFFF" android:textSize="@dimen/ButtonTextSzie" app:layout_constraintBottom_toTopOf="@+id/guideline5" app:layout_constraintEnd_toStartOf="@+id/guideline10" app:layout_constraintStart_toStartOf="@+id/guideline12" app:layout_constraintTop_toTopOf="@+id/guideline4" /> <Button android:id="@+id/button5" android:layout_width="0dp" android:layout_height="wrap_content" android:background="#F44336" android:onClick="@{()->data.aTeamAdd(3)}" android:text="@string/button3" android:textColor="#FFFFFF" android:textSize="@dimen/ButtonTextSzie" app:layout_constraintBottom_toTopOf="@+id/guideline6" app:layout_constraintEnd_toStartOf="@+id/guideline11" app:layout_constraintStart_toStartOf="@+id/guideline9" app:layout_constraintTop_toTopOf="@+id/guideline5" /> <Button android:id="@+id/button6" android:layout_width="0dp" android:layout_height="wrap_content" android:background="#8BC34A" android:onClick="@{()->data.bTeamAdd(3)}" android:text="@string/button3" android:textColor="#FFFFFF" android:textSize="@dimen/ButtonTextSzie" app:layout_constraintBottom_toTopOf="@+id/guideline6" app:layout_constraintEnd_toStartOf="@+id/guideline10" app:layout_constraintStart_toStartOf="@+id/guideline12" app:layout_constraintTop_toTopOf="@+id/guideline5" app:layout_constraintVertical_bias="0.507" /> <ImageButton android:id="@+id/imageButton2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:contentDescription="@string/ResetButtton" android:onClick="@{()->data.reset()}" app:layout_constraintBottom_toTopOf="@+id/guideline7" app:layout_constraintEnd_toStartOf="@+id/guideline10" app:layout_constraintStart_toStartOf="@+id/guideline12" app:layout_constraintTop_toTopOf="@+id/guideline6" app:srcCompat="@drawable/ic_baseline_loop_24" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline11" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" app:layout_constraintGuide_percent="0.45373666" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline12" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" app:layout_constraintGuide_percent="0.55" /> <Button android:id="@+id/button" android:layout_width="0dp" android:layout_height="wrap_content" android:background="#F44336" android:text="@string/button1" android:textColor="#FFFFFF" android:textSize="@dimen/ButtonTextSzie" android:onClick="@{()->data.aTeamAdd(1)}" app:layout_constraintBottom_toTopOf="@+id/guideline4" app:layout_constraintEnd_toStartOf="@+id/guideline11" app:layout_constraintStart_toStartOf="@+id/guideline9" app:layout_constraintTop_toTopOf="@+id/guideline3" /> <ImageButton android:id="@+id/imageButton3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:contentDescription="@string/UndoButton" android:onClick="@{()->data.withdraw()}" app:layout_constraintBottom_toTopOf="@+id/guideline7" app:layout_constraintEnd_toStartOf="@+id/guideline11" app:layout_constraintStart_toStartOf="@+id/guideline9" app:layout_constraintTop_toTopOf="@+id/guideline6" app:srcCompat="@drawable/ic_baseline_undo_24" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout>
主程序如下
package com.example.score; import androidx.appcompat.app.AppCompatActivity; import androidx.databinding.DataBindingUtil; import androidx.lifecycle.ViewModelProvider; import android.os.Bundle; import com.example.score.databinding.ActivityMainBinding; public class MainActivity extends AppCompatActivity { MyViewModel myViewModel; ActivityMainBinding binding; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //setContentView(R.layout.activity_main); binding=DataBindingUtil.setContentView(this,R.layout.activity_main); myViewModel=new ViewModelProvider(this).get(MyViewModel.class); binding.setData(myViewModel); binding.setLifecycleOwner(this);//这个一定不能少 } }