DataBinding学习笔记

1.介绍

    DataBinding使数据跟布局变成了一种可能,是MVVM思想的一部分,免去了我们一直在findViewById然后设置等,这是它的优点,既然是学习它,缺点我就不说了,毕竟每个东西的出现都会有两面,我们只要在合适的地方使用就会避免掉它的缺点,从而彰显它的优点。

      任何一个名字为aa_bc.xml布局只要是根节点标签是<layout>,只要这个布局被加载inflate,DataBinding工具都会帮我们生成一个相应的AaBcBinding,都是ViewDataBinding的子类,

可以通过DataBindingUtils.bind(view)或者DataBindingUtils.setContentView(actvity,View)或者其它方法得到。

      一旦我们调用了DataBindingUtils.setContentView(actvity,View)这个方法或者是DataBindingUtils.bind(view)方法,加载的布局文件,使用布局文件的类,就会通过生成Binding相关联,binding可以直接通过布局中每个控件的id找到这个控件,然后做相关操作。

     有时候我们自定义一个布局的时候,往往需要自定义自己的属性,这就需要导入:app标签,然后再style.xml<declare-stylable> </declare-stylable> 中声明自己的<attr/>,然后我们在自定义的布局的构造器中,用相应的方法得到自己设置的属性,然后设置给相应的控件,操作很繁琐吧,如果我们使用DataBinding就会非常简单,我们只需要导入:app标签然后直接声明属性给自己定义布局,比如app:tvName="String",然后我们在自定义布局中实现对应的setTvName(String str)方法就可以把str设置给我们想给的控件,是不是非常方便,从此不再需要写繁琐的<declaer-stylable>标签。


原理分析参考此文章,写的不错:http://www.jianshu.com/p/b1df61a4df77

2.构建

构建请参照github介绍https://github.com/XinRan5312/MasteringAndroidDataBinding


3.使用代码演示

如下是布局文件,如果使用Databinding最外层布局只能是layout另外还有data标签负责填写我们的数据定义和配置

<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <!--用到什么类就要事先用import导包,就跟在类中写代码一样,比如下面我们用到Student和View类,所以
        我们要首先导包,DataBinding用variable来定义变量 属性包裹变量名字和其类型,比如下面的stu和isQx变量
        ,另外这里定义的变量就成为使用类的Binding类的变量,我们可以在使用类里通过binding.setXX来给他们赋值,比如

        ActivityAsimpleBinding binding= DataBindingUtil.setContentView(this, R.layout.activity_asimple);
        Student stu=new Student(null,"Mack",18);
        binding.setStu(stu);
        binding.setIsQx(true);
        从而达到使用类和对应布局数据的绑定

        另外:
          1,@{这里其实就是书写java代码的地方}
          2,databinding语法中不但有?:三目运算,还有??两目预算@{stu.name??stu.firstName},如果
          stu.name非null就是stu.name,否则就是stu.firstName
        -->
        <import type="com.xinran.qxdatabinding.beans.Student"></import>
        <import type="android.view.View"></import>
        <variable name="stu" type="Student"></variable>
        <variable
            name="isQx"
            type="boolean"></variable>
    </data>

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginLeft="20dp"
    android:layout_marginRight="20dp">

        <TextView
            android:layout_height="wrap_content"
            android:layout_width="wrap_content"
            android:text="@{stu.name??stu.firstName}"/>
    <TextView
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:text="成年"
        android:visibility="@{stu.age>18 ? View.VISIBLE : View.GONE}"/>
    <TextView
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:text="上大学了"
        android:visibility="@{isQx ? View.VISIBLE : View.GONE}"/>


</LinearLayout>

</layout>

下面是使用布局的Activity的初始化代码,这是一个很简单使用例子

public class ASimpleActivity extends BaseActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityAsimpleBinding binding= DataBindingUtil.setContentView(this, R.layout.activity_asimple);
        Student stu=new Student(null,"Mack",18);
        binding.setStu(stu);
        binding.setIsQx(true);
    }
}

