您的位置:首页 > 其它

安卓 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 使用方法总结(姐姐篇)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: