MVVM与DataBinding基础(上)

本文深入探讨DataBinding与MVVM架构如何简化Android应用开发,减少模板代码,实现动态更新和双向绑定,提升开发效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 扯淡

在DataBinding之前,我们不可避免地要编写大量诸如  findViewById、setText和setOnClickListener等代码。通过 Data Binding,我们可以通过声明式布局以精简的代码来绑定应用程序逻辑和布局,这样就不用编写大量的模板代码了。在DataBinding和MVVM架构的配合下,代码显得更加的简洁。MVVM和MVP相比没有太多的搭建框架代码,MVVM模型如下。

 2 DataBinding

首先要说的是DataBinding是一个类似于便捷开发的工具,而MVVM是一种代码架构,两者并无关系,DataBinding也可以使用在MVP架构中,只是说DataBinding和MVVM和DataBinding的结合更加合适。再使用DataBinding时,首先需要在Module的build.gradle中添加如下的配置:

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

2.1 基础使用

首先常见一个简单的Model对象UserInfo。

public class UserInfo {
    private String name;
    private String pwd;

    public UserInfo(String name, String pwd) {
        this.name = name;
        this.pwd = pwd;
    }

    public String getName() {
        return name;
    }

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

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }
}

接着编写布局文件。

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

	<!-- 定义该布局需要绑定的数据名称和类型-->
	<data>
		<variable
			name="userInfo"
			type="com.itzb.mvvmdemo.model.UserInfo" />
	</data>

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

		<EditText
			android:id="@+id/et_name"
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:hint="请输入账号"
			android:text="@{userInfo.name}" />

		<EditText
			android:id="@+id/et_pwd"
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:hint="请输入密码"
			android:text="@={userInfo.pwd}" />

		<Button
			android:id="@+id/bt_login"
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:text="登录" />

	</LinearLayout>

</layout>

根节点由某一个容器(比如LinearLayout)变为layout,layout中包含了data节点和传统的视图,在data中定义了variable节点。其中,name属性表示变量的名称,type表示这个变量的类型,也就是UserInfo这个实体类的位置。variable节点的每一个变量都会在Binding辅助类中生成对应的 getters 和 setters。接着将@{userInfo.name}和@{userInfo.pwd}赋值给TextView的text属性。最后在Activity中将实体类和布局文件进行了绑定。

public class MainActivity extends AppCompatActivity {

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

        //1.先rebuild,2.书写代码绑定
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        UserInfo userInfo = new UserInfo("123", "456");
        binding.setUserInfo(userInfo);//给控件赋值
    }
}

运行结果:

2.2 事件处理

Data Binding也可以对点击事件进行处理,其有两种写法。其中第一种写法如下所示:

		<Button
			android:id="@+id/bt_login"
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:text="登录" />

首先在XML中定义一个Button,并设定它的id。接下来在Java代码中引用它:

binding.btLogin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

            }
        });

btNext指的就是XML中id为bt_next的Button。当我们给控件设定id时,就会在Binding辅助类中生成一个相应的public final 字段,以供调用。还有一种写法,在控件里通过@{}来设置自己的点击事件,接着在Java代码中引用它

		<Button
			android:id="@+id/bt_login"
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:onClick="@{OnClickListener}"
			android:text="登录" />
public class UserInfo {
    ...
    public View.OnClickListener loginClickListener = new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            
        }
    };
}

2.3 布局属性

1. import用法与别名

data节点支持import用法,如下所示

	<data>
		<import type="com.itzb.mvvmdemo.model.UserInfo"/>
		<variable
			name="userInfo"
			type="UserInfo" />
	</data>

data节点中的import需要写明具体导入的类名,这一点和Java的import是不同的。如果import引用了两个相同的类名,我们就可以用别名来区分它们

	<data>
		<import type="com.itzb.mvvmdemo.UserInfo" alias="zhangsan"/>
		<import type="com.itzb.mvvmdemo.model.UserInfo"/>

		<variable
			name="zhangsan"
			type="UserInfo" />
		
		<variable
			name="userInfo"
			type="UserInfo" />
	</data>

