什么是MVVM
用“某大文豪亲”的话说:MVVM并不存在,只是xml里找控件找了太多了,自然而然就“找”出了一套共性。
所以,MVVM只是包括了以下这些技术:
DataBind;
ViewModel双向绑定;
Okhttp3+retrofit+rxjava(时下最流行,我们后续教程会让学这个东西变得简单到极致);
其它非Android Studio内提供的一些控件、工具类的使用;
以上这一套又有人称为:Google Android Jetpack。
为什么传统的开发不用要去用MVVM?
首先让我们来看一下,传统的android开发我们会经常干一些什么样的事呢?
先定义一个layout布局,然后对控件设定android:id,然后在Activity的java端去做findViewById(R.id.myButton)这样找到这个控件,再然后。。。再然后。。。
再来看我们使用DataAdapter(MVC)的设计模式,动不动就是一堆的ViewHolder,getView还要避免二次重复加载......
这一切其实在Android的底层是根据一个xml格式的layout加载然后使用xml path路径查询去定位和绑定到我们的控件的。各位有Java Xml编程经验的都知道,xml的dom查找费时又费力,有时xml的层次嵌套一多还会出现性能问题。
因此呢,google就想在编译时就把这个xml一次转成一个Java的Object然后使用Java的内部支持的LinkList、ArrayList、HashMap等数据结构去实现这些定位,不是更优雅、更便捷、更“程序员”么?于是就产生了Data Binding。
那么有了Data Binding后,我们还要考虑诸如此类场景:譬如说我在界面有一个EditText,往里面录入了值,然后在点击一个【提交】按钮时,要把这个刚录进去的文字内容给取出来?那么换了平时我们需要先定位到这个控件->再把这个控件当前的内容取出来->赋给一个局部变量。有时如果碰到横坚屏切换,如果我们要保持输入的EditText内的内容保持不被clean掉还需要做二次赋值、二次查找控件,是不是?
那么现在假设我们可以让控件和这个临时局部变量和这个EditText的onChanged事件对开发时来说变得“透明”,每次set/get就可以了,让底层框架去实现这些控件定位、查找、二次赋值等工作,于是就有了ViewModel形式。
所以MVVM(Model View ViewModel)就这么来了!
当然,有了上述这些我们还要考虑如:Fragment里怎么MVVM、Activity里怎么MVVM、ImageView怎么MVVM、最常用的HttpJson Request怎么MVVM?
于是为了隐藏、掩盖极大便利开发者,把一些对“底层”的协议部分的转换、重复的劳动去除掉,这就有了什么Glide、Okhttp3+retrofix+rxjava这些东西了。
把这些东西打包在一起就成了google jetpack。
因此,一切都是源于“减少重复、无意义的劳动”而诞生的。大家可以认为MVVM就是J2ee->struts->this is not j2ee(spring)->spring+hibernate->jfx->spring+mybatis->spring boot->spring boot2这么一个演变过程中的产物而己。它并不是什么新的理念,也没有什么高的技术含量,它只是“因为开发的人多了、平时碰到的一些重复性劳动成为了共通的痛点后人们进行了总结、抽象”后的一个产物而己。
Android的学习路径
在进入正文前,我们的Android到今天为此算是一个里程碑,因为如果你不学后面的40多天,你其实已经自己可以开发点东西了,自己照着微博留言做一个小论坛、小商城,前面我们学习到的这些知识足够用了。但是,如果你想去正规化团队、好点的团队、公司工作肯定不能这么“作坊”,那么后面我们就会集中火力讲在jetpack即MVVM模式下的各种开发了。
所以,有必要先列一下什么称为学好Android需要经历哪些核心知识点。你也可以跳过这一节直接进入后续篇章,我是按照一个小白以及教学上从易入难、一个程序员是怎么培养的(从感性到理性)的一惯手法列出了下面共计:16大类、63个技术点,这也是我们这个系列遵照的一条写作线索。
当一个人把这些内容全学完了,Android才可以算是入门了(对不起,我从不说高手一类,因为高手是指哪些发明了Android的人,因此我到现在为止还一直称自己是合格的程序员,高级也不算。只有发明开发了那些mysql、linux、java语言的人才称得上真正的高级程序员)说白了就是可以找个好点的厂子了。
Android学习路线一览
一、开始阶段
新建工程
环境相关知识
二、基本布局
LinearLayout
TableLayout
RelativeLayout
几个基本布局的混用
三、基本组件与Android开发基础知识
TextView
EditText
监听回车
光标移动和选择
Button
ImageView
ProgressBar
RadioButton
CheckBox
SwitchButton
SeekBar
StatusBar
WebView
RecycleViews
DrawerLaout
自定义View
模块化基本知识
Gradle
NDK
调试机巧
一些常用第三方库
多线程
IO
Network相关基本知识
四、Activity相关
Android Activity是干什么的
Activity生命周期
Activity的启动以及携带参数启动
五、Service
Service是干什么的
后台Service
前台Service
IntentService用法
六、BroadCast
广播机制BroadCast的介绍
监听屏幕亮灭
七、Fragment
Fragment基本概念
Fragment的使用
八、res的应用
资源目录的使用
Shape的绘制
Android组件的一些自定义图片、背景
九、Android权限
十、字体
Text Style
Android的字库
十一、数据库
十二、设计模式
MVC
MVVM
十三、Android JetPack全家桶
介绍
Fragment间共享数据
DataBind
使用
数据绑定
对于ObserableFiled以及可观察对象的使用
Live Data
数据准备
Layout
Activity设计
LifeCycle
活动
状态转换
WorkManager
入门
Work Manager工作约束、延迟与查询
Work Manager定时任务
View Model
概览
Activity使用View Model
十四、编译打包CICD以及监控相关
十五、动画基础
十六、与设备相关开发
像机
蓝牙
Wifi
获取手机的角度、姿势
AIDL
ZIP
Binder
Notification
RTFSC
PackManager
MVVM-之从DataBind入门
我们假设我们有一个Login界面,里面就3个内容,如下图中所示。
user.name
user.password
user.header
然后点击一下【CLICK]按钮,把user.name和user.password里的内容变换成我们预设的另一个人的信息。
从传统的开发来说我们需要有两个TextView+一个ImageView,是不是?
现在我们用MVVM的DataBind是怎么实现的呢?
DataBind第一步-工程需要变成可以DataBind
在build.gradle里加入如下内容,一定不要忘了,记得它是加在android{}块内的
dataBinding {
enabled = true
}
DataBind第二步-build.properties内要启用google jetpack
在gradle.properties里加入以下两句话
android.useAndroidX=true
android.enableJetifier=true
还没完,接着把这个工程->Migrate to AndroidX,切记,否则后面你在学ViewModel时会碰到一堆很“怪”的问题,其实归根到底是因为我们前面一直用的都是android.v4.support的package,而MVVM里用到的都是androidx包内的内容,就算具体的类名相同其属性和相关的成员函数还是有不少区别的。从现在开始我们就要开始习惯于androidx了,如下截图操作:
选中你的工程->File->Refactor->Migrate to AndroidX。在Migrate时android studio会提示你备份好原有的项目,你可以选“Y”也可以选“Ignore”,看个人喜好吧。
DataBinding第三步-什么都正常的情况下出现了一堆红色的problem的处理手段
这一步后会提示项目重启,反正在新建项目时把这一步纳入第三步,这样一次干净的重启后顺达便项目进行了重编译、顺达便也就把一堆的gradle需要的依赖包给下下来了。
DataBinding第四步-Layout或者是activity的布局文件里要使用<layout>标签
如果我们已经有一个layout布局了,我们需要把我们的最最外层的xml的根结点,用layout替换成如下样例中的内容:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
>
改完后的一个标准的xml的layout内容如下所示:
<?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中可以添加多个数据源 -->
<data>
<variable
name="user"
type="com.mkyuan.android.demo.simplemvvm.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}" />
<ImageView
android:adjustViewBounds="true"
android:layout_width="150dp"
android:layout_height="200dp"
app:headId="@{user.header}" />
<Button
android:id="@+id/buttonChangeUserInfor"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="click"
android:text="click" />
</LinearLayout>
</layout>
DataBinding第五步-定义一个extends BaseObservable的Java POJO
拿我们的例子,这个界面有3个属性,一个是user.name,一个是user.password,一个是user.header。
package com.mkyuan.android.demo.simplemvvm;
/**
* User 实体类
* mvvm 绑定的步骤如下:
* <p>
* 1.User类继承被观察者的一个类 BaseObservable androidx.databinding包下面的一个类(我是使用的androidx)
* 2.get方法添加 @Bindable 注解,这是一个运行时注解。
* 3.在需要同步属性的set方法中设置对应的值 如setName中:notifyPropertyChanged(BR.name); 其中 BR是编译时生成的一个类,没有的话可以rebuild一下
* 这样一个基本的数据绑定就结束了
*/
import android.widget.ImageView;
import androidx.databinding.BaseObservable;
import androidx.databinding.Bindable;
import androidx.databinding.BindingAdapter;
import com.bumptech.glide.Glide;
import java.time.Instant;
public class User extends BaseObservable {
public User(String name, String password,int header) {
this.name = name;
this.password = password;
this.header=header;
}
@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);
}
private int header;
@Bindable
public int getHeader() {
return header;
}
public void setHeader(int header) {
this.header = header;
notifyPropertyChanged(BR.header);
}
//自定义属性 headUrl 是自定义的,在xml的imageView中引用
@BindingAdapter("headId")
public static void getImage(ImageView view, int headerId) {
Glide.with(view.getContext()).load(headerId).into(view);
}
private String name = "";
private String password = "";
}
此处我们使用了Glid,这是一个优化过的并且简化了的把远程/本地图片塞到ImageView的组件。要使用它你需要在gradle中加入以下的依赖:
implementation 'com.github.bumptech.glide:glide:4.9.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
核心代码解读:
@Bindable对象,它必须定义在getXXX方法上,它的作用等同于findViewById、find到后取控件、得到控件后取控件的相关属性、把控件相关属性里的value赋到一个临时变量这么一个作用;
notifyPropertyChanged(),它就相关于findViewById、找到控件后获取用户刚输入的那个内容、监听这个控件的onChanged事件,并且把这个刚输入的内容再替换之前get出来的内容赋给到的临时变量里的值的作用;
@BindingAdapter("headId"),介个就是对ImageView的databinding的用法了。它的作用就是绑定你的activity_main.xml文件里的ImageView对象,并使用Glide组件优雅的把一个本地(也可以是远程)Image(的ID)喂入ImageView里并显示成图片;
DataBinding正式使用
没有第六步了,第六步就是使用了,我们来看我们的Activity的交互端代码MainActivity.java
package com.mkyuan.android.demo.simplemvvm;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import android.os.Bundle;
import android.view.View;
import com.mkyuan.android.demo.simplemvvm.databinding.ActivityMainBinding;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "DemoSimpleMVVM";
private ActivityMainBinding activityMainBinding;
public void click(View view) {
int imageId=R.drawable.qq;
activityMainBinding.getUser().setName("李四");
activityMainBinding.getUser().setPassword("password123");
activityMainBinding.getUser().setHeader(imageId);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 通过 DataBindingUtil 的 setContentView 方法替代 activity 的 setContentView 方法,返回
// ActivityMainBinding
activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
int imageId=R.drawable.header;
User user = new User("张三", "123456",imageId);
// 通过 ActivityMainBinding 将数据源和view绑定
activityMainBinding.setUser(user);
}
}
看这个代码,从头到位有没有findViewById?
另外代码中我们可以看到有一个叫ActivityMainBinding的类,这个是哪来的?
我们看我们的项目中是不是一个activity就有一个叫activity_名字.xml的文件。那么我们在第四步中,在activity_main.xml文件里不是把最顶层的标签加了<Layout>以及相应的data的绑定了是不是?于是,AndroidStudio就自动会把这个activity_main.xml做以下操作:
去掉_xml;
把.xml前的用java驼峰命名方式连接_(下划线)前后的单词把这些单词大写+Binding并生成这个类(如果有一些开发者在AndroidStudio里没有生成这个类,那么你可以:Build->Rebuild Project一下)
然后你自己运行一下试试看这个效果,一个click按钮点下去,看似你在替换的是User这个类里的值实际Databind里把你的主界面里的user.name、user.password、user.header相binding的控件的值也给改变了。
是不是?嘿嘿!很好看吧?不妨自己动一下手试试看吧。
附、项目工程全结构
风雨虽大、但有你我携手“共撑一把伞”才能安然渡过。分享知识、便利你我、创造共赢!