DataBinding简易入门

本文介绍了DataBinding,Google在2018年推出的一个数据绑定框架,它采用MVVM模式简化代码结构,实现View和Model的解耦。文章详细讲解了如何启用DataBinding、布局绑定的过程,以及在Activity、Fragment和RecyclerView中的使用方法,包括实体类的定义、数据绑定语法和常见问题解决。
摘要由CSDN通过智能技术生成

简介

DataBinding是Google在18年推出的数据绑定框架,采用了MVVM模式来降低各模块之间代码的耦合度,使得整体代码逻辑设计更加清晰。众所周知,MVVM类似于MVC,主要目的是为分离View(视图)和Model(模型),同时进行双向数据绑定,当业务数据发生改变时,View能及时刷新;当View数据更新时,同时能同步到Model。同时,DataBinding能省去findViewById()操作,大量减少Activity内代码,根据业务场景,可以让数据能单向或双向绑定到对应界面layout中,能较好的避免空指针,以及防范内存泄漏。

使用

启用方式,在Module的build.gradle中构建:

android {    
    ...   
    buildFeatures {
            dataBinding true    
            }
}

1、布局绑定

在要编写的布局文件中选定根布局,按Alt+回车快捷键唤出提示:

image.png
选择第一个即可生成符合DataBinding规则的布局文件:

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

    <data>

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        ...

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

可看见原根布局被包裹住了,且布局元素上方出现< data>标签,在这< data>标签是用于声明要用到的变量以及变量类型,在MVVM模式中扮演的是一个View和Model通讯的中间人角色。

生成dataBinding规则的布局后,数据绑定库会自定生成将布局中的视图和数据绑定所需的类,类名为layout.xml文件名+DataBinding的驼峰规则命名组合(这点与ViewBinding类似)。

比如:我这个是MainActivity.class,布局文件为activity_main.xml,则相应绑定类类名为 ActivityMainBinding 。

2、绑定流程

步骤一:先编写一个简单的实体Bean类:

java版:

public class UserInfo {

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    private String name;

    private int age;

}

kt版:

data class UserInfo(val name: String,val age: Int) {}

步骤二:在布局文件中的里引用要使用到的变量名、类的全路径,如下:

<data>

    <variable
        name="UserInfoExample"
        type="com.example.dbjavatest.bean.UserInfo" />

</data>

在这里,中的name可以随意命名,但建议跟实体bean类一致,以免后续遗忘;type则为对应实体bean类的全路径

PS 这里有两个极少数情况:后续你可能发现如果要多出引用这个bean实体类,要写不同的命名区分,就会变成这样:

<data>
    <variable
        name="UserInfoExample"
        type="com.example.dbjavatest.bean.UserInfo" />
    <variable
        name="UserInfoExample2"
        type="com.example.dbjavatest.bean.UserInfo" />
    ...
</data>

这时候可以通过 import 标签声明所有用到的这个实体bean类,以上代码即变为:

<data>
    <import type="com.example.dbjavatest.bean.UserInfo"/>
    <variable
        name="UserInfoExample"
        type="UserInfo" />
    <variable
        name="UserInfoExample2"
        type="UserInfo" />
    ...
</data>

如果import标签下两个类名相同,哪怕全路径不同,这里就要使用 alias 来指定别名,如下:

<data>
    <import type="com.example.dbjavatest.bean.UserInfo"/>
    <import
        alias="NewUserInfo"
        type="com.example.dbjavatest.otherbean.UserInfo"/>
    <variable
        name="UserInfoExample"
        type="UserInfo" />
    <variable
        name="UserInfoExample2"
        type="NewUserInfo" />
    ...
</data>

步骤三:在View中引入数据,使用 @{} 语法,类名即为我们在中定义的name:

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

    <data>
        <variable
            name="UserInfoExample"
            type="com.example.dbjavatest.bean.UserInfo" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <TextView
            android:id="@+id/tv_user_first"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{UserInfoExample.name}"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"/>

        <TextView
            android:id="@+id/tv_user_second"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{UserInfoExample.age}"
            app:layout_constraintTop_toBottomOf="@+id/tv_user_first"
            app:layout_constraintStart_toStartOf="parent"/>

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

可以看出,布局文件里,第一个TextView的text通过@{UserInfoExample.name}引用UserInfo的name属性,第二个TextView的text通过@{UserInfoExample.age}引用UserInfo的age属性。

步骤四:在Activity中实现数据绑定:

java版:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding viewDataBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        UserInfo userInfo = new UserInfo("亚历山大", 99);
        viewDataBinding.setUserInfoExample(userInfo);
    }
}

kt版:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val viewDataBinding: ActivityMainBinding =
            DataBindingUtil.setContentView(this, R.layout.activity_main)
        viewDataBinding.userInfoExample = UserInfo("亚历山大", 99)
    }
}

可以看到这里不再需要传统的setContentView()指定布局了。直接通过 DataBindingUtil 设置布局文件并得到对应dataBinding对象,并给相应属性赋值,你可以发现此属性的setxxx()并不是bean实体类名,而是布局中里的name属性名。

一运行,发现,报错:

image.png
顺着提示,找下原因,在对应生成的ActivityMainBindingImpl.java类中的116行,如下:

image.png
原因就在此,原UserInfo的实例getAge()返回的是int类型,这里setText里只接受String,按此推断,修改原布局文件就可以了。

<TextView
    android:id="@+id/tv_user_second"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{String.valueOf(UserInfoExample.age)}"
    app:layout_constraintTop_toBottomOf="@+id/tv_user_first"
    app:layout_constraintStart_toStartOf="parent"/>

