DataBinding的学习使用

先看两个问题:

问题1:android开发中,需要使用到findviewById来进行初始化view,如果页面,可能会看到几十行的findviewById方法,而如果需要设置点击事件,又要几十行的代码。

解决:ButterKnife,自动绑定了view,点击事件等,代替了繁杂的手写步骤。

问题2:用ButterKnife,就是如果一个页面view过多的话,也需要一长串的 @bindView 代码,导致一个页面轻轻松松300行+的代码量,看着也有点不舒服。而这些也都是重复性的工作,那么有什么办法解决吗?

解决:DataBinding!

MVVM

MVVM和MVP的区别,是把presenter层换成了viewmodel层,还有就是view层和viewmodel层是相互绑定的关系,这意味着当你更新viewmodel层的数据的时候,view层会相应的变动ui。视图和数据的双向绑定就是 通过Android Data Binding技术。

下面学习开始学习dataBinding。

一、配置databinding

首先在Module app下build.gradle中配置databinding

android {
    dataBinding{
        enabled = true
    }
}

二、使用databinding

1、一个例子--简单使用

先写个Model实体类Man:

/**
 * 侠客,ViewModel
 */
public class Man extends BaseObservable{
    private String name;

    private String level;

    public Man(String name, String level) {
        this.name = name;
        this.level = level;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getLevel() {
        return level;
    }

    public void setLevel(String level) {
        this.level = level;
    }
}

然后布局文件:

<?xml version="1.0" encoding="utf-8"?>

<!--根标签-->
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <!--变量的定义-->
    <data>
        <variable
            name="man"   <!--变量名-->
            type="com.hfy.demo01.module.mvvm.bean.Man" />  <!--变量类型-->
    </data>

   <!--传统的视图-->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{man.name}" />  <!--变量的引用-->

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{man.level}" />

    </LinearLayout>

</layout>

说明:

a.布局根标签为:<layout>,由以前的<LinearLayout>改为<layout>,注意 l 小写。

b.定义变量的标签:<data>、<variable>,变量的引用:@{man.level}

最后在MvvmActivity中对 数据和视图 进行绑定。(要先Make Project)注意下面代码的注释1、2、3。

    public class MvvmActivity extends AppCompatActivity {

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

//        setContentView(R.layout.activity_mvvm);

            //1、这里使用DataBindingUtil.setContentView()
            //2、ActivityMvvmBinding是在写好<layout>布局后,make project,自动生成的Binding辅助类,包含了布局中所有的绑定关系
            ActivityMvvmBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_mvvm);

            //3、给 布局中定义的变量man 进行赋值。
            Man man = new Man("段誉","爱情高手");
            binding.setMan(man);
        }

运行结果如下图。可见:我们没有findViewById、setText,依然可以把数据展示出来。

2、事件处理,两种方法如下图

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <!--对象变量的定义-->
        <variable
            name="man"
            type="com.hfy.demo01.module.mvvm.bean.Man" />

        <variable
            name="onClickListener"
            type="android.view.View.OnClickListener" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{man.name}" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{man.level}" /> <!--支持表达式-->

        <!--事件处理的两种方法-->

        <!--方法一、先设置控件id,然后java代码中使用Binding设置-->
        <Button
            android:id="@+id/button1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="button1" />
        
        <!--方法二、引用变量-->
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="@{onClickListener}"
            android:text="button2" />

    </LinearLayout>

</layout>
        //事件处理方法一,binding.
        binding.btnTest.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                man.setName("段正淳");
            }
        });

        //事件处理方法二:
        binding.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MvvmActivity.this, "button2", Toast.LENGTH_SHORT).show();
            }
        });

3、布局属性

import用法,

    <data>
        
        <!--导入-->
        <import type="com.hfy.demo01.module.mvvm.bean.Man" />

        <!--对象变量的定义-->
        <variable
            name="man"
            type="Man" />

    </data>

变量定义,

        <!--基本数据类型 的变量, java.lang.*会自动导入-->
        <variable
            name="home"
            type="String" />

        <variable
            name="age"
            type="int" />

        <variable
            name="isMale"
            type="boolean" />

        <!--集合变量的定义( <> 要用转义符 )-->
        <variable
            name="list"
            type="ArrayList&lt;String&gt;" />

<!-------->

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{home}" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{list.get(0)}" />
        binding.setHome("安徽");

        ArrayList<String> strings = new ArrayList<>();
        strings.add("一");
        strings.add("二");
        binding.setList(strings);

静态方法调用,

        <!--静态方法调用-->
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{Utils.getName(man)}" /> <!--要先导入Utils-->
public class Utils {
    public static String getName(Man man) {
        return man.getName();
    }
}
Man man = new Man("段正淳","爱情高高高手");
                binding.setMan(man);

支持表达式,(三目预算、+、!、&&、>= 等等)

        <variable
            name="isMale"
            type="boolean" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text='@{isMale? man.name + ":男的": man.name + ":女的"}' /> <!--支持表达式-->

Convert,把数据格式 转为需要的格式

        <variable
            name="time"
            type="java.util.Date" />
public class Utils {
    /**
     * convertDate()这个方法在哪个类不重要,重要的是 @BindingConversion
     * @param date
     * @return
     */
    @BindingConversion
    public static String convertDate(Date date) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        String format = simpleDateFormat.format(date);
        return format;
    }
}
        ActivityMvvmBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_mvvm);
        binding.setTime(new Date());

三、动态更新 / 双向绑定

此前例子中,Model实体类变化,UI是不会动态更新的。 接下来实现 动态更新:继承BaseObservable

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <import type="com.hfy.demo01.module.mvvm.bean.Man" />

        <variable
            name="man"
            type="Man" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{man.name}" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{man.level}" />

    </LinearLayout>

</layout>
/**
 * 侠客,  继承BaseObservable
 */
public class Man extends BaseObservable {
    private String name;

    private String level;

    public Man(String name, String level) {
        this.name = name;
        this.level = level;
    }

    /**
     * @return
     * @Bindable BR中生成一个对应的字段,BR编译时生成,类似R文件
     */
    @Bindable
    public String getName() {
        return name;
    }

    /**
     * notifyPropertyChanged(BR.name),通知系统BR.name已发送变化,并更新UI
     *
     * @param name
     */
    public void setName(String name) {
        this.name = name;
        notifyPropertyChanged(BR.name);
    }

    @Bindable
    public String getLevel() {
        return level;
    }

    public void setLevel(String level) {
        this.level = level;
        notifyPropertyChanged(BR.level);
    }
    
}
        ActivityMvvmBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_mvvm);

        Man man = new Man("段誉","爱情高手");
        binding.setMan(man);

//        //事件处理方法一,binding.
        binding.btnTest.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //Model实体类发生变化  -- UI会动态变化
                man.setName("段正淳");
            }
        });

接下来就是双向绑定:就是Model和View 通过VIewModel 进行双向动态更新。Model实体类变化,VIew会自动更新;View发生变化,Model实体类也能动态改变。

        <!--使用EditText通过 "@={}" 改变man.name的值-->
        <!--UI EditText变化,实体类man也动态变化-->
        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@={man.name}"/>

五、结合RecyclerView

avtivity布局:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <!--这里设置了id,就可以通过binding直接找到RecyclerView-->
        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

        </android.support.v7.widget.RecyclerView>

    </LinearLayout>
</layout>

item布局:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable
            name="man"
            type="com.hfy.demo01.module.mvvm.bean.Man" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{man.name}" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{man.level}" />

    </LinearLayout>
</layout>

activity:

public class RecyclerActivity extends AppCompatActivity {

    private ActivityRecyclerBinding binding;

    /**
     * launch MvpActivity
     */
    public static void launch(Activity activity) {
        Intent intent = new Intent(activity, RecyclerActivity.class);
        activity.startActivity(intent);
    }

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

//        setContentView(R.layout.activity_recycler);

        // 1、使用 DataBindingUtil.setContentView() 代替  setContentView(R.layout.activity_recycler);
        binding = DataBindingUtil.setContentView(this, R.layout.activity_recycler);

        //直接通过binding找到了recyclerView
        LinearLayoutManager layout = new LinearLayoutManager(this);
        layout.setOrientation(LinearLayoutManager.VERTICAL);
        binding.recycler.setLayoutManager(layout);
        binding.recycler.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
        ManAdapter manAdapter = new ManAdapter(getList());
        binding.recycler.setAdapter(manAdapter);
    }

    private List<Man> getList() {
        List<Man> list = new ArrayList<>();
        list.add(new Man("乔峰", "帮主"));
        list.add(new Man("虚竹", "小和尚"));
        return list;
    }

    private class ManAdapter extends RecyclerView.Adapter<ManViewHolder> {

        private final List<Man> mList;

        public ManAdapter(List<Man> list) {
            mList = list;
        }

        @NonNull
        @Override
        public ManViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {

            //2、这里是把item_layout对应的Binding 注入iewHolder(以前是View)
            ItemLayoutRecyclerBinding manViewBinding = DataBindingUtil.inflate(LayoutInflater.from(viewGroup.getContext()), R.layout.item_layout_recycler, viewGroup, false);
            return new ManViewHolder(manViewBinding);
        }

        @Override
        public void onBindViewHolder(@NonNull ManViewHolder manViewHolder, int i) {

            //3、绑定只用使用Binding,很简洁。
            manViewHolder.manViewBinding.setMan(mList.get(i));
        }

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

    private class ManViewHolder extends RecyclerView.ViewHolder {

        private final ItemLayoutRecyclerBinding manViewBinding;

        //4、这里没有findViewById()去找各个view。就holder了Binding。
        public ManViewHolder(ItemLayoutRecyclerBinding manViewBinding) {
            super(manViewBinding.getRoot());
            this.manViewBinding = manViewBinding;
        }
    }
}

结果:

RecyclerView功能强大,使用DataBinding来处理RecyclerView Item 再合适不过,做到了数据和itemView的完美分离,告别了反复、冗余的自定义Adapter,不需要关心太多无意义的事。

六、使用注意点

1、尽可能少的variable和import。  过多的variable除了会让你多做几次无谓的绑定外,数据也将变得难以管理。

bad

<data>

        <import type="com.ditclear.app.util.DateUtil"/>

        <import type="android.view.View"/>

        <import type="com.ditclear.app.util.StringUtil"/>

        <import type="com.ditclear.app.network.model.StudentState"/>

        <import type="java.math.BigDecimal"/>

        <import type="com.ditclear.app.presentation.student.StudentActivity"/>

        <variable
            name="isShow"
            type="Boolean"/>

        <variable
            name="time"
            type="java.util.Date"/>

        <variable
            name="date"
            type="java.util.Date"/>

        <variable
            name="signTime"
            type="String"/>

        <variable
            name="item"
            type="com.ditclear.app.network.model.student.StudentItem"/>

        <variable
            name="presenter"
            type="com.ditclear.app.presentation.student.StudentActivity.Presenter"/>
    </data>

better

<data>
        <variable
                name="vm"
                type="com.ditclear.app.view.student.viewmodel.StudentViewModel"/>
</data>

数据的处理和view的显示都用ViewModel来处理,比如:

android:text="@{vm.signTime}"
android:visibility="@{vm.isShowVisbility}"

好处是减轻了layout.xml文件的复杂程度,xml文件只用来单纯的展示数据, 而复杂的逻辑判断都放在了ViewModel中,并且为页面布局数据提供了唯一的入口,方便查看和管理数据源。

2、避免使用复杂的表达式,下面这种的逻辑判断最好放在ViewModel中,由ViewModel提供对应的变量。

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text='@{isMale? man.name + ":男的": man.name + ":女的"}' /> <!--支持表达式-->

而且,刚入门DataBinding的人都遇到过找不到binding文件的错,可能是表达式写错、variable的类路径不对或者没import相应的类(比如View)等等,都与复杂的表达式有关系,而DataBinding报错也不太智能,有时不能准确定位,所以建议不要在xml里进行复杂的数据绑定,这些都尽量放到ViewModel里进行,xml只绑定简单的基础数据类型。

七、Android Studio插件 Databinding Support

https://plugins.jetbrains.com/plugin/9271-databinding-support

参考:

《Android进阶之光》

DataBinding实用指南

推荐阅读  详细系列文章:

DataBinding使用指南(一):布局和binding表达式

发布了53 篇原创文章 · 获赞 9 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/hfy8971613/article/details/84961241