Databinding的使用(自定义控件的全局注入、布局绑定)

一、Databinding使用的优势

1.1,实现xml的绑定,去除id的绑定操作;

1.2,将自定义控件很方便的全局注入xml,比如xml中的head。并实现对应逻辑的统一注入,比如点击返回销毁当前页面;

1.3,另外提供全局点击事件的单点操作,防止过快点击产生多次打开同一个页面的情况产生。

二、使用方法

2.1,在需要用的module中配置

 

android {
   ...
    dataBinding {
        enabled = true
    }
}
即可

2.2,使用 Data Binding 之后,xml的布局文件就不再单纯地展示 UI 元素,还需要定义 UI 元素用到的变量。所以,它的根节点不再是一个 ViewGroup,而是变成了 layout,并且新增了一个节点 data。

 

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <import type="android.databinding.ObservableMap"/>
        <variable
            name="user"
            type="com.xinguang.test.UserBean"/>
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <com.xinguang.test.BaseHeader
            android:layout_width="match_parent"
            android:layout_height="wrap_content"></com.xinguang.test.BaseHeader>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.name}"></TextView>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.yearsOld}"></TextView>
    </LinearLayout>
</layout>

2.3,那么如何实现绑定?

 

one:java类中:

 

activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
//one
userBean=new UserBean("zhanglei","18 years old");
activityMainBinding.setUser(userBean);

xml布局中:(data里面绑定实体类后)

 
android:text="@{user.name}"
    实体类中:
 
public class UserBean { private String name; private String yearsOld; public UserBean(String name, String yearsOld) { this.name = name; this.yearsOld = yearsOld; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getYearsOld() { return yearsOld; } public void setYearsOld(String yearsOld) { this.yearsOld = yearsOld; } }
 

 

 

two:java类中:

 

activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
//two
 
private UserField userField = new UserField();
userField.realName.set("Chiclaim");
userField.mobile.set("119");
 
binding.setFields(userField);


xml布局中:(data里面绑定实体类后)

 

 
 
android:text="@{fields.realName}"
    实体类中:
 
 
public class UserField { public final ObservableField<String> realName = new ObservableField<>(); public final ObservableField<String> mobile = new ObservableField<>(); }
 
 

 

three:java类中:

 

activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
//three
 
 
private ObservableArrayMap<String, Object> map = new ObservableArrayMap();
 
map.put("realName", "Chiclaim");
map.put("mobile", "119");
 
 
binding.setCollection(map);


xml布局中:

 
data里面绑定实体类后

 

<variable
    name="collection"
    type="ObservableMap&lt;String,Object>"/>

 

 

android:text="@{collection[`mobile`]}"
 
 
    实体类中:无

三、自定义控件的全局注入

3.1,这里首先提供一个防止快速点击,产生多响应事件的抽象类

/**
 * 功能:防止快速点击,产生多响应事件
 */
public abstract class OnClickEvent implements View.OnClickListener {

    public long lastTime;
    public long delayTime = 500;
    public final static long longDelayTime = 1000;

    public abstract void singleClick(View v);

    public OnClickEvent() {
    }

    public OnClickEvent(boolean isLongTime) {
        if (isLongTime) {
            delayTime = longDelayTime;
        }
    }

    @Override
    public void onClick(View v) {
        if (onDoubClick()) {
            return;
        }
        singleClick(v);
    }

    public boolean onDoubClick() {
        boolean flag = false;
        long time = System.currentTimeMillis() - lastTime;

        if (time < delayTime) {
            flag = true;
        }
        lastTime = System.currentTimeMillis();
        return flag;
    }
}

3.2,继承对应布局控件,将代码DataBindingUtil提供的方法注入即可,代码如下

 
public class BaseHeader extends FrameLayout{ private BaseHeaderBinding mBinding; public BaseHeader(@NonNull Context context) { super(context); initView(context); } public BaseHeader(@NonNull Context context, @Nullable AttributeSet attrs) { super(context, attrs); initView(context); } public BaseHeader(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(context); } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public BaseHeader(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); initView(context); } private void initView(Context context){ LayoutInflater inflater= (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); mBinding= DataBindingUtil.inflate(inflater,R.layout.base_header,null,false); mBinding.tvBack.setOnClickListener(new OnClickEvent() { @Override public void singleClick(View v) { ((Activity)v.getContext()).finish(); } }); addView(mBinding.getRoot()); } }
<strong>四、关于双向绑定几点注意事项</strong>
<strong>4.1,现在假设一种情况,当你更换成EditText时,如果你的用户名User.name已经绑定到EditText中,当用户输入文字的时候,你原来的user.name数据并没有同步改动。</strong>
这时候我们就要将正向绑定改成双向绑定
属于正向绑定的写法:android:text="@{user.name}"
属于双向绑定的写法:android:text="@={user.name}"
看出微小的差别了吗?对,就是"@{}"改成了"@={}",是不是很简单?

 

 

4.2,隐式引用属性

 

同样你也可以在别的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中。

4.3,目前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

 

 

4.4,自定义双向绑定

 

 

设想一下我们使用了下拉刷新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);
  }
}

参考网址:http://www.jb51.net/article/126566.htm,https://github.com/chiclaim/awesome-android-mvvm

猛戳这里demo下载

五、Databinding布局绑定框架

猛戳这里布局绑定demo下载

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
可以通过在自定义件的布局文件中使用 `<layout>` 标签来启用 DataBinding,然后在代码中使用 DataBindingUtil 类来数据。 例如,假设我们有一个自定义件 MyCustomView,它的布局文件为 custom_view.xml,我们想要一个名为 `text` 的字符串属性。我们可以这样做: 1. 在 custom_view.xml 中使用 `<layout>` 标签包裹布局文件的根布局: ```xml <layout xmlns:android="http://schemas.android.com/apk/res/android"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <!-- 自定义件的布局 --> </LinearLayout> </layout> ``` 2. 在 MyCustomView 的构造函数中使用 DataBindingUtil.inflate 方法来获取对象,并将它与自定义件的根布局: ```java public MyCustomView(Context context, AttributeSet attrs) { super(context, attrs); // 获取对象 CustomViewBinding binding = DataBindingUtil.inflate(LayoutInflater.from(context), R.layout.custom_view, this, true); // 数据 binding.setText("Hello, world!"); } ``` 3. 在 MyCustomView 中添加一个 `text` 属性,并在 custom_view.xml 中使用 `@{}` 语法来该属性: ```java public class MyCustomView extends LinearLayout { private String text; public MyCustomView(Context context, AttributeSet attrs) { super(context, attrs); CustomViewBinding binding = DataBindingUtil.inflate(LayoutInflater.from(context), R.layout.custom_view, this, true); binding.setCustomView(this); } public String getText() { return text; } public void setText(String text) { this.text = text; } } ``` ```xml <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="customView" type="com.example.MyCustomView" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{customView.text}" /> </LinearLayout> </layout> ``` 这样,当 MyCustomView 的 `text` 属性发生变化时,custom_view.xml 中的 TextView 的文本也会自动更新。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流星雨在线

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值