使用过databing的开发人员,想必对它能够支持自定义属性并不陌生。本文也就是在此基础上,实现对RecyclerView的数据绑定。本文主要涉及到实体类:
- UserBean:用户信息
- BindAdapter:自定义Adapter
- BindRecyclerView:自定义RecyclerView
- OnRecyclerItemClickListener:事件回调接口
- RecyclerViewBinding:自定义绑定属性
一、集成
在项目的gradle中配置如下:
android {
...
dataBinding {
enabled = true
}
}
二、布局文件
1、activity_rv.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">
<data>
<import type="com.xinyartech.mvvm.UserBean"/>
<import type="androidx.databinding.ObservableList"/>
<variable
name="userList"
type="ObservableList<UserBean>" />
<variable
name="presenter"
type="com.xinyartech.mvvm.DataBindingPresenter" />
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/load"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="加载更多"
android:onClick="@{(view)->presenter.loadMore(userList)}"
/>
<Button
android:id="@+id/gc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="验证gc"
/>
<com.xinyartech.mvvm.rv.BindRecyclerView
android:layout_below="@+id/load"
android:id="@+id/leftRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutId="@{@layout/adapter_item2}"
app:list="@{userList}"
app:itemClickListener="@{(item) -> presenter.itemClick((UserBean)item)}"
/>
</RelativeLayout>
</layout>
2、adapter_item2.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="com.xinyartech.mvvm.rv.OnRecyclerItemClickListener"/>
<variable
name="item"
type="com.xinyartech.mvvm.UserBean" />
<variable
name="itemOneClickListener"
type="OnRecyclerItemClickListener" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/btn2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{item.userName}" />
</LinearLayout>
</layout>
三、UserBean
public class UserBean extends BaseObservable {
//方式1 BaseObservable+@Bindable+notifyPropertyChanged
public String userName;
//方式2 ObservableField
public ObservableField<String> pwd = new ObservableField<>();
@Bindable
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
notifyPropertyChanged(BR.userName);
}
}
有两种方式进行双向绑定实现
- 实体类集成BaseObservable,并在getUserName()方法上添加@Bindable注解,在setUserName()方法内,通过notifyPropertyChanged()进行数据更新
- 使用ObservableField标识属性。
这里推荐使用第二种,简单易操作。
四、BinderAdapter
代码有点多,先贴出来,一步步理解。
public abstract class BindAdapter<T> extends RecyclerView.Adapter<BindAdapter.BindHolder> {
private List<T> list = new ArrayList<>();
private ObservableList<T> observableList;
private OnRecyclerItemClickListener itemOneClickListener;
public BindAdapter(final RecyclerView view, ObservableList<T> observableList) {
this.observableList = observableList;
this.list.addAll(observableList);
//添加数据监听
observableList.addOnListChangedCallback(new ObservableList.OnListChangedCallback<ObservableList<T>>() {
@Override
public void onChanged(ObservableList<T> sender) {
}
@Override
public void onItemRangeChanged(ObservableList<T> sender, int positionStart,
int itemCount) {
notifyItemRangeChanged(positionStart,itemCount);
}
@Override
public void onItemRangeInserted(final ObservableList<T> sender, final int positionStart,
final int itemCount) {
view.post(new Runnable() {
@Override
public void run() {
list.add(positionStart, sender.get(positionStart));
notifyItemRangeInserted(positionStart, itemCount);
view.scrollToPosition(positionStart);
}
});
}
@Override
public void onItemRangeMoved(ObservableList<T> sender, int fromPosition,
int toPosition, int itemCount) {
}
@Override
public void onItemRangeRemoved(ObservableList<T> sender, final int positionStart, final int itemCount) {
view.post(new Runnable() {
@Override
public void run() {
if (itemCount == 1) {
list.remove(positionStart);
notifyItemRangeRemoved(positionStart, itemCount);
notifyItemRangeChanged(positionStart, getItemCount() - positionStart, new Object());
} else {
list.clear();
notifyDataSetChanged();
}
}
});
}
});
}
public OnRecyclerItemClickListener getItemOneClickListener() {
return itemOneClickListener;
}
public ObservableList<T> getObservableList() {
return observableList;
}
public void setObservableList(ObservableList<T> observableList) {
this.observableList = observableList;
}
public void setItemOneClickListener(OnRecyclerItemClickListener itemOneClickListener) {
this.itemOneClickListener = itemOneClickListener;
}
public abstract int onCreateViewHolderLayoutId();
@NonNull
@Override
public BindHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ViewDataBinding binding =
DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()),
onCreateViewHolderLayoutId(), parent, false);
return new BindAdapter.BindHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull BindHolder holder, final int position) {
holder.bind(this.list.get(holder.getAdapterPosition()),
new OnRecyclerItemClickListener() {
@Override
public void onRecyclerItemClick(Object item) {
if (itemOneClickListener != null) {
itemOneClickListener.onRecyclerItemClick(item);
}
}
});
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (itemOneClickListener != null) {
itemOneClickListener.onRecyclerItemClick(getItem(position));
}
}
});
}
public T getItem(int position) {
if (position < this.list.size()) {
return this.list.get(position);
} else {
return null;
}
}
@Override
public int getItemCount() {
return list.size();
}
/**
* 自定义ViewHolder
*/
public static class BindHolder extends RecyclerView.ViewHolder {
ViewDataBinding mBinding;
public BindHolder(ViewDataBinding binding) {
super(binding.getRoot());
this.mBinding = binding;
}
public void bind(Object obj,
OnRecyclerItemClickListener itemOneClickListener) {
this.mBinding.setVariable(BR.item, obj);
this.mBinding.setVariable(BR.itemOneClickListener, itemOneClickListener);
this.mBinding.executePendingBindings();
}
}
}
这里将代码分解成以下一个部分:
- 自定义viewHodler
- 绑定item布局
- 添加绑定数据监听
- 添加item点击事件监听
1、自定义ViewHolder、添加item点击事件
public static class BindHolder extends RecyclerView.ViewHolder {
ViewDataBinding mBinding;
public BindHolder(ViewDataBinding binding) {
super(binding.getRoot());
this.mBinding = binding;
}
public void bind(Object obj,
OnRecyclerItemClickListener itemOneClickListener) {
this.mBinding.setVariable(BR.item, obj);
this.mBinding.setVariable(BR.itemOneClickListener, itemOneClickListener);
this.mBinding.executePendingBindings();
}
}
赋值item和条目点击OnRecyclerItemClickListener ,点击事件需要配合holder.itemView.setOnClickListener使用。
2、绑定item布局
@Override
public BindHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ViewDataBinding binding =
DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()),
onCreateViewHolderLayoutId(), parent, false);
return new BindAdapter.BindHolder(binding);
}
其中 onCreateViewHolderLayoutId() == R.layout.adapter_item2.xml 。
3、添加绑定数据监听
通过设置 observableList.addOnListChangedCallback 监听list数据集合变化,并在回调中处理adapter的数据更新,代码如上。
五、BindRecyclerView
public class BindRecyclerView extends RecyclerView {
public BindRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public void setOnRecyclerItemClickListener(OnRecyclerItemClickListener onRecyclerItemClickListener) {
BindAdapter bindAdapter = getBindAdapter();
if (bindAdapter != null && onRecyclerItemClickListener != null) {
bindAdapter.setItemOneClickListener(onRecyclerItemClickListener);
}
}
private BindAdapter getBindAdapter() {
if (getAdapter() instanceof BindAdapter) {
return (BindAdapter) getAdapter();
}
return null;
}
}
六、OnRecyclerItemClickListener
public interface OnRecyclerItemClickListener {
void onRecyclerItemClick(Object item);
}
七、RecyclerViewBinding
这里是重点部分,先贴代码
public class RecyclerViewBinding {
@BindingAdapter(value = {"layoutId", "list","itemClickListener"
}, requireAll = false)
public static <T> void setAdapter(BindRecyclerView view,
@LayoutRes final int layoutId,
ObservableList<T> list,
OnRecyclerItemClickListener itemClickListener){
if (list == null) {
return;
}
if (view.getLayoutManager() == null) {
// default LinearLayoutManager 垂直布局
LinearLayoutManager layoutManager = new LinearLayoutManager(
view.getContext(), LinearLayoutManager.VERTICAL, false);
view.setLayoutManager(layoutManager);
DividerItemDecoration decoration = new DividerItemDecoration(
view.getContext(), LinearLayoutManager.VERTICAL);
//noinspection ConstantConditions 添加分割线
//decoration.setDrawable(ContextCompat.getDrawable(view.getContext(), R.drawable.divider_gray));
view.addItemDecoration(decoration);
}
//noinspection unchecked 绑定adpter
BindAdapter<T> bindAdapter = (BindAdapter<T>) view.getAdapter();
if (bindAdapter == null || !Objects.equals(bindAdapter.getObservableList(), list)) {
//创建适配器
bindAdapter = new BindAdapter<T>(view, list) {
@Override
public int onCreateViewHolderLayoutId() {
return layoutId;
}
};
}
//设置适配器
view.setAdapter(bindAdapter);
//item点击事件
if (itemClickListener != null) {
view.setOnRecyclerItemClickListener(itemClickListener);
}
}
}
添加@BindingAdapter注解,实现自定义属性layoutId(item布局文件)、list(数据集合)、itemClickListener(条目点击)。
以上就完成了自定义具有databinding功能的RecyclerView。其实最主要的还是自定义属性的使用,@BindingAdapter至关重要,如果需要自定义双向绑定就需要配合@InverseBindingAdapter使用了。