美文网首页
DataBinding学习笔记(一)

DataBinding学习笔记(一)

作者: Ursus_M | 来源:发表于2017-10-09 23:46 被阅读0次

    引入DataBinding

    要在当前module的build.gradle文件中添加如下代码

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

    快速使用

    第一步 :创建对象

    一个普通的java对象即可

    public class User {
        public String name;
        public String phone;
    }
    

    第二步 :修改布局

    1. 规范布局:在布局文件最外层添加一个 layout 的根标签
    2. 引入数据:在 layout 标签下,添加 data 标签
    3. 声明对象:在 data 标签中添加 variable 的标签,其中 name 表示对象名,type 表示类名(包含包名)
    4. 关联属性:通过表达式 “@{}” 获取对象的属性,并将他们绑定到控件中
    <?xml version="1.0" encoding="utf-8"?>
    <layout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools">
        
        <data>
            <variable name="user" type="cn.com.ursus.User"/>
        </data>
    
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{user.name}"/>
        </RelativeLayout>
    </layout>
    

    注意事项

    1. User 中的有公有属性 name 时,@{user.name} 相当于 user.name
      User 中的无公有属性 name 时,@{user.name} 相当于 user.getName()

    2. android.text 绑定属性的时候,注意转成字符串,如果是整型,会被当成资源id处理,可以参考下面的代码(字符串用双引号 " " 原先外面那层双引号转成单引号' '

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text='@{student.age + ""}'/>
    

    第三步 :绑定对象

    1. ActivityonCreate 方法中,用 DataBindingUtil.setContentView 来替换原来的 setContentView ,得到一个名为 ActivityMainBinding 对象。( ActivityMainBinding 对象是根据布
      局文件自动生成的,名称来自于布局文件的名称配合上驼峰规则。)
    2. 通过刚才生成的 ActivityMainBinding 将和布局绑定的对象设置进去
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        User user = new User();
        user.name = "luffy";
        user.phone = "130****5678";
        binding.setUser(user);
    }
    

    这样一个最基本的数据绑定就完成了。

    Observable

    绑定完之后,肯定希望的是 User 对象中的属性值改变之后,绑定的控件也跟着自动刷新,然而并没有,于是乎,需要对 User 对象进行如下改造。

    public class User extends BaseObservable{
    
        private String name;
        private String phone;
    
        @Bindable
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
            notifyPropertyChanged(BR.name);
        }
        
        @Bindable
        public String getPhone() {
            return phone;
        }
    
        public void setPhone(String phone) {
            this.phone = phone;
            notifyPropertyChanged(BR.phone);
        }
    }
    

    注解 @Bindable 修饰 getName 方法可在 BR 类中自动生成一个对应属性 name 的整型常量 BR.name。 使用 notifyPropertyChanged 方法即可刷新绑定改属性的控件。至于 BR 是什么,可以类比为 Android 中的 R

    如果一个类中只有个别属性别绑定到ui,需要即使刷新,而整个类又不想继承
    BaseObservable ,可以使用 ObservableField , 具体可以参考下面的代码

    public class Student {
        public final ObservableField<String> name = new ObservableField<>();
        public final ObservableField<String> grade = new ObservableField<>();
        public final ObservableInt age = new ObservableInt();
    }
    
    ...
    
    final Student student = new Student();
    student.name.set("Ace");
    student.grade.set("grade2");
    student.age.set(1);
    binding.setStudent(student);
    

    表达式和事件

    前面提过为了避免 android:text 将整型识别为资源文件,需要将整型转成字符串。

    android:text='@{student.age + ""}'
    

    由此可见在 @{} 中进行一些简单的表达式操作。

    三目运算符 ?:

    android:text="@{ user.phone != null ? user.phone : @string/no_phone}"
    

    Null Coalescing Operator ??

    这个不是 java 代码的语法,Databinding 自定义的,类似于三目运算符特殊情况的一种简易写法

    android:text="@{ user.phone ?? @string/no_phone}"
    
    

    这和上面那种写法是等价的

    使用静态属性和静态方法

    上面代三目运算符的例子,如果我们要控制某个控件的显示与否可以这么写

    android:visibility="@{ user.phone != null ? View.GONE : View.VISIBLE}"
    

    这里不可以使用 gonevisible , 必须使用 View.GONEView.VISIBLE
    可是这个 View 是哪里来的? 我们可以在 data 标签中 import 进来

    <data>
        <import type="android.view.View"/>
        <import type="android.text.TextUtils"/>
    </data>
    

    import 进来之后,我们就可以也只能使用其中的静态属性静态方法

    android:visibility="@{TextUtils.isEmpty(user.phone) ? View.GONE : View.VISIBLE}
    

    注意:
    如果两个 import 进来的两个类,类名相同,我们可以给他们设置别名

    <import alias="MainActivityPresenter"
            type="cn.com.ursus.PermissionUtils"/>
    <import alias="ActivityPresenter"
            type="cn.com.ursus.presenter.PermissionUtils"/>
    

    资源文件

    上面的几个例子中在 @{} 中用到了 @string 资源文件,那么可以使用带占位符的 @string 吗?当然可以

    <string name="welcome_name">Welcome,%s</string>
    ...
    android:text="@{@string/welcome_name(user.name)}"
    

    当然除了 @string@dimen@color 等资源文件也肯定是支持的

    事件

    我们可以在 @{} 中可以用表达式来响应事件,比如最常用的 onClick,我们可以之间在之前的 User 类中编写相应的方法来响应,不过此处我重新创建一个类专门处理响应事件。

    class Presenter{
        public void clickUserName(View v){...}
        public void userNameChanged(CharSequence s, int start, int before, int count) {...}
    }
    
    ...
    android:onClick="@{presenter.clickUserName}"
    android:onTextChanged="@{presenter.userNameChanged}"
    

    在 Databinding 中只需要响应 onTextChanged ,无需理会 beforeTextChanged afterTextChanged ,然而如果在代码中实现 onTextChanged 我们一般都会采用如下的方式,就会显得有些臃肿。

    tvName.addTextChangedListener(new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    
        }
    
        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
    
        }
    
        @Override
        public void afterTextChanged(Editable s) {
    
        }
    });
    

    我们自定义的响应方法必须和常规监听方法保持一致的参数吗?答案是否定的,我们可以自己定义响应方法的参数,不过表达式和先前略有不同,类似lambda

    public void clickUserName(View v,String username){
        ...
    }
    
    ...
    android:onClick="@{(v)->presenter.clickUserName(v,user.name)}
    

    还有一些表达式就不一一列举了,下面是从官方Data Binding Guide上复制下来的目前 @{} 支持的表达式

    • Mathematical + - / * %
    • String concatenation +
    • Logical && ||
    • Binary & | ^
    • Unary + - ! ~
    • Shift >> >>> <<
    • Comparison == > < >= <=
    • instanceof
    • Grouping ()
    • Literals - character, String, numeric,null
    • Cast
    • Method calls
    • Field access
    • Array access []
    • Ternary operator ?:
    • Null Coalescing Operator ??

    自定义属性

    自动属性

    我们现在自定义了一个控件,其中有一个如下的 setPhoneNumber 方法

    public class MyTextView extends TextView {
        ...
        public void setPhoneNumber(String phone) {
          if (!isPhoneNumber(phone)) {
             throw new IllegalArgumentException("手机号格式不正确");
          }
          String show = phone.substring(0, phone.length() - (phone.substring(3)).length())
                  + "****"
                  + phone.substring(7);
          setText(show);
        }
        ...
    }
    

    神奇的一幕发生了 ,我们可以直接在布局文件中使用 phoneNumber 的布局属性,虽然 MyTextView 中并没有该属性

    <cn.com.ursus.view.MyTextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:phoneNumber="@{user.phone}"/>
    

    自定义属性

    现在有个需求,项目用的 Picasso 图片框架,我们需要在ImageView中自定义一个布局属性,使得我们可以给该属性设置一个网络url时,自动使用 Picasso 图片框架来加载网络图片,该如何做?我们只需写一个静态方法,给他打上一个 @BindingAdapter

    @BindingAdapter({"image_url"})
    public static void setImageUrl(ImageView view, String url){
       Picasso.with(MainApplication.getContext())
                .load(url)
                .placeholder(R.mipmap.ic_launcher)
                .into(view);
    }
    

    然后就可以在布局中使用该属性了

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:image_url="@{user.icon}"/>
    

    那么这个 setImageUrl 方法该放在哪个类里呢?需要将那个类导入布局么?
    其实 setImageUrl 方法可以放在任意类里面,而且不需要导入布局中,不过同一个控件的自定义属性一般放在一起管理。

    相关文章

      网友评论

          本文标题:DataBinding学习笔记(一)

          本文链接:https://www.haomeiwen.com/subject/hltbzttx.html