Android DataBinding的官方双向绑定示例
在android studio 2.1 preview 3之后,官方开始支持双向绑定了。
可惜目前google并没有在data binding指南里面加入这个教程,并且在整个互联网之中只有介绍了如何使用反向绑定。
在阅读一下文章之前,我假设你已经知道如何正向绑定。
回顾一下data binding
在正向绑定中,我们在layout里面的绑定表达式是这样的:
<layout ...> <data> <variable type="com.example.myapp.user" name="user"/> </data> <relativelayout ...> <textview android:text="@{user.name}" .../> </relativelayout> </layout>
当user.name的数据改动时,我们的textview都会同步改变文字。
双向绑定
现在假设一种情况,当你更换成edittext时,如果你的用户名user.name已经绑定到edittext中,当用户输入文字的时候,你原来的user.name数据并没有同步改动,因此我们需要修改成:
<layout ...> <data> <variable type="com.example.myapp.user" name="user"/> </data> <relativelayout ...> <edittext android:text="@={user.name}" .../> </relativelayout> </layout>
看出微小的差别了吗?对,就是"@{}"改成了"@={}",是不是很简单?
隐式引用属性
同样你也可以在别的view上引用属性:
<layout ...> <data> <import type="android.view.view"/> </data> <relativelayout ...> <checkbox android:id="@+id/seeads" .../> <imageview android:visibility="@{seeads.checked ? view.visible : view.gone}" .../> </relativelayout> </layout>
当checkbox的状态发生改变的时候,imageview也会同时发生改变。在复杂情况下,这个特性没什么卵用,因为逻辑部分我们是不建议写在xml中。
如何开启双向绑定
开启双向绑定,需要在项目的build.gradle中设置:
classpath 'com.android.tools.build:gradle:2.1.0-alpha3'
同样,你需要在你module的build.gradle中设置:
android { ... databinding.enabled = true }
貌似还有点问题
我们刚才的例子里面只显示了系统自带的应用,那么如果是自定义控件,或者是我们更细颗粒度的observable呢?等下就揭晓如何自定义自己的双向绑定,我们来看看目前android支持的控件:
- abslistview android:selecteditemposition
- calendarview android:date
- compoundbutton android:checked
- datepicker android:year, android:month, android:day
- numberpicker android:value
- radiogroup android:checkedbutton
- ratingbar android:rating
- seekbar android:progress
- tabhost android:currenttab (估计没人用)
- textview android:text
- timepicker android:hour, android:minute
自定义双向绑定
设想一下我们使用了下拉刷新swiperefreshlayout
控件,这个时候我们希望在加载数据的时候能控制refreshing的状态,所以我们加入了observableboolean的变量swiperefreshviewrefreshing
来正向绑定数据,并且能够在用户手动下拉刷新的时候同步更新swiperefreshviewrefreshing数据:
// swiperefreshlayout.java public class swiperefreshlayout extends view { private boolean isrefreshing; public void setrefreshing() {/* ... */} public boolean isrefreshing() {/* ... */} public void setonrefreshlistener(onrefreshlistener listener) { /* ... */ } public interface onrefreshlistener { void onrefresh(); } }
接下来我们需要告诉框架,我们需要将swiperefreshlayout
的isrefreshing的值反向绑定到swiperefreshviewrefreshing
:
@inversebindingmethods({ @inversebindingmethod( type = android.support.v4.widget.swiperefreshlayout.class, attribute = "refreshing", event = "refreshingattrchanged", method = "isrefreshing")})
这是一种简单的定义,其中event和method都不是必须的,因为系统会自动生成,写出来是为了更好地了解如何绑定的,可以参考官方文档inversebindingmethod。
当然你也可以使用另外一种写法,并且如果你的值并不是直接对应observable的值的时候,就可以在这里进行转换:
@inversebindingadapter(attribute = "refreshing", event = "refreshingattrchanged") public static boolean isrefreshing(swiperefreshlayout view) { return view.isrefreshing(); }
上面的event同样也不是必须的。以上的定义都是为了让我们能够在布局文件中使用"@={}"这个双向绑定的特性。接下来你需要告诉框架如何处理refreshingattrchanged事件,就像处理一般的监听事件一样:
@bindingadapter("refreshingattrchanged") public static void setonrefreshlistener(final swiperefreshlayout view, final inversebindinglistener refreshingattrchanged) { if (refreshingattrchanged == null) { view.setonrefreshlistener(null); } else { view.setonrefreshlistener(new onrefreshlistener() { @override public void onrefresh() { colorchange.onchange(); } }); } }
一般情况下,我们都需要设置正常的onrefreshlistener,所以我们可以合并写成:
@bindingadapter(value = {"onrefreshlistener", "refreshingattrchanged"}, requireall = false) public static void setonrefreshlistener(final swiperefreshlayout view, final onrefreshlistener listener, final inversebindinglistener refreshingattrchanged) { onrefreshlistener newvalue = new onrefreshlistener() { @override public void onrefresh() { if (listener != null) { listener.onrefresh(); } if (refreshingattrchanged != null) { refreshingattrchanged.onchange(); } } }; onrefreshlistener oldvalue = listenerutil.tracklistener(view, newvalue, r.id.onrefreshlistener); if (oldvalue != null) { view.setonrefreshlistener(null); } view.setonrefreshlistener(newvalue); }
现在我们终于可以使用双向绑定的技术啦。但是要注意,需要设置requireall = false,否则系统将识别不了refreshingattrchanged属性,前文提到的文章例子里并没有设置这个。
在viewmodel中,我们的数据是这样的:
// myviewmodel.java public final observableboolean swiperefreshviewrefreshing = new observableboolean(false); public void load() { swiperefreshviewrefreshing.set(true); // 网络请求 .... swiperefreshviewrefreshing.set(false); } public swiperefreshlayout.onrefreshlistener onrefreshlistener() { return new swiperefreshlayout.onrefreshlistener() { @override public void onrefresh() { // do something you need } }; }
在布局文件中是这样设置的:
<android.support.v4.widget.swiperefreshlayout android:id="@+id/swipe_refresh_view" android:layout_width="match_parent" android:layout_height="match_parent" app:onrefreshlistener="@{viewmodel.onrefreshlistener}" app:refreshing="@={viewmodel.swiperefreshviewrefreshing}"> ... </android.support.v4.widget.swiperefreshlayout>
最后我们还有一个小问题,就是双向绑定有可能会出现死循环,因为当你通过listener反向设置数据时,数据也会再次发送事件给view。所以我们需要在设置一下避免死循环:
@bindingadapter("refreshing") public static void setrefreshing(swiperefreshlayout view, boolean refreshing) { if (refreshing != view.isrefreshing()) { view.setrefreshing(refreshing); } }
这样就没问题啦。以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。