这篇文章主要是介绍使用,可用于学习使用或者复习使用,至于想详细了解其他,请查询其他博客;
DataBinding的使用
简单介绍:
DataBinding是Android Jetpack中的一个库,实现了数据绑定的设计模式,允许在布局文件中将UI组件和数据相互绑定,建立声明式的连接关系;
基本原理:双向绑定机制,建立视图和数据模式的双向绑定;
- 当数据发生变化时,自动更新UI的显示;
- 当UI变化时,自动更新数据;
- 代码生成过程:DataBinding在编译时期通过注解处理器分析布局文件,自动生成绑定类,这些类包含了所有必要的绑定逻辑,在运行时可以直接操作视图,避免了findviewbyid的繁琐操作;
使用:
在你的AndroidManifest里面设置一下:
dataBinding{
enabled = true
}
数据模型实体类:
package com.example.mvvm;
public class Rjsb {
private String name;
private int id;
public Rjsb(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
布局设置
把你的布局文件中的根布局替换成layout, 添加以下结点:
data节点:存放一系列的variable节点,每一个布局中只有一个data结点;
variable节点:布局文件中声明数据模型的类型和名称,是连接数据类和对应的view视图的中转点;
- name:你后续在view中使用的就是bind.setname = “”,所以这个很重要
- type: 引入这个类
<data
>
<variable
name="rjsbljx"
type="com.example.mvvm.Rjsb" />
</data>
定义你想实现的布局,然后通过@{rjsbljx.name}和UI所需要显示的地方相绑定,这里就是显示文本,就是android:text 和 对应的数据模型的某个字段;
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text = "@{String.valueOf(rjsbljx.id)}">
</TextView><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text = "@{rjsbljx.name}"></TextView></LinearLayout>
设置布局文件的方式变了,通过DataBindingUtil类的setContentView方法设置,;
得到数据模型类的实例;
设置给databinding;
就实现绑定了;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
ActivityMainBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_main);
Rjsb rjsb =new Rjsb(1,"李佳雪");
binding.setRjsbljx(rjsb);
}
}
加入点击事件:
- 引入view的点击事件监听类,后面直接设置监听事件…
<variable
name="xxonclicklistener"
type="android.view.View.OnClickListener" />
- 通过android:onClick="@{xxonclicklistener}"设置给这个按钮
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id = "@+id/bt_mainat"
android:onClick="@{xxonclicklistener}">
</Button>
- 设立点击事件呗;
binding.btMainat.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(),"xxxhswy",Toast.LENGTH_SHORT).show();
}
});
binding.setXxonclicklistener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(),"xxbxhswy",Toast.LENGTH_SHORT).show();
}
});
好吧,一般在mvvm中,在viewmodel层我们实现不同点击事件;然后在xml中
public class MainViewModel extends ViewModel {
public void onBtn1Click(View view) { /* ... */ }
public void onBtn2Click(View view) { /* ... */ }
}
然后这么写:
vm::onBtn1Click叫做方法引用(把某个方法当做对象来传递);
这句话的意思就是把vm这个类的onBtn1Click方法传递给DataBinding;
<variable
name="vm"
type="com.example.MainViewModel" />
<Button
android:onClick="@{vm::onBtn1Click}" />
<Button
android:onClick="@{vm::onBtn2Click}" />
通过import引入类:
这里强调一下,如果引入类的类名相同,我们可以通过 alias属性起一个别名;
<import type="com.example.mvvm.Rjsb"> </import><import type="ljx.Rjsb" alias="swy"> </import><variablename="rjsbljx"type="swy" />
使用List,Map
在view中创建List和Map的实例:
ArrayList<String> strings = new ArrayList<>();strings.add("ljxaswy");Map map = new HashMap();map.put("age","ljxxhswy");binding.setList(strings);binding.setMap(map);
在xml文件中使用import引入类,并且variable定义具体的泛型;
type="ArrayList<String>" :<代表"<" ,&rt代表“>”
<import type="com.example.mvvm.Rjsb"> </import><import type="java.util.ArrayList"> </import><import type="java.util.Map"> </import><variablename="rjsbljx"type="Rjsb" /><variablename="list"type="ArrayList<String>" /><variablename="map"type="Map<String,String>" /><variablename="arrays"type="String[]" />
在xml文件中控件的使用:
这里强调一下,为什么外面是单引号;
因为在xml语法中,如果你写成"(1)map[“(2)age”]" ,那么xml识别到(1)、(2)处的引号就以为自动结束,属性值为map[ ,所以我们不可以让双引号套双引号;
那么解决办法也很清晰了,要么,外面用单引号,要么里面有反引号标识 " " ,比如map[sd`];或者内部使用单引号;
list你就使用list.get(下标)或者List[下标] ;;; map你就使用map[key]
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{list.get(0)}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text = '@{map["age"] == null ? "不知道" : map["age"]}'>
</TextView>
修改databinding实体类名字
通过data结点下的class属性,定义我们的名字;
<data
class="com.example.mvvm.xzr.Xzrs">
view中使用:
com.example.mvvm.xzr.Xzrs binding = DataBindingUtil.setContentView(this,R.layout.activity_main);
数据统一转换
作用:自动把一种类型转换为另一种类型(比如 Date → String)。
生效范围:只要布局绑定了这种类型,DataBinding 就会自动调用。
不是每个 data 都会改,只有类型匹配的才会触发;
需要理解的是,一定要加上这个注解才能起效:@BindingConversion;
达到的效果,我们不需要手动每次都转换data->string,每次只需要传入data类,由于注解的方法会自动把data转换为string,最后显示到textview等类型匹配的地方;
使用如下:
public class Util {
@BindingConversion
public static String convertDate(Date date){
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-mm-dd");
return simpleDateFormat.format(date);
}
}
引入data类
<variable
name="gaitime"
type="java.util.Date" />
设置一个data类,但是由于会把所有的data类全部转换为string
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text = '@{gaitime}'>
</TextView>
RV的使用
关于recyclerview,与之前的主要不同点就是适配器了,在适配器中,oncreateviewholder方法参数返回的不是view,而是子项的binding;然后在Holder中得到binding并且设置方法使得在onbind方法中得到binding,最后我们再通过binding设置不同数据实现databinding;
使用Observable实现动态更新数据
在我们的实体类中,继承 BaseObservable
在get方法加上注解 @Bindable
在set方法中刷新 notifyPropertyChanged(BR.bookId);
public class Book extends BaseObservable {
private int bookId;
private String bookName;
public Book(int bookId, String bookName) {
this.bookId = bookId;
this.bookName = bookName;
}
public int getBookId() {
return bookId;
}
public void setBookId(int bookId) {
this.bookId = bookId;
notifyPropertyChanged(BR.bookId);
}
@Bindable
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
notifyPropertyChanged(BR.bookName);
}
}
这里我们通过按钮更新数据;
Book book = new Book(1,"ljxxhswy");
binding.setBookin(book);
binding.bt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
book.setBookName("swyxhljx");
}
});
使用ObservableField实现动态更新数据
字段使用ObservableField<泛型>来得到实例,其余不变;
public class Book {
public ObservableField<Integer> id = new ObservableField<>();public ObservableField<String> name = new ObservableField<>();public Book(String bookName, int bookId) {this.id.set(bookId);this.name.set(bookName);}public ObservableField<Integer> getId() {return id;}public void setId(ObservableField<Integer> id) {this.id = id;}public ObservableField<String> getName() {return name;}public void setName(ObservableField<String> name) {this.name = name;}}
实现双向绑定
实现双向绑定:在xml文件中改成@={}就行;
LiveData的使用
简介:
介绍:LiveData是一种可观察的数据存储器类;比较鲜明的特征就是LIveData具有生命周期的感知能力,能够响应组件(活动服务碎片等)生命周期,以确保LiveData仅更新处于活跃生命周期状态下的组件观察者;
总结:
- 是一个类,是一个储存信息的类,可以观察;
- 可以感知组件的生命周期;
- 更新处于活跃状态下的生命周期;
那么问题来了,什么可以被称之位活跃状态?
观察者的组件的生命周期位于Started或Resumed状态;
- 当组件的状态变化destroyded时->观察者 LifecycleObserver 组件的状态也变为 Destroyed ->系统会自动移除观察者;
- 当我们更新livedata中的值时,会触发所有注册这个livedata的观察者(处于活跃状态的);
优势
自动更新界面数据:遵循观察者模式,当数据发生改变时,livedata会通知Observer对象,观察者会替你进行更新;
不会因为活动的停止就崩溃,因为当组件变为非活跃状态时,只会不接受livedata事件;而且不需要手动处理生命周期,livedata会自动管理这些事件,做出响应->比如当旋转屏幕/由不可见变得可见时->会接受最新的数据;
LiveData的使用
在viewmodel中:创建一个livedata实例,储存数据;
在活动/碎片中:创建observer的实例,通过onchanged方法,响应liveData数据的变化;
在活动/碎片中:将两者相关联,通过observer方法,采用LifecycleOwner的方法,使Observer对象订阅LiveData对象;
没有关联LifecycleOwner的话:
- 使用obServeForever(Observer)的方式来注册观察者;
- 通过removeObserver(Observer) 来移除观察者;
这种情况下,观察者始终会被认为是活跃的状态,始终都会接受到livedata的通知;
1. 创建livedata的对象
livedata可以承载任何数据的容器,通常用于viewModel对象中,并且通过getter方法进行访问;
public class BookViewModel extends ViewModel {
private MutableLiveData<Book> mutableLiveData;public MutableLiveData<Book> getBook(){if(mutableLiveData == null){mutableLiveData = new MutableLiveData<>();}return mutableLiveData;}}
为什么不放在活动/碎片中?
- 活动/碎片中,你应该去做UI的更新,不能储存数据喔!!!
- 不随着活动/碎片生命周期,这样在配置更改之后livedata才能继续保存啊
2. 观察livedata对象
在活动/碎片中的oncreate方法中开始观察livedata:
- 确保活动/碎片变为活跃状态之前具有可以显示的数据;当处于活跃状态时,会从正在观察的livedata对象中获取最新值;
如果观察者从第二次非活跃变为活跃时,只有在上次变为活跃状态以来值发生变化,才会更新;
Book book1 = new Book("ll","jj");
binding.setBookin(book1);
//获得viewmodel对象
BookViewModel bookViewModel = new ViewModelProvider(this).get(BookViewModel.class);
//创建观察者
final Observer<Book> observer = new Observer<Book>() {@Overridepublic void onChanged(Book book) {//更新UIbinding.setBookin(book);}};//观察这个livedata,使得活动作为lifecycleownerbookViewModel.getBook().observe(this,observer);调用observe之后,如果给livedata设置值了,会调用Onchanged方法,否则不会;当我们使用databinding时,就不需要写观察者了,xml能使用livedata,自动根据变化更新UI,databinding自动帮我注册了观察者:databinding.setLifecycleOwner(this);
创建观察者的两种方式:
方式一:
//创建观察者
final Observer<Book> observer = new Observer<Book>() {@Overridepublic void onChanged(Book book) {//更新UIbinding.setBookin(book);}};//观察这个livedata,使得活动作为lifecycleownerbookViewModel.getBook().observe(this,observer);
就是new一个观察者,通过Livedata的Obsetve方法
方式二:
结合databinding:
databinding.setLifecycleOwner(this);
livedata没有公开可用的方法来更新存储的数据,MutableLiveData setvalue(T)(修改值),postvalue(T)可以修改livedata对象中的值,所以通常会使用MutableLiveData;
- setvalue(T):必须用在主线程;
- postvalue(T):任意线程都可以使用,底层会使用:handler.post;
binding.bt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
bookViewModel.getBook().setValue(new Book("ljxxhswy","swyxhljx"));
}
});
ViewModel的使用
ViewModel简介
- ViewModel是一个组件,主要功能是储存UI数据,在活动/碎片生命周期变化时保存数据;
ViewModel的工作原理
- viewmodelStoreviewmodelStore数据存储:当活动/碎片销毁时,保存数据;
- 生命周期的感知:它的生命周期和活动/碎片生命周期解耦,当组件销毁时,viewmodel会自动清除,它只在组件的生命周期内生效;
- 减少内存泄漏的风险:因为viewmodel保证了它和UI组件解耦,所以避免了UI组件的直接引用,减少了内存泄漏的风险;
viewModel的优劣
除了以上提到的活动或者碎片销毁重建时,viewmodel会自动保存数据,还有一个优势就是作用域;
- viewmodel储存在viewmodelStoreOwner里的viewmodelStore里面(哈希表,key是viewmodel类名,value是viewmodel);
- viewmodel的作用域是viewmodelStoreOwner的生命周期,它会一直在内存,直到viewmodelStoreOwner永久消失;
- 当活动/碎片销毁时,异步任务会到viewmodel里进行,所以这也是为什么能销毁后重建依旧数据保存的核心把;
- 实例化viewmodel时,它会传递实现viewmodelStoreOwner的对象:活动、碎片、 NavBackStackEntry(每一个通过导航跳转的活动/碎片都有一个此实例);
ViewModel的生命周期
生命周期开始是从viewmodel第一次被创建,结束是:
- activity:活动完成(finish())
- fragment:碎片分离(remove())
- Navigation条目:从返回栈中移除时;
ViewModel+LiveData+DataBinding的完整实例
添加viewmodel的依赖
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
其他的内容大家移步这篇文章:【Android】常见的架构模式:MVC, MVP, MVVP中的MVVM中的实例;
好啦,本次分享到此结束;