随着5G技术的流行,MVVM一定会大火起来。支持5G的手机肯定有更好的性能。而MVVM恰好对性能要求很高,MVVM内存消耗大,但开发速度为各种模式之最。
M是model,V是View,VM是viewmodle,MVVM有一个双向绑定的功能,在view和model之间,有一个叫做databinding的组件,这个组件负责把View和modle绑定。是一种数据驱动的形式,对应的modle层数据发生变化,view层马上发生变化。比如modle层变成了1,那么view层马上变成1,modle层变成2,那么View层马上变成2,数据驱动UI模式在电脑上用的很多,目前手机上用的还不多。
开始撸代码
第一部分
bean类
package com.example.testmvvm;
//这个类就相当于ViewModel
public class User {
private String name;
private String password;
public User(String name, String password) {
this.name = name;
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
布局代码
<?xml version="1.0" encoding="utf-8"?>
<!--与我们以前的布局有些不一样,这是我们MVVVM的布局-->
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
>
<data>
<!--此处定义该布局要用到的数据的名字和类型-->
<!--name是自定义的,type是我们的bean类-->
<variable
name="abc"
type="com.example.testmvvm.User" />
</data>
<!--这里定义我们的布局-->
<LinearLayout
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:text="@{`姓名:`+abc.name}"
android:layout_height="wrap_content"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{`密码`+abc.password}"/>
</LinearLayout>
</layout>
gradle的配置
apply plugin: 'com.android.application'
android {
compileSdkVersion 29
buildToolsVersion "29.0.2"
//build.gradle的配置
dataBinding{
enabled true
}
defaultConfig {
applicationId "com.example.testmvvm"
minSdkVersion 15
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
//加载图片的库
implementation 'com.squareup.picasso:picasso:2.5.2'
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
}
MainActivity代码
package com.example.testmvvm;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import android.os.Bundle;
import com.example.testmvvm.databinding.ActivityMainBinding;
public class MainActivity extends AppCompatActivity {
User user;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_main);
//DataBindingUtil编译出来的类,不再用setContentView加载布局
ActivityMainBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_main);
user = new User("zhang_xin","123");
//user已经到UI上去了
binding.setAbc(user);
}
}
看下运行效果
第二部分
大家不要离开,接下来还有更牛叉的。现在我们时时更新数据,我们在User类中进行修改
package com.example.testmvvm;
import androidx.databinding.BaseObservable;
import androidx.databinding.Bindable;
//这个类就相当于ViewModel
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;
//更新数据,该方法需要一个ID,这个ID是什么呢?
//我们自己写的ViewModel里面的每个成员,会生成一个BR的文件,把ID注册
//到里面,类似我们的R文件
notifyPropertyChanged(BR.name);
}
@Bindable
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
//通知UI更新的动作
notifyPropertyChanged(BR.password);
}
}
修改MainActivity
package com.example.testmvvm;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import android.os.Bundle;
import android.os.Handler;
import com.example.testmvvm.databinding.ActivityMainBinding;
public class MainActivity extends AppCompatActivity {
User user;
//延迟更新
Handler handler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_main);
//DataBindingUtil编译出来的类,不再用setContentView加载布局
ActivityMainBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_main);
user = new User("zhang_xin","123");
//user已经到UI上去了
binding.setAbc(user);
//5秒后更新数据
handler.postDelayed(new Runnable() {
@Override
public void run() {
user.setName("aaaaaaaaaaa");
user.setPassword("22222222222222");
}
},5000);
}
}
运行会发现,界面在5秒之后会发生改变
数据发生变化的时候会直接返回到我们UI上
第三部分
我们现在要加载图片,看看怎么加载图片
先看下xml文件的修改
<?xml version="1.0" encoding="utf-8"?>
<!--与我们以前的布局有些不一样,这是我们MVVVM的布局-->
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
>
<data>
<!--此处定义该布局要用到的数据的名字和类型-->
<!--name是自定义的,type是我们的bean类-->
<variable
name="abc"
type="com.example.testmvvm.User" />
</data>
<!--这里定义我们的布局-->
<LinearLayout
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="wrap_content">
<!--这里需要自定义属性,这里要和User关联-->
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:headImage = "@{abc.headImage}"
/>
<TextView
android:layout_width="wrap_content"
android:text="@{`姓名:`+abc.name}"
android:layout_height="wrap_content"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{`密码`+abc.password}"/>
</LinearLayout>
</layout>
再看下User类
package com.example.testmvvm;
import android.widget.ImageView;
import androidx.databinding.BaseObservable;
import androidx.databinding.Bindable;
import androidx.databinding.BindingAdapter;
import com.squareup.picasso.Picasso;
//这个类就相当于ViewModel
public class User extends BaseObservable {
private String name;
private String password;
//新增的属性,可以填到xml文件<ImageView/>标签中
private String headImage;
public User(String name, String password, String headImage) {
this.name = name;
this.password = password;
this.headImage = headImage;
}
public String getHeadImage() {
return headImage;
}
public void setHeadImage(String headImage) {
this.headImage = headImage;
}
@Bindable
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
//更新数据,该方法需要一个ID,这个ID是什么呢?
//我们自己写的ViewModel里面的每个成员,会生成一个BR的文件,把ID注册
//到里面,类似我们的R文件
notifyPropertyChanged(BR.name);
}
@Bindable
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
//通知UI更新的动作
notifyPropertyChanged(BR.password);
}
//自定义属性:提供一个静态方法,注意必须是静态方法,来加载headImage
//bind后面就是属性的名字
@BindingAdapter("bind:headImage")
public static void getImaage(ImageView view, String url) {
//view就通过bind 绑定到了app:headImage = ""属性,而@{abc.headImage}就相当于这里的url
Picasso.with(view.getContext()).load(url).into(view);//加载图片
}
}
看下ManiActivity类
package com.example.testmvvm;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import android.os.Bundle;
import android.os.Handler;
import com.example.testmvvm.databinding.ActivityMainBinding;
public class MainActivity extends AppCompatActivity {
User user;
//延迟更新
Handler handler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_main);
//DataBindingUtil编译出来的类,不再用setContentView加载布局
ActivityMainBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_main);
user = new User("zhang_xin","123","https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1573721346897&di=a236e053ff5f8b91f2c97964ccadb336&imgtype=0&src=http%3A%2F%2F5b0988e595225.cdn.sohucs.com%2Fq_70%2Cc_zoom%2Cw_640%2Fimages%2F20191017%2F8fd83a2e2b694ba2bcbeabdcf21f0510.jpeg");
//user已经到UI上去了
binding.setAbc(user);
//5秒后更新数据
handler.postDelayed(new Runnable() {
@Override
public void run() {
user.setName("aaaaaaaaaaa");
user.setPassword("22222222222222");
}
},5000);
}
}
看下运行效果
第四部分
图片也可以实时更新,我们修改下代码
package com.example.testmvvm;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import android.os.Bundle;
import android.os.Handler;
import com.example.testmvvm.databinding.ActivityMainBinding;
public class MainActivity extends AppCompatActivity {
User user;
Handler handler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_main);
user = new User("zhang_xin","123","https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1573723095492&di=e831e4354d4923542cdabf97cef383a6&imgtype=0&src=http%3A%2F%2Fi1.sinaimg.cn%2Fent%2Fd%2F2008-06-04%2FU105P28T3D2048907F326DT20080604225106.jpg");
binding.setAbc(user);
handler.postDelayed(new Runnable() {
@Override
public void run() {
user.setName("aaaaaaaaaaa");
user.setPassword("22222222222222");
//添加这段代码
user.setHeadImage("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1573721346897&di=a236e053ff5f8b91f2c97964ccadb336&imgtype=0&src=http%3A%2F%2F5b0988e595225.cdn.sohucs.com%2Fq_70%2Cc_zoom%2Cw_640%2Fimages%2F20191017%2F8fd83a2e2b694ba2bcbeabdcf21f0510.jpeg");
}
},5000);
}
}
修改User类
package com.example.testmvvm;
import android.widget.ImageView;
import androidx.databinding.BaseObservable;
import androidx.databinding.Bindable;
import androidx.databinding.BindingAdapter;
import com.squareup.picasso.Picasso;
//这个类就相当于ViewModel
public class User extends BaseObservable {
private String name;
private String password;
//新增的属性,可以填到xml文件<ImageView/>标签中
private String headImage;
public User(String name, String password, String headImage) {
this.name = name;
this.password = password;
this.headImage = headImage;
}
@Bindable
public String getHeadImage() {
return headImage;
}
public void setHeadImage(String headImage) {
this.headImage = headImage;
notifyPropertyChanged(BR.headImage);
}
@Bindable
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
//更新数据,该方法需要一个ID,这个ID是什么呢?
//我们自己写的ViewModel里面的每个成员,会生成一个BR的文件,把ID注册
//到里面,类似我们的R文件
notifyPropertyChanged(BR.name);
}
@Bindable
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
//通知UI更新的动作
notifyPropertyChanged(BR.password);
}
//自定义属性:提供一个静态方法,注意必须是静态方法,来加载headImage
//bind后面就是属性的名字
@BindingAdapter("bind:headImage")
public static void getImaage(ImageView view, String url) {
//view就通过bind 绑定到了app:headImage = ""属性,而@{abc.headImage}就相当于这里的url
Picasso.with(view.getContext()).load(url).into(view);//加载图片
}
}
第五部分
使用ListView
看下布局和listitem的布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<!--与我们以前的布局有些不一样,这是我们MVVVM的布局-->
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
>
<data>
<!--此处定义该布局要用到的数据的名字和类型-->
<!--name是自定义的,type是我们的bean类-->
<variable
name="abc"
type="com.example.testmvvm.User" />
</data>
<!--这里定义我们的布局-->
<LinearLayout
android:layout_width="match_parent"
android:orientation="horizontal"
android:layout_height="wrap_content">
<!--这里需要自定义属性,这里要和User关联-->
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:headImage = "@{abc.headImage}"
/>
<TextView
android:layout_width="wrap_content"
android:text="@{`姓名:`+abc.name}"
android:layout_height="wrap_content"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{`密码`+abc.password}"/>
</LinearLayout>
</layout>
看下user类
package com.example.testmvvm;
import android.widget.ImageView;
import androidx.databinding.BaseObservable;
import androidx.databinding.Bindable;
import androidx.databinding.BindingAdapter;
import com.squareup.picasso.Picasso;
//这个类就相当于ViewModel
public class User extends BaseObservable {
private String name;
private String password;
//新增的属性,可以填到xml文件<ImageView/>标签中
private String headImage;
public User(String name, String password, String headImage) {
this.name = name;
this.password = password;
this.headImage = headImage;
}
@Bindable
public String getHeadImage() {
return headImage;
}
public void setHeadImage(String headImage) {
this.headImage = headImage;
notifyPropertyChanged(BR.headImage);
}
@Bindable
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
//更新数据,该方法需要一个ID,这个ID是什么呢?
//我们自己写的ViewModel里面的每个成员,会生成一个BR的文件,把ID注册
//到里面,类似我们的R文件
notifyPropertyChanged(BR.name);
}
@Bindable
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
//通知UI更新的动作
notifyPropertyChanged(BR.password);
}
//自定义属性:提供一个静态方法,注意必须是静态方法,来加载headImage
//bind后面就是属性的名字
@BindingAdapter("bind:headImage")
public static void getImaage(ImageView view, String url) {
//view就通过bind 绑定到了app:headImage = ""属性,而@{abc.headImage}就相当于这里的url
Picasso.with(view.getContext()).load(url).into(view);//加载图片
}
}
看下MainActivity类
package com.example.test;
import android.os.Bundle;
import android.widget.ListView;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.example.testmvvm.R;
import com.example.testmvvm.User;
import java.util.ArrayList;
import java.util.List;
/**
* @author writing
* @time 2019/11/14 15:09
* @note
*/
public class MainActivity extends AppCompatActivity {
ListView listView;
private List<User> users;
private String url = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1573721346897&di=a236e053ff5f8b91f2c97964ccadb336&imgtype=0&src=http%3A%2F%2F5b0988e595225.cdn.sohucs.com%2Fq_70%2Cc_zoom%2Cw_640%2Fimages%2F20191017%2F8fd83a2e2b694ba2bcbeabdcf21f0510.jpeg";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);
users = new ArrayList<>();
//核心是适配器,ListView就直接使用findViewById了
listView = findViewById(R.id.listView);
users.add(new User( "zhang_xin1","123456",url));
users.add(new User( "zhang_xin2","123456",url));
users.add(new User( "zhang_xin3","123456",url));
users.add(new User( "zhang_xin4","123456",url));
listView.setAdapter(new CommonAdapter<User>(this,getLayoutInflater(),R.layout.item, com.example.testmvvm.BR.abc,users));
}
}
看下CommonAdapter类
package com.example.test;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import androidx.databinding.DataBindingUtil;
import androidx.databinding.ViewDataBinding;
import java.util.List;
/**
* 写一个公共适配器,里面放的数据用泛型
*/
public class CommonAdapter<T> extends BaseAdapter {
private int layoutId;//布局ID
/**
* 与布局中的这个属性有关
* <variable
* name="abc"
* type="com.example.testmvvm.User" />
*/
private int variableId;//会自动生成
private Context context;
private LayoutInflater layoutInflater;
//需要添加的数据
private List<T> list;
public CommonAdapter(Context context, LayoutInflater inflater, int layoutId, int variableId, List<T> list) {
this.context = context;
this.layoutInflater = inflater;
this.layoutId = layoutId;
this.variableId = variableId;
this.list = list;
}
@Override
public int getCount() {
return list.size();
}
@Override
public Object getItem(int position) {
return list.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
//进行绑定动作
ViewDataBinding viewDataBinding;
if(convertView==null){
//解析文件
viewDataBinding = DataBindingUtil.inflate(layoutInflater,layoutId,parent,false);
}else{
//有数据就重用
viewDataBinding = DataBindingUtil.getBinding(convertView);
}
viewDataBinding.setVariable(variableId,list.get(position));
return viewDataBinding.getRoot().getRootView();
}
}
看下运行效果
第六部分增加点击事件
修改User类增加点击事件
public void click(View view) {
Toast.makeText(view.getContext(), "点击了", Toast.LENGTH_LONG).show();
}
修改listitem的布局中< ImageView />标签
<ImageView
android:onClick="@{abc.click}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:headImage = "@{abc.headImage}"
/>