一、BindingCollectionAdapter使用
1、简单使用
BindingCollectionAdapter是简单的在Data Binding中绑定listviews 和recyclerviews ,viewpager
github地址:https://github.com/evant/binding-collection-adaptergithub地址:https://github.com/evant/binding-collection-adapter
只要几步就可以完成一个列表的显示。
提供一个items代表数据,必须用ObservableList
-
提供一个itemBinding 表示item的view布局。
class ViewModel { val items = ObservableArrayList<>(); val itemBind: ItemBinding<DemoItem> = ItemBinding.of(BR.item, R.layout.recycle_item_fragment_bindingadapter) }
-
在xml中使用RecyclerView/ViewPager/Spinner/ListView,将items和itemBinding 填充
<!-- layout.xml --> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <import type="com.example.R" /> <variable name="viewModel" type="com.example.ViewModel"/> </data> <ListView android:layout_width="match_parent" android:layout_height="match_parent" app:items="@{viewModel.items}" app:itemBinding="@{viewModel.itemBinding}"/> <androidx.recyclerview.widget.RecyclerView android:layout_width="match_parent" android:layout_height="match_parent" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:items="@{viewModel.items}" app:itemBinding="@{viewModel.itemBinding}"/> <androidx.viewpager.widget.ViewPager android:layout_width="match_parent" android:layout_height="match_parent" app:items="@{viewModel.items}" app:itemBinding="@{viewModel.itemBinding}"/> <Spinner android:layout_width="match_parent" android:layout_height="match_parent" app:items="@{viewModel.items}" app:itemBinding="@{viewModel.itemBinding}" app:itemDropDownLayout="@{R.layout.item_dropdown}"/> </layout>
-
在提供item 的layout
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <variable name="item" type="String"/> </data> <TextView android:id="@+id/text" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@{item}"/> </layout>
简单的4步就完成了对view的绑定。
2、多布局绑定
val itemBind = OnItemBind<DemoItem> { itemBinding, position, item ->
Log.i(TAG, "itemBinding $itemBinding position: $position item $item")
itemBinding.set(BR.item, R.layout.recycle_item_fragment_bindingadapter)
}
3、增加其他变量使用bind
ItemBinding<Item> itemBinding = ItemBinding.<Item>of(BR.item, R.layout.item)
.bindExtra(BR.listener, listener);
二、BindingAdapter
1、引入
我们在写xml时都习惯用databinding来绑定视图和数据,比如下图的TextView,可以通过vm.fpsString设置值
fragment_chroreopragher.xml
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="vm"
type="com.mary.box.point.choreographer.ChoreographerVM" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
...>
<TextView
...
android:text="@{vm.fpsString}"
... />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
ChoreographerVM根据帧率动态的修改TextView中的text。
class ChoreographerVM(application: Application) : BaseViewModel(application) {
val fpsString: ObservableField<String> = ObservableField<String>()
/**
* 计算帧率
*/
private val callback = FrameCallback {
if (!traversalScheduled) {
Log.i(TAG, "skip: ")
return@FrameCallback
}
traversalScheduled = false
if (lastFrameTime == 0L) {
lastFrameTime = it
}
val interval = (it - lastFrameTime) / NANOS_PER_MS
if (interval > 1000) {
val fps = frameCount * 1000 / interval
frameCount = 0
lastFrameTime = 0
sb.append(fps)
sb.append(",")
fpsString.set(sb.toString())
} else {
++frameCount
}
start()
}
fun start() {
if (!isStop) {
if (traversalScheduled) {
Log.i(TAG, "start: skip")
return
}
traversalScheduled = true
Choreographer.getInstance().postFrameCallbackDelayed(callback, getViewTravelDelay())
}
}
fun stop() {
isStop = true
Choreographer.getInstance().removeFrameCallback(callback)
}
}
解析其源码,会发现自动生成一个FragmentChroreopragherBindingImpl
,有个方法很重要。executeBindings
@Override
protected void executeBindings() {
long dirtyFlags = 0;
synchronized(this) {
dirtyFlags = mDirtyFlags;
mDirtyFlags = 0;
}
com.mary.box.point.choreographer.ChoreographerVM vm = mVm;
androidx.databinding.ObservableField<java.lang.String> vmFpsString = null;
java.lang.String vmFpsStringGet = null;
if ((dirtyFlags & 0x7L) != 0) {
if (vm != null) {
// read vm.fpsString
vmFpsString = vm.getFpsString();
}
updateRegistration(0, vmFpsString);
if (vmFpsString != null) {
// read vm.fpsString.get()
vmFpsStringGet = vmFpsString.get();
}
}
// batch finished
if ((dirtyFlags & 0x7L) != 0) {
// api target 1
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView1, vmFpsStringGet);
}
}
这里会对textview进行赋值,但是赋值的不是调用TextView.setText(xx)
,而是调用androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView1, vmFpsStringGet);
跟进去可以看到,
- 封装了一个新的方法setText,是静态的。
- 有个注解@BindingAdapter("android:text")
- 方法实际上也是调用了TextView.setText(xx),但是有做一下数据的校验--性能的优化
@BindingAdapter("android:text")
public static void setText(TextView view, CharSequence text) {
final CharSequence oldText = view.getText();
//校验text内容是否有效
if (text == oldText || (text == null && oldText.length() == 0)) {
return;
}
if (text instanceof Spanned) {
if (text.equals(oldText)) {
return; // No change in the spans, so don't set anything.
}
} else if (!haveContentsChanged(text, oldText)) {
return; // No content changes, so don't set anything.
}
view.setText(text);
}
2、BindingAdapter
2.1 解释
BindingAdapter :绑定适配器,是 Jetpack DataBinding 中用来扩展布局 xml 属性行为的注解,允许你针对布局 xml 中的一个或多个属性进行绑定行为扩展,这个属性可以是自定义属性,也可以是原生属性。这个扩展行为可以是简单的ViewModel属性与控件赋值绑定,也可以是关联某个控件属性的额外操作,例如在设置属性之前进行值域检查,或类型转换,或者统一处理一些事情。
2.2 工作机制
BindingAdapter 是基于 APT 注解技术工作的,APT 可以在项目构建时更具相关编写规则生成特定代码完成一些指定功能的技术.
BindingAdapter 有两个属性,value 是一个 String[] ,requireAll 是一个boolean类型:
@Target(ElementType.METHOD)
public @interface BindingAdapter {
/**
* @return 与此绑定适配器相关的属性。
*/
String[] value();
/**
* 这个值默认是 true,表示使用这个适配规则必须在 XML 中声明该注解关注所有属性值,否则编译时会报错,而 false 则不需要,他允许只使用关注的部分或全部属性来使用该规则The default
* value is true.
*/
boolean requireAll() default true;
}
value 用来描述 XML 中感兴趣的关联属性,这里是个数组,说明一个扩展方法可以同时关注多个 XML 属性。这个很重要,有时候我们可能在对控件进行赋值时,需要同时给定多个值,而 XML 属性值却一次只能指定一个变量,这样可能会让我们不的不为此去扩展一个复合对象来完成这样的赋值,但是实际上并不需要那样。
requireAll 用来对 value 补充说明的,这个值默认是 true,表示使用这个适配规则必须在 XML 中声明该注解关注所有属性值,否则编译时会报错,而 false 则不需要,他允许只使用关注的部分或全部属性来使用该规则
2.3 使用
BindingAdapter声明有固定的格式
// 类构建可以随意,APT会在构建时扫描全局代码
public class ViewAttrAdapter {
// 需要注意的是 XML标签 关注了几个,参数列表就需要写几个对应的接受参数。且关注控件类必须在第一个参数。
@BindingAdapter({xml属性标签, ...})
public static void 函数名(关注的控件类 view, xml属性标签值 value, ...){
// 行为
}
// 可以包含多个函数
.....
}
例如:
public class BindingRecyclerViewAdapters {
@BindingAdapter(value = {"itemBinding", "items", "adapter", "itemIds", "viewHolder", "diffConfig"}, requireAll = false)
public static <T> void setAdapter(RecyclerView recyclerView,
ItemBinding<? super T> itemBinding,
List<T> items,
BindingRecyclerViewAdapter<T> adapter,
BindingRecyclerViewAdapter.ItemIds<? super T> itemIds,
BindingRecyclerViewAdapter.ViewHolderFactory viewHolderFactory,
AsyncDifferConfig<T> diffConfig) {
if (itemBinding != null) {
BindingRecyclerViewAdapter oldAdapter = (BindingRecyclerViewAdapter) recyclerView.getAdapter();
if (adapter == null) {
if (oldAdapter == null) {
adapter = new BindingRecyclerViewAdapter<>();
} else {
adapter = oldAdapter;
}
}
adapter.setItemBinding(itemBinding);
if (diffConfig != null && items != null) {
AsyncDiffObservableList<T> list = (AsyncDiffObservableList<T>) recyclerView.getTag(R.id.bindingcollectiondapter_list_id);
if (list == null) {
list = new AsyncDiffObservableList<>(diffConfig);
recyclerView.setTag(R.id.bindingcollectiondapter_list_id, list);
adapter.setItems(list);
}
list.update(items);
} else {
adapter.setItems(items);
}
adapter.setItemIds(itemIds);
adapter.setViewHolderFactory(viewHolderFactory);
if (oldAdapter != adapter) {
recyclerView.setAdapter(adapter);
}
} else {
recyclerView.setAdapter(null);
}
}
xml中使用:
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycleView"
itemBinding="@{recycleViewViewModel.itemBind}"
items="@{recycleViewViewModel.items}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/btn">
</androidx.recyclerview.widget.RecyclerView>
这样就完成了一个xml和属性设置的绑定。
-
自动生成的源码 直接调用对应的方法。
@Override protected void executeBindings() { ... if ((dirtyFlags & 0x7L) != 0) { // api target 1 me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapters.setAdapter(this.recycleView, me.tatarka.bindingcollectionadapter2.BindingCollectionAdapters.toItemBinding(recycleViewViewModelItemBind), recycleViewViewModelItems, (me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapter)null, (me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapter.ItemIds)null, (me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapter.ViewHolderFactory)null, (androidx.recyclerview.widget.AsyncDifferConfig)null); } }