2.变量定义

我们也可以定义一些基本数据类型和String类型。除了可以定义基本类型的变量外,我们还可以定义List、Map等这样的集合变量。

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

	<!-- 定义该布局需要绑定的数据名称和类型-->
	<data>

		<import type="java.util.ArrayList" />

		<import type="java.util.Map" />

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

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

		<variable
			name="list"
			type="ArrayList&lt;String&gt;" />

		<variable
			name="map"
			type="Map&lt;String,String&gt;" />

		<variable
			name="array"
			type="String[]" />

	</data>

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

		<EditText
			android:id="@+id/et_name"
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:hint="请输入账号"
			android:text="@{name}" />

		<EditText
			android:id="@+id/et_pwd"
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:hint="请输入密码"
			android:text="@={String.valueOf(pwd)}" />

		<EditText
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:text="@={list.get(0)}" />

		<EditText
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:text="@={map.get('age')}" />

		<EditText
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:text="@={array[1]}" />

	</LinearLayout>

</layout>

为了支持泛型,我们可以使用转义字符,虽然这样会在代码中标记红色,但却可以运行。List集合可以通过list[index]来获取数值,也可以调用list.get(index)达到相同的目的。同样,Map集合也是如此。在Activity中使用变量,如下:

        binding.setName("123");
        binding.setPwd(456);

        ArrayList<String> list = new ArrayList<>();
        list.add("111");
        binding.setList(list);

        Map<String, String> map = new HashMap<>();
        map.put("age","18");
        binding.setMap(map);

        String[] array = {"111","222"};
        binding.setArray(array);

3.静态方法调用

在布局文件中也可以直接调用静态方法,达到对数据进行转换的目的

public class Utils {

    public static String getName(UserInfo userInfo) {
        return userInfo.getName();
    }
}
	<data>
		<import type="com.itzb.mvvmdemo.Utils"/>
		<variable
			name="userInfo"
			type="com.itzb.mvvmdemo.model.UserInfo" />

	</data>


		<EditText
			android:id="@+id/et_name"
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:hint="请输入账号"
			android:text="@{Utils.getName(userInfo)}" />

4.支持表达正式

XML中的表达式与 Java 表达式有很多相似之处。下面是二者的相同之处。