上面的例子是一个DataBinding自动生成Binding类,生成规则是布局名字根据驼峰,如果要自定义呢,也很简单只需要在data跟标签加class属性就好,代码如下:

<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data class=".CustomBinding">
      <variable
          name="name"
          type="String"></variable>
        <variable
            name="age"
            type="String"></variable>
        <!-- 我们自定义一个Binding的时候,就是不需要按照我们布局名字让DataBinding帮我们生成一个Binding时,我们
        只需在data根标签添加class属性就好,用.开头后面跟自定义的类名字,比如 <data class=".CustomBinding">
        另外:定义的变量类型一定要和真实需要类型相同,否则DataBinding回报notFind异常,比如TextView的text需要的是
             String类型的数据,如果我们非要把age变量定义成int类型,就会报错
        -->
    </data>

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginLeft="20dp"
    android:layout_marginRight="20dp">

        <TextView
            android:layout_height="wrap_content"
            android:layout_width="wrap_content"
            android:text="@{name}"/>
    <TextView
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:text="@{age}" />
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="toChange"
        android:text="更改"/>


</LinearLayout>

</layout>


使用布局的类的代码:

public class CustomBindingActvity extends BaseActivity {
    private CustomBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding= DataBindingUtil.setContentView(this, R.layout.activity_custom);
        binding.setName("Join");
        binding.setAge("20");
    }
    public void toChange(View view){
        binding.setName("Mack");
        binding.setAge("16");
    }
}

处理Include标签include的公共布局,而且Include的布局中也定义了DataBinding变量,并使用,核心知识点在根布局layout中导入:bind标签,如下布局代码:


<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:bind="http://schemas.android.com/apk/res-auto">
    <data>
        <!-- 在布局中include公共布局由于inclue布局中也有自己定义的DataBinding变量,该怎么关联上呢,还好DataBinding
        为我们提供了bind标签,我们只需导入xmlns:bind="http://schemas.android.com/apk/res-auto",然后我们就可以用
        bind:Include不居中的变量名字=给他们赋的值,比如
     <include
     layout="@layout/layout_title_bar"
     bind:stu="@{student}"
     bind:isQx="@{isOk}"/>

     就像我们引用android:标签赋值一样

   -->
        <import type="com.xinran.qxdatabinding.beans.Student"></import>
        <variable
            name="student"
            type="Student"></variable>
      <variable
          name="name"
          type="String"></variable>

        <variable
            name="isOk"
            type="boolean"></variable>
    </data>

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginLeft="20dp"
    android:layout_marginRight="20dp"
    android:orientation="vertical">

 <include
     layout="@layout/layout_title_bar"
     bind:stu="@{student}"
     bind:isQx="@{isOk}"/>
    <include
        layout="@layout/layout_asimple"
        bind:discript="@{name}" />
    <include
        android:id="@+id/input"
        layout="@layout/layout_edit"/>
</LinearLayout>

</layout>

使用Include布局类使用代码如下:

public class IncludeLayoutActvity extends BaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        final ActivityIncludeLayoutBinding binding= DataBindingUtil.setContentView(this, R.layout.activity_include_layout);
        binding.setName("王大拿");
        binding.setIsOk(true);
        binding.setStudent(new Student("jack", "Mack", 19));
        //其中input是布局中的id,evInputName是id是input的布局中的id为ev_input_name的控件id
        //本例中input是布局layout_edit.xml的id,evInputName是layout_edit.xml布局中一个EditText的id
        binding.input.evInputName.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void afterTextChanged(Editable editable) {
                      binding.setName(editable.toString());
            }
        });
    }
}


对资源文件的使用,布局文件如下:

