mvvm简介
- MVVM是Model-View-ViewModel的简写。它本质上就是MVC 的改进版。MVVM 就是将其中的View 的状态和行为抽象化,让我们将视图 UI 和业务逻辑分开。当然这些事 ViewModel 已经帮我们做了,它可以取出 Model 的数据同时帮忙处理 View 中由于需要展示内容而涉及的业务逻辑。与MVC相比它减少了很多维护的成本,也大大提高的开发速度。但是由于mvvm由于生成大量的副本,且数据双向绑定同步需要开启线程,所以对内存的要求相比 mvc和mvp是要大很多的,不过随着手机内存的不断升高,消耗的性能基本会慢慢忽略不计。前端的一些框架比如vue等已经很完善的应用了mvvm架构。
mvvm基本使用方式
- 在 app gradle 的 android{} 下开启 dataBinding 的使用
dataBinding{
enabled = true
}
- 在使用的布局中顶层使用 layout 标签,然后在内部设置 data 数据源,然后才是我们正常构建的布局。代码如下:
其中 data 是设置数据源,<variable 标签里面设置的name是内部调用的方式,type对应相应的model类。我这里是新建了一个 User 类,里面有name 和 password 两个属性。具体用法看代码
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 设置数据源 data中可以添加多个数据源 -->
<data>
<variable
name="user"
type="com.example.myapplication.User" />
</data>
<!-- 我们自己的布局 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!--
数据设置采用上面设置的name.属性的方式
这里是 user.name
如果想拼接字符串使用的是键盘左上角数字1旁边的那个符号
"@{`拼接字符串`+user.name}"
-->
<TextView
android:id="@+id/tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{`姓名:`+user.name}" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{`密码:`+user.password}" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="click"
android:text="click" />
</LinearLayout>
</layout>
- 在Activity中使用方式
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 通过 DataBindingUtil 的 setContentView 方法替代 activity 的 setContentView 方法,返回 ActivityMainBinding
ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
tv = findViewById(R.id.tv);
User user = new User("张三", "123456");
// 通过 ActivityMainBinding 将数据源和view绑定
activityMainBinding.setUser(user);
}
public void click(View view) {
// empty
}
}
运行效果就是 User 的信息正确展示到的布局上。
- 那么mvvm数据变化了怎么同步更新UI呢。需要在model中修改一下
/**
* User 实体类
* mvvm 绑定的步骤如下:
* <p>
* 1.User类继承被观察者的一个类 BaseObservable androidx.databinding包下面的一个类(我是使用的androidx)
* 2.get方法添加 @Bindable 注解,这是一个运行时注解。
* 3.在需要同步属性的set方法中设置对应的值 如setName中:notifyPropertyChanged(BR.name); 其中 BR是编译时生成的一个类,没有的话可以rebuild一下
* 这样一个基本的数据绑定就结束了
*/
public class User extends BaseObservable {
private String name;
private String password;
public User(String name, String password) {
this.name = name;
this.password = password;
}
@Bindable
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name);
}
@Bindable
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
notifyPropertyChanged(BR.password);
}
}
此时我在点击事件重新设置User属性时,UI上的展示也随之变化。
设置 ImageView
- 如何设置 ImageVIew 图片呢?
首先在User中添加 header 属性代表图片地址。
public class User extends BaseObservable {
private String header;
public String getHeader() {
return header;
}
public void setHeader(String header) {
this.header = header;
}
//自定义属性 headUrl 是自定义的,在xml的imageView中引用
@BindingAdapter("headUrl")
public static void getImage(ImageView view, String url) {
Glide.with(view.getContext()).load(url).into(view);
}
}
xml中引用如下:
headUrl 为model中自己定义的属性 user.header为返回的数据
<ImageView
android:layout_width="200dp"
android:layout_height="500dp"
app:headUrl="@{user.header}" />
使用 ListView
- 下面是适配器代码
/**
* 通用适配器
* @param <T>
*/
public class MyListAdapter<T> extends BaseAdapter {
private Context context;
private List<T> lists;
private LayoutInflater inflater;
// 布局id
private int layoutId;
// 这是对应的 List的bean中生成的 variableId
private int variableId;
public MyListAdapter(Context context, List<T> lists, LayoutInflater inflater, int layoutId, int variableId) {
this.context = context;
this.lists = lists;
this.inflater = inflater;
this.layoutId = layoutId;
this.variableId = variableId;
}
@Override
public int getCount() {
return lists.size();
}
@Override
public Object getItem(int position) {
return lists.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewDataBinding dataBinding;
if (convertView == null) {
dataBinding = DataBindingUtil.inflate(inflater, layoutId, parent, false);
} else {
dataBinding = DataBindingUtil.getBinding(convertView);
}
dataBinding.setVariable(variableId, lists.get(position));
return dataBinding.getRoot().getRootView();
}
}
- 下面item代码
<?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">
<data>
<variable
name="item"
type="com.example.myapplication.User" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:layout_width="80dp"
android:layout_height="160dp"
app:headUrl="@{item.header}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="10dp"
android:text="@{item.name}"
android:textSize="18sp" />
</LinearLayout>
</layout>
下面是使用:
List<User> list = new ArrayList<>();
list.add(new User("张三", "123456", "图片地址"));
list.add(new User("李四", "123456", "图片地址"));
list.add(new User("王五", "123456", "图片地址"));
list.add(new User("赵六", "123456", "图片地址"));
listView.setAdapter(new MyListAdapter<User>(this, list, getLayoutInflater(), R.layout.item, BR.item));
- 点击事件添加方式如下:方式有几种 我这种只是其中一种
在User中添加
// getiName就是User中的一个get方法。
public void click(View view) {
Toast.makeText(view.getContext(), getName(), Toast.LENGTH_SHORT).show();
}
在item的xml对应控件的点击事件中添加,我是添加到了 ImageVIew中
<ImageView
android:layout_width="80dp"
android:layout_height="160dp"
android:onClick="@{item.click}"
app:headUrl="@{item.header}" />
- 这样就可以展示出来ListView列表了。这么做ListView适配器可以使用一个,因为适配器中不关心 bean和view的关系。所以其他Listview可以直接引用。