• 数学表达式:+-/*%
• 字符串拼接:+-
• 逻辑表达式:&&||
• 位操作符:&|^
• 一元操作符:+-!~
• 位移操作符:>>>>><<
• 比较操作符:==><>=<=
• instanceof
• 分组操作符:()
• 字面量
• character,String,numeric,null
• 强转、方法调用
• 字段访问
• 数组访问:[]
• 三元操作符:?:

2.4 动态更新和双向绑定

在前面的例子中,如果 Model 实体类的内容发生变化,那么界面 UI 是不会动态更新的。Data Binding提供了3种动态更新机制,根据Model实体类的内容来动态更新UI,分别对应于类(Observable)、字段(ObservableField)和集合类型(Observable 容器类)。从 MVVM 模式的角度来讲,通过动态更新机制,当Model发生变化时,就会通知ViewModel对View进行动态更新。结合动态更新机制,我们还可以实现双向绑定:当 View 发生变化时,ViewModel也会通知Model进行数据更新。

1.使用Observable

通过继承BaseObservable来实现动态更新

public class UserInfo extends BaseObservable {
    private String name;
    private String pwd;

    public UserInfo(String name, String pwd) {
        this.name = name;
        this.pwd = pwd;
    }

    @Bindable
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
        notifyPropertyChanged(com.itzb.mvvmdemo.BR.name);
    }

    @Bindable
    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
        notifyPropertyChanged(com.itzb.mvvmdemo.BR.pwd);
    }

}

我们只需要在getter上使用@Bindable注解,在setter中通知更新就可以了。其中BR是编译时生成的类,其功能与R.java类似,用@Bindable  标记过的getter方法会在BR中生成一个相应的字段。在setter中调用notifyPropertyChanged(BR.name)  通知系统BR.name这个字段的数据已经发生变化并更新UI。我们在Activity中使用UserInfo

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //1.先rebuild,2.书写代码绑定
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        userInfo = new UserInfo("123", "456");
        binding.setUserInfo(userInfo);//给控件赋值
        binding.btLogin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                userInfo.setName("zzz");
            }
        });
    }

当点击Button时,更改了userInfo的name字段,这时候会发现,界面上显示的name也发生了改变。

2.使用ObservableField

除了继承BaseObservable来实现动态更新外,还可以使用系统为我们提供的所有基本数据类型对应的Observable  类,比如ObservableInt、ObservableFloat、ObservableBoolean等,也可以使用引用数据类型和基本数据类型通用的ObservableField。它们都继承自BaseObservable。

public class UserInfo {
    public ObservableField<String> name = new ObservableField<>();
    public ObservableField<String> pwd = new ObservableField<>();

    public UserInfo(String name, String pwd) {
        this.name.set(name);
        this.pwd.set(pwd);
    }
}
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //1.先rebuild,2.书写代码绑定
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        userInfo = new UserInfo("123", "456");
        binding.setUserInfo(userInfo);//给控件赋值
        binding.btLogin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                userInfo.name.set("zzz");
            }
        });
    }

3. 使用Observable容器类

如果有多个UserInfo类型的数据需要动态更新怎么办?系统同样也提供了Observable容器类帮助我们解决这一问题。Observable容器类包括ObservableArrayList和ObservableArrayMap。这个场景使用ObservableArrayList是最佳选择。这时无须创建符合动态更新机制的Model实体类,只需要在代码中应用ObservableArrayList就可以了。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //1.先rebuild,2.书写代码绑定
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        userInfo1 = new UserInfo("123", "456");
        userInfo2 = new UserInfo("abc", "def");
        list = new ObservableArrayList<>();
        binding.setList(list);//给控件赋值
        binding.btLogin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                userInfo1.name.set("zzz");
                userInfo2.name.set("aaa");
            }
        });
    }

4.双向绑定

从MVVM模式的角度来讲,双向绑定就是Model和VIew通过ViewModel进行双向动态更新。前面讲到了Model实体类发生变化,UI会动态更新;反过来,如果UI发生变化,Model实体类也可以动态更新。

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

	<!-- 定义该布局需要绑定的数据名称和类型-->
	<data>

		<variable
			name="userInfo"
			type="com.itzb.mvvmdemo.model.UserInfo" />

	</data>

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

		<EditText
			android:id="@+id/et_name"
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:hint="请输入账号"
			android:text="@={userInfo.name}" />

		<EditText
			android:id="@+id/et_pwd"
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:hint="请输入密码"
			android:text="@={String.valueOf(pwd)}" />

		<Button
			android:id="@+id/bt_login"
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:text="登录" />

	</LinearLayout>

</layout>

在布局文件中定义 EditText 来改变 UserInfo的 name 字段,关键就是将"@{userInfo.name}"改为"@={userInfo.name}"。定义TextView来动态显示ObSwordsman的name字段的变化。最后定义Button来重置ObSwordsman的name字段。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //1.先rebuild,2.书写代码绑定
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        userInfo = new UserInfo("123", "456");
        binding.setUserInfo(userInfo);//给控件赋值
        binding.btLogin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                userInfo.name.set("zzz");
            }
        });
    }

这样运行程序,当EditText编辑的内容发生变化时,TextView中的内容也会相应地变化。当我们点击Button时,EditText的内容会改变。这样就实现了一个简单的双向绑定。

MVVM与DataBinding基础(下):https://blog.youkuaiyun.com/zb987570437/article/details/103377641

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值