<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <!--
        DataBinding语法对资源文件的使用其实就是把,原来的代码放入到@{}中就可以了,比如:
        @{@plurals/banana(bananaCount)}//对数组的选择使用
        @{@string/nameFormat(firstName,lastName)}//对string的使用
        @{isBig?@dimen/text_big:@dimen/text_small}//对dimen数值的引用

        -->
       <variable
           name="firstName"
           type="String"></variable>
        <variable
            name="lastName"
            type="String"></variable>
        <variable
            name="orangeCount"
            type="int"></variable>
        <variable
            name="bananaCount"
            type="int"></variable>
        <variable
            name="isBig"
            type="boolean"></variable>
    </data>

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginLeft="20dp"
    android:layout_marginRight="20dp">

        <TextView
            android:layout_height="wrap_content"
            android:layout_width="wrap_content"
            android:text="@{@string/nameFormat(firstName,lastName)}"/>
    <TextView
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:text="@{@plurals/banana(bananaCount)}" />
    <TextView
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:text="@{@plurals/orange(orangeCount,orangeCount)}"
        android:textSize="@{isBig?@dimen/text_big:@dimen/text_small}"
      />


</LinearLayout>

</layout>

DataBinding的双向绑定的使用,代码如下:

/**

 * DataBinding本来只支持单向绑定,如果需要双向绑定,就要用系统各种相应的ObsevableXXX类,比如ObservableArrayList
 * ObservableInt,ObservableField<T> *
 * 或者自己自定义
 * 一个类继承BaseObservable,在每个属性的set方法里调用   notifyPropertyChanged(BR.属性名字);
 * 比如ObservableStudent
 *
 * 或者是自己的实体类中的属性都用ObservableXX来定义,比如ObservableStudentOther
 */
public class ObservableActvity  extends BaseActivity{
    private ObservableArrayList<String> list=new ObservableArrayList<>();
    private ObservableArrayMap<String,String> map=new ObservableArrayMap<>();
    private ObservableStudent observableStudent=new ObservableStudent();
    private ObservableStudentOther observableStudentOther=new ObservableStudentOther();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityObservableBinding binding= DataBindingUtil.setContentView(this, R.layout.activity_observable);
        initData();
        binding.setList(list);
        binding.setMap(map);
        binding.setStuOne(observableStudent);
        binding.setStuoTo(observableStudentOther);

    }
    public void changeName(View view){
        changeData();
    }
    public void initData(){
        list.add("mack");
        list.add("jack");
        map.put("1", "fhkg");
        map.put("2", "mjs");
        observableStudent.setName("Join");
        observableStudent.setAge("18");
        observableStudentOther.name.set("Frank");
        observableStudentOther.age.set("25");
    }

    public void changeData(){
        list.add("mack2");
        list.add("jack2");
        map.put("1", "fhkg2");
        map.put("2", "mjs2");
        observableStudent.setName("Join2");
        observableStudent.setAge("18");
        observableStudentOther.name.set("Frank2");
        observableStudentOther.age.set("25");
    }
}

ObservableStudent的定义,继承BaseObservable在setXX方法里调用notifyPropertyChanged(BR.XX):

  

public class ObservableStudent extends BaseObservable {

    private String name;
    private String age;

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
        notifyPropertyChanged(BR.name);
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
        notifyPropertyChanged(BR.age);
    }
}

ObservableStudentOther直接利用ObservableXX来定义属性,就不需要继承BaseObservable了代码如下:

public class ObservableStudentOther {
    public final ObservableField<String> name=new ObservableField<>();
    public final ObservableField<String> age=new ObservableField<>();
}
但是使用赋值的时候需要:ObservableStudentOther obj=new ObservableStudentOther();  obj.name.set(Value)

Observable双向绑定的布局代码:

<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <!--
        在定义List或者Map泛型的时候&lt;表示文本泛型的开始最后还要加一个>表示泛型定义的结束,千万别忘了最后这个>要不然会报错的
        -->
        <import type="android.databinding.ObservableArrayList"></import>
        <import type="android.databinding.ObservableArrayMap"></import>
        <variable name="stuOne" type="com.xinran.qxdatabinding.beans.ObservableStudent"></variable>
        <variable
            name="stuoTo"
            type="com.xinran.qxdatabinding.beans.ObservableStudentOther"></variable>
        <variable
            name="list"
            type="ObservableArrayList&lt;String>"/>
        <variable
            name="map"
            type="ObservableArrayMap&lt;String, String>"/>

    </data>

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginLeft="20dp"
    android:layout_marginRight="20dp"
    android:orientation="vertical">

        <TextView
            android:layout_height="wrap_content"
            android:layout_width="wrap_content"
            android:text="@{@string/nameFormatWithAge(stuOne.name,stuOne.age,18)}"/>
    <TextView
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:text="@{@string/nameFormatWithAge(stuoTo.name,stuoTo.age,28)}"/>
    <TextView
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:text="@{list[1]}"/>
    <TextView
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:text="@{map['1']}"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="changeName"
        android:text="Change"/>


