赞
踩
在之前的章节中,已经介绍过JectPack
的两个组件,都与生命周期的感知有关----Lifecycle
+ LiveData
,那么本节将会介绍跟数据绑定有关的两个组件DataBinding
+ ViewModel
,从而引出MVVM架构的设计实现。
1、MVVM介绍
跟MVP不同的是,在MVP的P层中,主要做逻辑处理,像请求数据等;在MVVM中,是通过ViewModel代替了P层,ViewModel实现了View层和Model层的双向绑定,数据的绑定就是通过DataBinding这个组件实现的。
首先,要想使用MVVM,那么就要你的项目支持DataBinding,在build.gradle中,添加一行代码
dataBinding{
enabled true
}
2、DataBinding
+ ViewModel
一般来说,在使用MVVM架构的时候,更多的是在做XML布局和DataBean,一个布局对应一个页面,该页面的数据来源,通过ViewModel来提供,使用DataBinding与数据源绑定,因此在XML布局文件中,要声明该页面的数据来源。
(1)XML布局:在XML布局中,需要设置layout
、data
标签,layout
标签用来包裹整体的布局,data
则是声明在该布局中使用到的DataBean
类,如果有多个DataBean
,那么就设置多个variable
。
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <variable //这个databean的别名 name="user" //databean的全类名 type="com.example.mvvm.User" /> </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="@{user.username}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.password}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> </LinearLayout> </layout>
(2)DataBean:在MVVM中,就是ViewModel层,跟之前的DataBean不同的是,这个DataBean类所要做的工作可是很多的,一方面需要监听数据是否变化,然后将数据更新在XML布局文件;另一方面,也会监听XML布局文件中数据的变化,去更新数据库数据,这就是双向绑定。
/** * ViewModel */ public class User extends BaseObservable { private String username; private String password; public User(String username, String password) { this.username = username; this.password = password; } @Bindable public String getUsername() { return username; } public void setUsername(String username) { this.username = username; notifyPropertyChanged(BR.username); } @Bindable public String getPassword() { return password; } public void setPassword(String password) { this.password = password; notifyPropertyChanged(BR.password); } }
DataBean
要继承BaseObservable
就是一个被观察者,XML布局会查询数据,然后在get
方法需要加 @Bindable
注解,获取当前的返回数据值,与XML布局中的android:text="@{user.username}"
绑定;当数据源的数据发生变化时,在set方法中,加入notifyPropertyChanged(BR.user);
属性值变化,BR.user
就是在布局文件中设置的DataBean的别名,这样在数据发生变化时,就会同步更新到手机界面。
在完成View
层和ViewModel
层的基本工作完成之后,要在Activity
中完成DataBinding
super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_main);
ActivityMainBinding mainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
//从Model层来
user = new User("kobe","123456");
mainBinding.setUser(user);
通过DataBindingUtil
加载布局,会根据当前Activity的名字生成一个ActivityMainBinding
类,一般来说,我们在Model层会进行网路请求,或者数据库请求,会将数据回调到View层界面,在View层做数据调用更新UI,然后在XML布局中已经设置了DataBean的种类,所以通过ActivityMainBinding
来去设置数据,就可以将数据更新在界面。
当网路数据源发生更新之后,也会同步更新在与之绑定的布局文件上。
如果是加载一张图片到ImageView
,可以通过自定义属性,来将图片加载到界面。
//自定义属性
@BindingAdapter("bind:header")
public static void getImage(ImageView view,String url){
Glide.with(view.getContext()).load(url).into(view);
}
public String getHeader() {
return header;
}
public void setHeader(String header) {
this.header = header;
}
public User(String username, String password,String header) {
this.username = username;
this.password = password;
this.header = header;
}
然后在布局文件中:
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
bind:header="@{user.header}"></ImageView>
其中内部的原理就是:在加载布局时,因为layout
和data
标签,会分割为两部分,其中一部分,将这些@属性转换为tag
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:tag="binding_1" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:tag="binding_2" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:tag="binding_3" ></ImageView>
一些Tag的集合,然后在编译时生成的代表该Activity的DataBinding的实现类,从这些tags集合中,取出实例化,来取代findViewById
:
this.mboundView0 = (android.widget.LinearLayout) bindings[0];
this.mboundView0.setTag(null);
this.mboundView1 = (android.widget.TextView) bindings[1];
this.mboundView1.setTag(null);
this.mboundView2 = (android.widget.TextView) bindings[2];
this.mboundView2.setTag(null);
this.mboundView3 = (android.widget.ImageView) bindings[3];
this.mboundView3.setTag(null);
setRootTag(root);
然后,通过获取ViewModel中的数据,将数据填充到视图中。
@Override protected void executeBindings() { long dirtyFlags = 0; synchronized(this) { dirtyFlags = mDirtyFlags; mDirtyFlags = 0; } com.example.mvvm.User user = mUser; java.lang.String userHeader = null; java.lang.String userUsername = null; java.lang.String userPassword = null; if ((dirtyFlags & 0xfL) != 0) { if ((dirtyFlags & 0x9L) != 0) { if (user != null) { // read user.header userHeader = user.getHeader(); } } if ((dirtyFlags & 0xbL) != 0) { if (user != null) { // read user.username userUsername = user.getUsername(); } } if ((dirtyFlags & 0xdL) != 0) { if (user != null) { // read user.password userPassword = user.getPassword(); } } } // batch finished if ((dirtyFlags & 0xbL) != 0) { // api target 1 androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView1, userUsername); } if ((dirtyFlags & 0xdL) != 0) { // api target 1 androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView2, userPassword); } if ((dirtyFlags & 0x9L) != 0) { // api target 1 com.example.mvvm.User.getImage(this.mboundView3, userHeader); } }
除了上述的方式之外,在项目开发中,使用最多的,还是列表,无论是ListView,还是RecyclerView…涉及到适配器的有很多,我这里简单地用ListView介绍一下,其他的在后续项目中,如果用到,就再解释。
使用ListView时,最重要的还是数据(List数据),因此在数据源中,需要自定义属性,来得到所要展示的List数据。
public class ListBean { private List<User> list; public ListBean(List<User> list) { this.list = list; } public List<User> getList() { return list; } public void setList(List<User> list) { this.list = list; } @BindingAdapter("app:list") public static void getAdapter(ListView view,List<User> list){ view.setAdapter(new ListViewAdapter(view.getContext(),list)); } }
两个参数ListView和其对应的参数,使用BindingAdapter
注解,然后在布局文件中声明数据源。
<variable
name="rank"
type="com.example.mvvm.ListBean" />
<ListView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:list="@{rank.list}"></ListView>
在Activity中,进行数据绑定。
User user1 = new User();
user1.setUsername("lili");
User user2 = new User();
user2.setUsername("lili");
List<User> datas = new ArrayList<>();
datas.add(user1);
datas.add(user2);
ListBean bean = new ListBean(datas);
mainBinding.setRank(bean);
在ListView的适配器中,同样也使用DataBinding,这个使用就比较简单了。
<layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="user" type="com.example.mvvm.User" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/tv_rank" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.username}" android:textSize="20dp"></TextView> </LinearLayout> </layout>
在适配器中,代码简直节省了不止一点!!
@Override public View getView(int position, View convertView, ViewGroup parent) { // ViewHolder viewHolder = null; // if(convertView == null){ // convertView = View.inflate(context,R.layout.item_rank,null); // viewHolder = new ViewHolder(); // viewHolder.tv_rank = convertView.findViewById(R.id.tv_rank); // convertView.setTag(viewHolder); // }else{ // viewHolder = (ViewHolder) convertView.getTag(); // } // viewHolder.tv_rank.setText(datas.get(position).getUsername()); ItemRankBinding binding = null; if(convertView == null) { binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.item_rank, parent, false); }else{ binding = DataBindingUtil.getBinding(convertView); } binding.setUser(datas.get(position)); return binding.getRoot().getRootView(); }
我只贴出关键代码,之前使用ListView的时候,和使用的方式对比一下,代码很简洁,因为不用findViewById
,所以ViewHolder
也不必写了。
Button
点击事件
在之前使用Button单击事件时,常规的用法是:findViewById + 设置单击事件监听,然后获取数据。
btn_search.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String city = et_city.getText().toString();
//获取数据
ModelImpl.getInstance().getWeatherByCity(city,
"7****************5");
}
});
如果使用DataBinding
来处理,就简洁了许多:设置Activity
为变量之一,得到Activity
中该Button
的单击事件函数,就可以响应点击事件,不需要去findViewById
<variable
name="activity"
type="com.example.weather.view.activity.MainActivity" />
<Button
android:id="@+id/btn_search"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="查询"
android:textSize="20dp"
android:onClick="@{activity.onSearchClick}"
android:layout_below="@id/et_city"></Button>
//别忘记绑定数据
binding.setActivity(this);
public void onSearchClick(View view){
String city = et_city.getText().toString();
//获取数据
ModelImpl.getInstance().getWeatherByCity(city,
"7b153f32bcfb18ff064c519e5e7a6b35");
}
各类操作符的使用
算术运算符 + - / * %
字符串连接运算符 +
逻辑运算符 && ||
二元运算符 & | ^
一元运算符 + - ! ~
移位运算符 >> >>> <<
比较运算符 == > < >= <=(请注意,< 需要转义为 <)
instanceof
分组运算符 ()
字面量运算符 - 字符、字符串、数字、null
Null 合并运算符
如果左边运算数不是 null,则 Null 合并运算符 (??) 选择左边运算数,如果左边运算数为 ,则选择右边运算数。
android:text="@{user.displayName ?? user.lastName}"
这在功能上等效于:
android:text="@{user.displayName != null ? user.displayName :
user.lastName}"
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。