按上述代码修改对应android:text=“”中的属性,运行后可正常发布:

image.png
细心的你可能会发现,xml预览界面中由于没有填写相应数值,致使显示是一片空白。这样不利于我们去观察所设置的TextView颜色大小等属性实际表现结果,这时我们可以去设置默认值来让其在预览界面中显示,格式如下:

android:text="@{UserInfoExample.name,default=defaultValue}"

我们修改原布局中元素属性为:

<TextView
    android:id="@+id/tv_user_first"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{UserInfoExample.name,default=defaultValue}"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintStart_toStartOf="parent"/>

<TextView
    android:id="@+id/tv_user_second"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{UserInfoExample.age,default=25}"
    app:layout_constraintTop_toBottomOf="@+id/tv_user_first"
    app:layout_constraintStart_toStartOf="parent"/>

对应生成的预览界面为:

image.png

3、Activity中使用

跟ViewBinding一样,可通过ActivityMainBinding(你对应的activity的dataBinding类)来获取指定id控件,例如:

java版:

viewDataBinding.tvUserFirst.setText("get到了第一个TextView");

kt版:

viewDataBinding.tvUserFirst.text = "get到了第一个TextView"

原布局中,可在< data>标签里指定对应的ViewBinding名称,如果不指定就是默认根据文件名和驼峰原则生成的类名(上面说的activity_main.xml和ActivityMainBinding)。例如:

<data class="DemoBinding">
    ...
</data>

指定中的class后,原activity就已经开始报错了,此时要修改对应的ViewBinding为 DemoBinding 。原来activity中代码则改为了:

java版:

    ...
        DemoBinding viewDataBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        UserInfo userInfo = new UserInfo("亚历山大", 99);
        viewDataBinding.setUserInfoExample(userInfo);
    ...

kt版:

    ...
        val viewDataBinding: DemoBinding =
            DataBindingUtil.setContentView(this, R.layout.activity_main)
        viewDataBinding.userInfoExample = UserInfo("亚历山大", 99)
    ...

4、在Fragment和RecyclerView中使用

在Fragment中的使用于Activity类似,这里为了方便,Fragment的布局fragment_main.xml内容就跟原activity_main.xml内容保持一致。其activity_main.xml引用此Fragment的布局就修改为(Fragment中id不能省略):

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

    <data>
        <variable
            name="UserInfoExample"
            type="com.example.dbjavatest.bean.UserInfo" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <fragment
            android:id="@+id/fragment_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:name="com.example.dbjavatest.MainFragment"/>

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

Fragment中代码表现则为:

java:

public class MainFragment extends Fragment {
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        FragmentMainBinding viewDataBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_main, container, false);
        viewDataBinding.setUserInfoExample("屋大维",99);
        return viewDataBinding.getRoot();
    }
}

kt:

class MainFragment : Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val viewDataBinding: FragmentMainBinding =
            DataBindingUtil.inflate(inflater, R.layout.fragment_main, container, false)
        viewDataBinding.userInfoExample = UserInfo("屋大维",99)
        return viewDataBinding.getRoot()
    }
    ...
}

可见,在Fragment中使用的是 DataBindingUtil对应的inflate(),在onCreateView()中返回对应ViewBinding.getRoot()。其他使用与在Activity中使用一致,这里就不赘述。

同样使用DataBindingUtil.inflate()来获取ViewBinding对象的是RecyclerView的Adapter中:

java版:

...
@NonNull
@Override
public ViewBindHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    ViewDataBinding inflate = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.item_recyclerview,parent,false);
    ViewBindHolder viewHolder = new ViewBindHolder(inflate);
    viewHolder.setmViewBing(viewHolder);
    return new ViewHolder(inflater);
}
@Override
public void onBindViewHolder(@NonNull ViewBindHolder holder, int position) {
    holder.bind(...);//传入的UserInfo
}
static class ViewBindHolder extends RecyclerView.ViewHolder{
    private ItemRecyclerviewBinding mViewBing;
    public ViewBindHolder(View view){
        super(view);
    }
    public void setmViewBing(ItemRecyclerviewBinding mViewBing) {
        this.mViewBing = mViewBing;
    }
    public void bind(UserInfo userInfo){
        mViewBing.setUserInfoExample(userInfo);
    }
}
...

kt版:

   @NonNull
    override fun onCreateViewHolder(@NonNull parent: ViewGroup, viewType: Int): ViewBindHolder? {
        val inflate: ViewDataBinding = DataBindingUtil.inflate(
            LayoutInflater.from(parent.context),
            R.layout.item_recyclerview,
            parent,
            false
        )
        val viewHolder = ViewBindHolder(inflate)
        viewHolder.setmViewBing(viewHolder)
        return RecyclerView.ViewHolder(inflater)
    }
    override fun onBindViewHolder(@NonNull holder: ViewBindHolder?, position: Int) {
//        holder.bind(...);//传入的UserInfo
    }
    class ViewBindHolder(view: View?) : RecyclerView.ViewHolder(view!!) {
        private var mViewBing: ItemRecyclerviewBinding? = null
        fun setmViewBing(mViewBing: ItemRecyclerviewBinding?) {
            this.mViewBing = mViewBing
        }
        fun bind(userInfo: UserInfo?) {
            mViewBing!!.setUserInfoExample(userInfo)
        }
    }

在onCreateViewHolder中通过DataBindingUtil去获取相应的dataBinding对象,在ViewHolder中进行binding,其余操作与Activity、Fragment差别不大,这里不再赘述。

  • 14
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值