</LinearLayout>

</layout>

不要<data>节点直接给每个view定义一个id,DataBinding会帮我们根据id的名字生成一一对应的绑定的属性名字,我们根据这个属性名字也能找到对应view,代码如下:


<layout xmlns:android="http://schemas.android.com/apk/res/android">

  
        <!--
          <data>
        如果每个view布局的时候设置了id,我们通过DataBindingUtil.setContentView(Actvity,布局Id)的时候
        已经把Actvity跟它使用使用的布局关联起来,我们直接利用binding.id名字就可以直接找到对应的布局控件
        注:名字生成规则:根据布局中的id命名的下划线为节点,自动生成驼峰式的名字,如本例tv_name对应tvName:

       ActivityViewWithIdBinding binding= DataBindingUtil.setContentView(this, R.layout.activity_view_with_id);

        binding.tvName.setText("Mack");
        binding.tvAge.setText("22");
        binding.tvName.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                binding.tvAge.setText("点击了TvName");
            }
        });
            </data>
        -->


    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp">

        <TextView
            android:id="@+id/tv_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

        <TextView
            android:id="@+id/tv_age"
            android:layout_width="wrap_content"
            android:layout_below="@+id/tv_name"
            android:layout_height="wrap_content" />


    </RelativeLayout>

</layout>

public class ViewWithIdActvity extends BaseActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //DataBindingUtil.setContentView(this, R.layout.activity_view_with_id)这句代码把布局跟Actvity和DataBinding
        //给关联上了,如想知道具体细节可以看DataBindingUtil.setContentView()源码细节

        final ActivityViewWithIdBinding binding= DataBindingUtil.setContentView(this, R.layout.activity_view_with_id);
        //DataBinding会根据布局每个控件中的id名字自动生成对应的属性名字,并且一一对应,所以我们可以直接引用

        binding.tvName.setText("Mack");
        binding.tvAge.setText("22");
        binding.tvName.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                binding.tvAge.setText("点击了TvName");
            }
        });
    }
}

Databinding延迟加载并且只能inflate一次的ViewStub的使用:

<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin" >

        <Button
            android:text="Inflate the ViewStub"
            android:onClick="inflateViewStub"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

        <ViewStub
            android:id="@+id/view_stub"
            android:layout="@layout/view_stub"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

    </LinearLayout>
</layout>

public class ViewStubActivity extends BaseActivity {
    private ActivityViewStubBinding mBinding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_view_stub);
        mBinding.viewStub.setOnInflateListener(new ViewStub.OnInflateListener() {
            @Override
            public void onInflate(ViewStub stub, View inflated) {
                ViewStubBinding binding = DataBindingUtil.bind(inflated);
                User user = new User("liang", "fei");
                binding.setUser(user);
            }
        });

    }


    /**
     * Don't panic for red error reporting. Just ignore it and run the app. Surprise never ends.
     */
    public void inflateViewStub(View view) {
        if (!mBinding.viewStub.isInflated()) {
            mBinding.viewStub.getViewStub().inflate();
        }
    }
}

Dynamic动态绑定view,代码使用RecycerView来掩饰,因为它涉及到Adapter中绑定数据,代码如下:

public class StudentAdapter extends RecyclerView.Adapter<StudentHolder> {
    private List<Student> list;
    public StudentAdapter(List<Student> list){
        this.list=list;
    }

