源码网商城,靠谱的源码在线交易网站 我的订单 购物车 帮助

源码网商城

Android DataBinding的官方双向绑定示例

  • 时间:2021-02-01 19:56 编辑: 来源: 阅读:
  • 扫一扫,手机访问
摘要:Android DataBinding的官方双向绑定示例
在Android Studio 2.1 Preview 3之后,官方开始支持双向绑定了。 可惜目前Google并没有在Data Binding指南里面加入这个教程,并且在整个互联网之中只有[url=https://halfthought.wordpress.com/2016/03/23/2-way-data-binding-on-android/comment-page-1/]这篇文章[/url]介绍了如何使用反向绑定。 在阅读一下文章之前,我假设你已经知道如何正向绑定。 [b]回顾一下Data Binding[/b] 在正向绑定中,我们在Layout里面的绑定表达式是这样的:
<layout ...>
 <data>
  <variable type="com.example.myapp.User" name="user"/>
 </data>
 <RelativeLayout ...>
  <TextView android:text="@{user.name}" .../>
 </RelativeLayout>
</layout>
当user.name的数据改动时,我们的TextView都会同步改变文字。 [b]双向绑定[/b] 现在假设一种情况,当你更换成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>
看出微小的差别了吗?对,就是"@{}"改成了"@={}",是不是很简单? [b]隐式引用属性[/b] 同样你也可以在别的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中。 [b]如何开启双向绑定[/b] 开启双向绑定,需要在项目的build.gradle中设置:
classpath 'com.android.tools.build:gradle:2.1.0-alpha3'
同样,你需要在你Module的build.gradle中设置:
android {
  ...
  dataBinding.enabled = true
}
[b]貌似还有点问题[/b] 我们刚才的例子里面只显示了系统自带的应用,那么如果是自定义控件,或者是我们更细颗粒度的Observable呢?等下就揭晓如何自定义自己的双向绑定,我们来看看目前Android支持的控件: [list=1] [*]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[/*] [/list] [b]自定义双向绑定[/b] 设想一下我们使用了下拉刷新[code]SwipeRefreshLayout[/code]控件,这个时候我们希望在加载数据的时候能控制refreshing的状态,所以我们加入了ObservableBoolean的变量[code]swipeRefreshViewRefreshing[/code]来正向绑定数据,并且能够在用户手动下拉刷新的时候同步更新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();
  }
}
接下来我们需要告诉框架,我们需要将[code]SwipeRefreshLayout[/code]的isRefreshing的值反向绑定到[code]swipeRefreshViewRefreshing[/code]:
@InverseBindingMethods({
    @InverseBindingMethod(
        type = android.support.v4.widget.SwipeRefreshLayout.class,
        attribute = "refreshing",
        event = "refreshingAttrChanged",
        method = "isRefreshing")})
这是一种简单的定义,其中event和method都不是必须的,因为系统会自动生成,写出来是为了更好地了解如何绑定的,可以参考官方文档[url=http://githang.com/androiddoc/docs/reference/android/databinding/InverseBindingMethod.html]InverseBindingMethod[/url]。 当然你也可以使用另外一种写法,并且如果你的值并不是直接对应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);
  }
}
这样就没问题啦。以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程素材网。
  • 全部评论(0)
联系客服
客服电话:
400-000-3129
微信版

扫一扫进微信版
返回顶部