这是我参与8月更文挑战的第26天,活动详情查看:8月更文挑战
Android DataBinding扫盲篇(二)
上篇文件简单的讲解了数据绑定的基本操作,但是上篇文章是单向的绑定,这篇文章讲如何进行双向绑定,以及如何使用复杂点的业务场景。
一、双向绑定
什么被称为双向绑定?
数据改变,同步更新到视图上;视图改变,也同步修改数据。
数据改变同步视图很好理解,我们拿到服务器数据后就会封装成对象,对象映射给view直接显示就可以了;
视图改变同步数据,我们的数据都呈现在视图上了,为什么还要同步一遍数据,这是因为安卓中有内容会变化的组件,比如EditText、CheckBox等
按照MVVM的思想,它是这样的流程。
graph LR
A[model] --> B[viewModel]
B --> A
B --> C[view]
C --> B
那么在代码中如何实现双向绑定呢?
CheckBox 单向绑定数据
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="@{person.isSelect}"/>
CheckBox 双向绑定数据
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="@={person.isSelect}"/>
仅仅从@{person.isSelect} -> @={person.isSelect} 即可。
EditText 双向绑定也是一样,当EditText改变的过程中,对象属性也跟着变化了。
eg
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@={person.name}" />
二、进阶用法
2.1 加载图片
布局文件
<ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" app:url="@{person.photo}" />
Bean文件
@BindingAdapter("url")
public static void setURL(ImageView imageView, String photo) {
Glide.with(imageView.getContext()).load(photo).into(imageView);
}
注意布局文件中的app:url 是自定义属性,需要导入xmlns 且和Bean文件中的@BindingAdapter("url") 的 url 一定要对应,这样才能识别到。
我看到有些是这么人是这么写的 @BindingAdapter("bind:url"), 这样写也可以。
Bean文件中的 setURL 可以随意命名,因为它是靠上方的注解识别的。
2.2 ListView加载列表
1. 准备界面
像往常一样,在布局中写一个ListView
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
2. 准备viewmodel类
与平常封装对象不同的是,这个对象类继承了BaseObservable 实现了数据的绑定,并增加了一些方法,比如显示图片、设置点击事件等。
public class Person extends BaseObservable {
private int id;
private String name;
private String photo;
public Person() {
}
public Person(int id, String name, String photo) {
this.id = id;
this.name = name;
this.photo = photo;
}
@Bindable
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
notifyPropertyChanged(BR.id);
}
@Bindable
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name);
}
@Bindable
public String getPhoto() {
return photo;
}
public void setPhoto(String photo) {
this.photo = photo;
notifyPropertyChanged(BR.photo);
}
@BindingAdapter("url")
public static void setURL(ImageView imageView, String photo) {
Glide.with(imageView.getContext()).load(photo).into(imageView);
}
public void itemClick(View view) {
Log.d("Person", "item click");
}
}
3. 准备item的布局
item的布局中@{} 和数据产生了联系,使它们能够绑定在一块。
如果想要执行点击事件,就需要添加 android:onClick="@{person.itemClick}"
<?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">
<data>
<variable
name="person"
type="com.demo.mvvmdemo.Person" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:onClick="@{person.itemClick}">
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
app:url="@{person.photo}" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueOf(person.id)}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{person.name}" />
</LinearLayout>
</LinearLayout>
</layout>
4. 准备适配器
这里我模仿着别人写的通用的适配器,普通的效果都能满足了。 来看看适配器怎么实现的。
public class CommonAdapter<T> extends BaseAdapter {
private final String TAG = "CommonAdapter";
LayoutInflater mLayoutInflater;
int mLayoutId;
int mVariableId;
List<T> mList;
public CommonAdapter(LayoutInflater layoutInflater, int layoutId, int variableId, List<T> list) {
this.mLayoutInflater = layoutInflater;
this.mLayoutId = layoutId;
this.mVariableId = variableId;
this.mList = list;
}
@Override
public int getCount() {
return mList.size();
}
@Override
public Object getItem(int position) {
return mList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewDataBinding binding;
// 复用convertView
if (convertView == null) {
binding = DataBindingUtil.inflate(mLayoutInflater, mLayoutId, parent, false);
convertView = binding.getRoot();
convertView.setTag(binding);
} else {
binding = (ViewDataBinding) convertView.getTag();
}
binding.setVariable(mVariableId, mList.get(position));
return binding.getRoot();
}
}
5. ListView和视图绑定
模拟了一些数据,然后ListView绑定上适配器就可以了。
public class MainActivity extends AppCompatActivity {
List<Person> mPersonList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initData();
ListView listView = findViewById(R.id.list_view);
CommonAdapter<Person> personCommonAdapter = new CommonAdapter<>(getLayoutInflater(), R.layout.item, BR.person, mPersonList);
listView.setAdapter(personCommonAdapter);
}
private void initData() {
for (int i = 0; i < 10; i++) {
mPersonList.add(new Person(i, "姓名" + i, "https://c-ssl.duitang.com/uploads/blog/202107/12/20210712132638_bd95f.thumb.1000_0.jpg"));
}
}
}
2.3 RecyclerView加载列表
除了适配器的不同和绑定方式不同之外,其余的还是按照ListView的方式,没有变动,下面代码是RecyclerView的适配器。
public class CommonAdapter<T> extends RecyclerView.Adapter<CommonAdapter.CommonViewHolder> {
private final String TAG = "CommonAdapter";
LayoutInflater mLayoutInflater;
int mLayoutId;
int mVariableId;
List<T> mList;
public CommonAdapter(LayoutInflater layoutInflater, int layoutId, int variableId, List<T> list) {
this.mLayoutInflater = layoutInflater;
this.mLayoutId = layoutId;
this.mVariableId = variableId;
this.mList = list;
}
@NonNull
@Override
public CommonViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int i) {
ViewDataBinding viewDataBinding = DataBindingUtil.inflate(mLayoutInflater,mLayoutId, parent, false);
CommonViewHolder commonViewHolder = new CommonViewHolder(viewDataBinding.getRoot().getRootView());
commonViewHolder.setViewDataBinding(viewDataBinding);
return commonViewHolder;
}
@Override
public void onBindViewHolder(@NonNull CommonAdapter.CommonViewHolder commonViewHolder, int position) {
//更新视图
commonViewHolder.setContent(mList.get(position));
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public int getItemCount() {
return mList.size();
}
public class CommonViewHolder extends RecyclerView.ViewHolder{
ViewDataBinding viewDataBinding;
public CommonViewHolder(@NonNull View itemView) {
super(itemView);
}
public ViewDataBinding getViewDataBinding() {
return viewDataBinding;
}
public void setViewDataBinding(ViewDataBinding viewDataBinding) {
this.viewDataBinding = viewDataBinding;
}
public void setContent(T t) {
viewDataBinding.setVariable(mVariableId, t);
viewDataBinding.executePendingBindings();
}
}
}