    @Override
    public StudentHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
        View view= LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_student_adapter,viewGroup,false);

        return new StudentHolder(view);
    }

    @Override
    public void onBindViewHolder(StudentHolder studentHolder, int i) {
        //因为在StudentHolder中已经把itemVie布局跟binding绑定关联,所以这里设置的数据可以到布局中
            studentHolder.bindData(list.get(i));
    }

    @Override
    public int getItemCount() {
        return list.size();
    }
}

public class StudentHolder extends RecyclerView.ViewHolder{
    private ItemStudentAdapterBinding binding;
    public StudentHolder(View itemView) {
        super(itemView);
        //在ViewHolder中使itemView跟相应的binding进行关联
        binding= DataBindingUtil.bind(itemView);
    }
    public void bindData(Student stu){
        //关联后就可以为他设值
        binding.setStu(stu);

    }
}

public class DynamicActvity extends BaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityDynamicBinding binding= DataBindingUtil.setContentView(this, R.layout.activity_dynamic);
        //根据布局控件的id直接通过binding的点语法,得到相应的控件(因为DataBinding根据布局id生成了final属性变量)
        binding.recycerView.setLayoutManager(new LinearLayoutManager(this));
//        binding.recycerView.setOnScrollChangeListener(new View.OnScrollChangeListener() {
//            @Override
//            public void onScrollChange(View view, int i, int i1, int i2, int i3) {
//                Log.e("eeee:", "onScroll");
//            }
//        });
        binding.recycerView.setAdapter(new StudentAdapter(mockData()));

    }
    private List<Student> mockData(){
        List<Student> list=new ArrayList<>();
        Student stu=null;
        for(int i=0;i<10;i++){
            stu=new Student("Yang"+i,"mack"+i,i);
            list.add(stu);
        }

        return list;

    }
}


使用DataBinding自定义布局,自定义属性时不用在style.xml声明<declear-stylable>标签声明自己想要的各种<attr/>,代码如下:


public class TabLayout extends LinearLayout {

    private float textSize;

    private TextView tv1;
    private TextView tv2;
    private ImageView imageView;

    public TabLayout(Context context) {
        this(context, null);
    }

    public TabLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public TabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TabLayout);
        textSize = typedArray.getDimension(R.styleable.TabLayout_tvSize, 12f);
        if (typedArray != null) {
            typedArray.recycle();
        }
        init();
    }

    private void init() {
        inflate(this.getContext(), R.layout.layout_custom, this);
        tv1 = (TextView) findViewById(R.id.first);
        tv2 = (TextView) findViewById(R.id.second);
        tv1.setTextSize(textSize);

    }

    //set方法名字的后缀名字 布局中app:后面的属性名字一致,DataBinding就会自动根据
    //app:后面的属性名字找自定义布局中对应的方法调用的,并把值传过来,就不用声明declare-stylable属性了
    //效果是一样的
    public void setTvFirst(String firstTabName) {
        tv1.setText(firstTabName);
    }

   public void setTvColor(int color){
       tv1.setTextColor(color);
   }
    public void setTvSecond(String firstTabName) {
        tv2.setText(firstTabName);
    }
    public void setImgIcon(int resid){
        imageView.setImageResource(resid);
    }

}

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
<!--  //在TabLayout类中声明app:后属性名字相同的后缀set方法就好
    比如app:tvFirst就对应setTvFirst(String str)这个set方法-->
<com.xinran.qxdatabinding.views.TabLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginLeft="20dp"
    android:layout_marginRight="20dp"
    app:tvFirst="@{@string/firstName}"
    app:tvSecond="@{@string/lastName}"
    app:tvSize="@dimen/text_big"
    app:tvColor="@{@color/red_1}">

</com.xinran.qxdatabinding.views.TabLayout>

</layout>

使用<declear-stylable>标签自定义的tvSize属性:

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
    </style>
<declare-styleable name="TabLayout">
    <attr name="tvSize" format="dimension"></attr>
</declare-styleable>
</resources>

发布了121 篇原创文章 · 获赞 23 · 访问量 20万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览