安卓 Data Binding 使用方法总结(妹妹篇)
2016-06-15 14:38
309 查看
前言
本文是《 安卓 Data Binding 使用方法总结(姐姐篇)》的姊妹篇。姐姐篇写于 Google I/O 2016 之前,当时还没有双向绑定、lambda 表达式这些特性。本文参考视频 Advanced Data Binding - Google I/O 2016 、经实战后写成,主要涉及:
双向绑定
lambda 表达式
特殊变量
动画
自定义字体
一起来充电吧!
双向绑定
用法举例
很简单,在要使用双向绑定的地方,使用 “@={}” 即可。<EditText android:text="@={user.firstName}" />
注意,这里的 firstName 必须是 ObservableField <T> 类型,至于原因,我们在探讨双向绑定的实现原理的时候会说明。
适用范围
双向绑定只适用于那些某个属性绑定监听事件的控件,如TextView/EditView/Button (android:text, TextWatcher)
CheckBox (android:checked, OnCheckedChangeListener)
DatePicker(android:year, android:month, android:day, OnDateChangedListener)
TimePicker(android:hour, android:minute, OnTimeChangedListener)
RatingBar(android:rating, OnRatingBarChangeListener)
…
大部分控件都能满足双向绑定的需求,实在不行就自定义满足该要求的控件吧。
原理简析
双向绑定的实现原理的核心是 InverseBindingListener 这个接口:package android.databinding; public interface InverseBindingListener { void onChange(); }
我们再看这个例子:
<EditText android:text="@={user.firstName}" />
此时框架生成对应的 MainActivity2WayBinding.java,摘录其中的相关代码:
private android.databinding.InverseBindingListener mboundView1androidTe = new android.databinding.InverseBindingListener() { @Override public void onChange() { // some code firstNameUser.set((java.lang.String) (callbackArg_0)); // some code } }; @Override protected void executeBindings() { if ((dirtyFlags & 0x7L) != 0) { android.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView1, firstNameUser); } }
简单分析一下上述代码。
框架自动生成了一个
android.databinding.InverseBindingListener,该 listener 的作用就是更新 firstName 的值,即 firstName.set()。所以 firstName 必须是 ObservableField<T> 类型。
然后该 listener 被绑定到 EditText 上。
firstName 值被更新时,会执行 executBindings(),调用 TextViewBindingAdapter.setText() 方法:
@BindingAdapter("android:text") public static void setText(TextView view, CharSequence text) { final CharSequence oldText = view.getText(); 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); }
只有值发生改变时才会更新 EditText 的值,否则什么都不做,防止出现无限循环。
lambda 表达式
<Button android:onClick="@{(v) -> presenter.save(v, user)}" />
在这个 lambda 表达式中,
OnClickListener.onClick(View v)没有返回值,
presenter.save(v, user)有或没有返回值都能正常运行;但是如果事件的方法有返回值,如
OnLongClickListener.onLongClick(View v)返回布尔值,则
android:onLongClick='@{(v) -> presenter.save(v, user)}'中的方法
presenter.save(v, user)必须返回对应类型的值,否则编译报错。
@{(v) -> presenter.save(v, user)}中,-> 前面的参数要么为空,要么全部列出来并和对应的 listener 的回调方法的参数保持一致,参数可随意命名,比如
OnFocusChangeListener.onFocusChanged(View v, boolean hasFocus),可以写成
android:hasFocus='@{() -> presenter.refresh(fcs)}'或
android:hasFocus='@{(v, fcs) -> presenter.refresh(fcs)}'的形式。
除了 lambda 表达式,我们还可以使用方法引用(method reference)的形式:
android:onClick='@{presenter.save}',这两种方式有什么区别呢?直接引用发布会上 keynote 中的截图:
和
在回调方法的参数方面也有所不同,在 lambda 表达式的参数可以是任意表达式,而方法引用的参数则必须要和 listener 的回调方法保持一致:
lambda
"... = @{()->presenter.save(user.friend)}" "... = @{()->data.presenter.save(user.friend)}"
this.saveButton.setOnClickListener(this); void onCick(View view) { Presenter presenter = this.presenter; Item item = this.item; if (presenter != null) { presenter.saveItem(item); } }
方法引用
"...onClick=@{presenter::save}"
Presenter presenter = this.presenter; if (presenter != null) { this.saveButton.setOnClickListener(new Listener(presenter)); } else { this.saveButton.setOnClickListener(null); } class Listener implements OnClickListener { void onClick(View view) { mPresenter.onClick(view); } }
注意,方法引用中,回调方法返回值的类型(不管是 void,boolean,还是其他)都要一致,否则编译报错。如,
...onClick='@{presenter::save}'中 Presenter.save(View v) 的返回值类型要和 OnClickListener.onClick(View v) 一致。
方法引用不止能在
android:onClick=属性中使用,在其他属性中也可以使用,而且可以使用表达式作为参数,见 特殊变量->Context 一节中的例子:
<TextView android:id="@+id/context_demo" android:text="@{user.load(context, @id/context_demo)}" /> public String load(Context context, int field) { return context.getResources().getString(R.string.app_name); }
特殊变量
带 id 的控件
可以在表达式中直接引用带 id 的 view,引用时采用驼峰命名法。将一个控件的属性赋值给另一个属性,这样我们可以在 layout 中完成 UI 的展示逻辑,简洁而且可读性强,从而让开发者把精力集中在业务逻辑的开发。
如下面的代码,是根据 CheckBox 是否勾选而决定是否展示相应的控件。第一个 EditText 的表达式中引用了 CheckBox 的属性 checked,第二个 EditText 引用第一个 EditText 的 visibility 属性。
<CheckBox android:id="@+id/checkbox" android:text="填写姓名" /> <EditText android:id="@+id/first_name" android:text="@={user.firstName}" android:visibility="@{checkbox.checked ? View.VISIBLE : View.GONE}" /> <EditText android:text="@{user.lastName}" android:visibility="@{firstName.visibility}" />
Context
现在我们可以脱离具体的 View 就能得到 Context,得到 Context 是根 view 的 Context。注意如果有名为 context 的自定义变量存在,前者会被覆盖掉。
<TextView android:id="@+id/context_demo" android:text="@{user.load(context, @id/context_demo)}" /> public String load(Context context, int field) { return context.getResources().getString(R.string.app_name); }
动画
在 DataBinding 中,我们可以使用 Transition (适用 API >= 19,系统 >= 4.4)来实现某些动画效果。如理如下:binding.addOnRebindCallback(new OnRebindCallback() { @Override public boolean onPreBind(ViewDataBinding binding) { ViewGroup root = (ViewGroup) binding.getRoot(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { TransitionManager.beginDelayedTransition(root); } return true; } });
两个 TextView 在隐藏和显示时会有延迟效果,如下图:
但是这种方法对某些情况是失效的,如随着滚轮的滑动 TextView 的内容发生改变:
更具普遍性的方法是在 @BindingAdapter 修饰的方法中进行设置:
@BindingAdapter("adText") public static animateTextChanges(TextView textView, String oldText, String newText) { if (oldText == null || oldText.equals(newText)) { return; } animateTextChange(textView, oldText, newText); }
自定义字体
我们可以通过如下方式来为 TextView 自定义字体:<TextView app:font="@{`Source-Sans-Pro-Regular.ttf`}"/> public class AppAdapters { @BindingAdapter({"font"}) public static void setFont(TextView textView, String fontName){ AssetManager assetManager = textView.getContext().getAssets(); String path = "fonts/" + fontName; Typeface typeface = sCache.get(path); if (typeface == null) { typeface = Typeface.createFromAsset(assetManager, path); sCache.put(path, typeface); } textView.setTypeface(typeface); } }
更多信息请参考开源项目 fontbinding。
最佳实践
通过一个登录页面的 layout 布局,来感受一下双向绑定、字符串引用等的使用方法:<layout> <data> <variable name="model" type="iotalks.ForModel" /> <import type="iotalks.Validator" /> </data> <LinearLayout> <TextView android:text="@={model.name}"/> <Button android:enabled="@{Validator.isValid(model)}" android:onClick="@{()->presenter.save(model)}" android:text="@{@string/welcome(model.name)}" /> </LinearLayout> </layout>
如果还不过瘾,请参考开源项目:Android Architecture Blueprints [beta],绝对让你茅塞顿、豁然开朗、醍醐灌顶、如梦初醒。
更多资料
也许本文不值得一看,但是下面这些资料则不然。Data Binding Guide
Advanced Data Binding - Google I/O 2016
Android Architecture Blueprints [beta]
LoginDemo4DataBinding
Data Binding – Write Apps Faster (Android Dev Summit 2015)
Marshmallow Brings Data Bindings to Android
精通 Android Data Binding
Android Data Binding从抵触到爱不释手
安卓 Data Binding 使用方法总结(姐姐篇)
相关文章推荐
- Android RecycleView上拉加载BaseAdapter(二)
- Longest Palindromic Substring
- 移动app应用性能测试要点
- Effective-Java学习笔记 遇到多个构造器参数时要考虑用构建器
- 平台中字符类型,人员选择编辑类型介绍
- 【Shader】人物选中高亮状态
- Redis介绍 && Java客户端操作Redis
- 应用RecyclerView实现Gallery相册效果——注意引入recyclerview-v7的版本
- TS流格式(转)
- .Net StackFrame
- leetcode 之 Longest Increasing Subsequence
- shell写多行到文件中
- 更新svn地址
- 贪心算法 活动安排问题
- hdu 1254(dfs+bfs+优先队列)
- 相机跟随主角移动,并带有延迟效果
- [资源] Visual Studio 2015正式版离线iso及在线下载,附专业版和企业版可用key!
- 做项目时一个listview 里面嵌套ediTtext 滑动之后 保存数据
- JAVA书写规范、命名规范
- 面试题29 数组中超过一半的数字