BindingCollectionAdapter2与BindingAdapter

一、BindingCollectionAdapter使用

1、简单使用

BindingCollectionAdapter是简单的在Data Binding中绑定listviews 和recyclerviews ,viewpager

github地址:https://github.com/evant/binding-collection-adaptergithub地址:https://github.com/evant/binding-collection-adapter

只要几步就可以完成一个列表的显示。

  1. 提供一个items代表数据,必须用ObservableList

  2. 提供一个itemBinding 表示item的view布局。

    class ViewModel {
     val items = ObservableArrayList<>();
     val itemBind: ItemBinding<DemoItem> = ItemBinding.of(BR.item, R.layout.recycle_item_fragment_bindingadapter)
    }
    
  3. 在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>
    
  4. 在提供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);

跟进去可以看到,

  1. 封装了一个新的方法setText,是静态的。
  2. 有个注解@BindingAdapter("android:text")
  3. 方法实际上也是调用了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和属性设置的绑定。

  1. 自动生成的源码 直接调用对应的方法。

    @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);
        }
    }
    
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,277评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,689评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,624评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,356评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,402评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,292评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,135评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,992评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,429评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,636评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,785评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,492评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,092评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,723评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,858评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,891评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,713评论 2 354

推荐阅读更多精彩内容