前言
从我开始接触Android再用使用MVC当时不太懂架构,也不太懂和框架的区别,后来随着工作经验的增多这些架构自然而然的都会用到或者体会到他们在软件开发中的作用;今天不谈MVC和MVP这些算是老的架构了,不过很多公司还在用MVP,MVC的估计很少了,估计有人或说MVP用着用着又用回MVC了,其实都差不多是在MVC的基础上发展进化而来,MVC可以说他们的先祖;今天简单学习以下MVVM架构基础和应用;
MVVM
MVVM和MVP类似,就是把MVP中的Presenter改为ViewModel,不同之处就是ViewModel与View和Model进行双向绑定,即当View发生变化时,viewmodel通知Model更新数据,同样当model中的数据变化时,viewmodel通知View更新;2015年左右Google的IO大会上推出Databinding,帮助开发人员在Android实现MVVM架构的;
MVVM和DataBinding的关系
MVVM可以说是一种思想,而DataBinding是帮助我们在Android项目中实现MVVM架构的工具库,在 google 推出 DataBinding 之前,因为xml layout功能较弱,想实现 MVVM 非常困难,而 DataBinding 的出现可以让我们很方便的实现 MVVM。
DataBinding的使用
官网文档:数据绑定库 | Android 开发者 | Android Developers数据绑定库 | Android 开发者 | Android Developers数据绑定库 | Android 开发者 | Android Developers
一,使用
开启databinding
在AndroidStudio(版本大于1.3 AGP大于1.5)项目app下build.gradle开启databinding
早期版本的AndroidStudio和 AGP这样开启
android {
dataBinding {
enabled = true
}
}
androidStudio4.0以上
buildFeatures {
dataBinding true
}
二,布局文件的改变
开启之后,使用databinding,xml和之前的下发有些不同,xml最外层布局使用layout标签,如果使用model和View绑定需要使用data标签引入model并定义变量;
首先顶定义一个model实体类
data class Student(var name:String,var age:Int)
接着编写XML布局文件activity_test.xml
<?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="mStudent"
type="com.ang.mvvmitem.Student" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".TestActivity">
//注意EditText中text属性需要的是String类型的,如果直接输入Integer会报错,所以需要转换一下
<EditText
android:id="@+id/editText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueOf(mStudent.age)}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{mStudent.name}"
android:layout_marginLeft="20dp"
app:layout_constraintStart_toEndOf="@+id/editText"
app:layout_constraintTop_toTopOf="parent" />
</layout>
这个布局变化,跟布局变为layout 接着是一个data标签,然后才是我们传统的布局视图,在data中定义variable节点,其中name属性表示变量的名称,type属性表示变量的类型,在本例就是Student这实体类的位置。variable节点的每一个变量都会在Binding辅助类中生成对应的getters和setters。接着将@{String.valueOf(mStudent.age)}和@{mStudent.name}赋值给TextView的text属性。最后在Activity中将实体类和布局文件进行绑定;
Activity中将model的实体类和XML布局文件进行绑定
class TestActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// setContentView(R.layout.activity_test)
val binding:ActivityTestBinding = DataBindingUtil.setContentView<ActivityTestBinding>(this,R.layout.activity_test)//注意绑定布局的用法和viewBinding区分
binding.mStudent = Student("旺仔",18)//可以分开写先创建Student对象再设置给binding.setStudent()方法,Java中一般这么写
}
}
ActivityTestBinding是根据activity_test.xml生成的一个ViewModel类(Binding辅助类),它包含了布局文件中所有的绑定关系,并根据绑定表达式给布局文件赋值。
三,布局属性
3.1支持import用法,通过data标签下import标签导入类名和通过 import的alias属性指定别名;
3.2,定义变量
就是data标签下的variable标签定义变量,例如:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="name"
type="String" />
<variable
name="age"
type="int" />
<variable
name="girl"
type="boolean" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".TestActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{name}"
android:layout_marginLeft="20dp" />
<TextView
android:id="@+id/tv1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueOf(age)}"/>
<TextView
android:id="@+id/tv2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueof(girl)}"/>
</LinearLayout>
</layout>
注意和Java一样java.lang.*包会自动导入,不需要再使用import导入,XML 中定义了name age girl 变量,类型分别为String,int ,boolean类型,再Activity中赋值或者使用
binding2.age = 12
binding2.name = "万三"
binding2.girl = false
除了基本数据类型外还可定义引用数据类型,比如List Map Set集合或者自定义类型Student,其实String就是引用数据类型还有基本数据类型的包装类,或者Kotlin中的基本数据类型都可以;
3.4,自定义Binding类名
例如ActivityTestBinding是根据XML布局自动生成,也可以自定义生成;通过XML中data标签的class属性
3.5,支持表达式
数学表达式:+ - * / %
字符串拼接: + -
位运算符:& | ^
一元操作符:+ - ! ~
位移操作符:>> >>>
比较操作符:== > < >= (请注意, 需要转义为 <)
instanceof(java)
分组操作符:()
字面量
character String numeric null
强转、方法调用
字段访问
数组访问:[]
三元操作符:?:
3.6,转换器Converter
四,dataBinding事件处理
dataBinding在Activity中使用上面已经介绍
DataBindingUtil.setContentView()
dataBinding在Fragment RecycleView ListView 中使用
val listItemBinding = ListItemBinding.inflate(layoutInflater, viewGroup, false) //ListItemBinding 根据具体的布局XML文件名生成,这个改成自己的布局文件去掉_的驼峰命名法生成的binding类名
// or
val listItemBinding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false)
val view = listItemBinding.root
可能会带来内存泄漏,在onDestroy中把binding设置为null即可;
通过Databinding实现动态更新View及View和Model双向绑定
双向绑定:Model和View通过viewmodel进行双向动态更新
使用Databinding中的Observable创建model实体类BOBStudent
//model实体类
class BOBStudent :BaseObservable() {
@get:Bindable
var name:String? = null
set(value){
field = value
notifyPropertyChanged(BR.name)
}
@get:Bindable
var age:Int? = null
set(value){
field = value
notifyPropertyChanged(BR.age)//BR报错在build.gradle(app)下引入kotlin-kapt插件
}
}
引入kotlin-kapt,注意写法不同的AGP版本下的下方不尽相同
plugins { id 'kotlin-kapt' }
XML布局文件fragment_test.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="com.ang.mvvmitem.Utils"/>
<variable
name="bobStudent"
type="com.ang.mvvmitem.BOBStudent" />
<variable
name="flashData"
type="android.view.View.OnClickListener" />
</data>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".BindingFragment">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@={bobStudent.name}" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{bobStudent.name}" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{Utils.Converter.intToString(bobStudent.age)}"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{flashData}"
android:text="显示数据"/>
</androidx.appcompat.widget.LinearLayoutCompat>
</layout>
Fragment中将model的实体类和XML布局文件进行绑定
class BindingFragment : Fragment() {
var binding: FragmentTestBinding? = null;
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
binding = inflate(inflater, R.layout.fragment_test, container, false)
return binding?.root;
}
override fun onStart() {
super.onStart()
val student = BOBStudent();
student.name = "小无忌"
student.age = 11
binding?.bobStudent = student
binding?.setFlashData {
Toast.makeText(context,"刷新",Toast.LENGTH_LONG).show();
student.name = "小贱"
}
}
}