首先在这里我讲的不会特别深,具体让大家对mvvm模式和databinding进行一个大概的了解;
MVC:
主要分为3层:view(主要为用户界面):
主要为xml文件和一些自定义view;发送指令到业务逻辑层;
controller(业务逻辑):
activity,fragment,adapter;根据view层的指令通知model层改变当前状态;
model(数据存储)
网路请求、数据解析、;请求数据和发送数据到view,用户得到反馈
MVVM(modelview和viewmodel):
Model:数据实现和逻辑处理;
view:界面显示;
viewmodel:将model和view绑定,model的改变通过viewmodel反馈给view;(databinding绑定数据和视图,双向绑定,View的变动,自动反映在viewmodel,反之亦然)
使开发者可以快速构建丰富的具有响应式的用户式体验 ,改善应用程序的开发,使代码更加干净优雅 ;
相关文档以及DEMO
https://github.com/LyndonChin/MasteringAndroidDataBinding databinding入门及高级用法
http://www.jianshu.com/p/2d3227d9707d 相关资料
https://realm.io/cn/news/data-binding-android-boyar-mount/ 棉花糖视频解疑
https://github.com/tianzhijiexian/DBinding DBinging(databinding改造库)
https://www.zybuluo.com/shark0017/note/256112 DBinging使用指南
http://blog.csdn.net/jdsjlzx/article/details/48133293详细指南
http://www.jianshu.com/p/c481d1f4e0b6 双向绑定
DEMO导入步骤:
demo:
版本改正dao自己当前编译环境版本:
compileSdkVersion 23
buildToolsVersion "23.0.3"
defaultConfig {
applicationId "kale.dbinding"
minSdkVersion 15
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
//recyclerview包不能填写固定版本
compile 'com.android.support:recyclerview-v7:+'
//设置支持databinding
dataBinding {
enabled = true
}
项目 build.gradle
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
classpath 'me.tatarka:gradle-retrolambda:3.2.0'//lambda
simple高版本AS可以使用,AS 2.2 Alpha gradle classpath 'com.android.tools.build:gradle:2.2.0-alpha2'
传统代码布局写法:
<LinearLayout …>
<TextView android:id="@+id/name"/>
<TextView android:id="@+id/lastName"/>
</LinearLayout>
private TextView mName;
protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_main);
mName = (TextView) findViewById(R.id.name);
mLastName = (TextView) findViewById(R.id.lastName);}public void updateUI(User user) {
if (user == null) {
mName.setText(null);
mLastName.setText(null);
} else {
mName.setText(user.getName());
mLastName.setText(user.getLastName());
}}
使用一些布局集成框架(ButterKnife):
@BindView(R.id.title) TextView title;
@BindView(R.id.subtitle) TextView subtitle;
@BindView(R.id.footer) TextView footer;
@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_activity);
ButterKnife.bind(this);
// TODO Use fields...
}
使用databinding绑定:
private ActivityMainBinding mBinding;
protected void onCreate(Bundle savedInstanceState) {
mBinding = DataBindingUtil.setContentView(this,
R.layout.activity_main);
}
public void updateUI(User user) {
mBinding.setUser(user);
}
布局文件:从我们当前的布局文件中就可以直接看出当前view的绑定数据,当添加新的view的时候,不需要改变其他的java代码;方便在布局查找当前程序的BUG;
<layout>
<data>
<!--绑定type的包下的对象,name为当前布局使用-->
<variable name="user"
type="com.android.example.User"/>
</data>
<LinearLayout …>
<TextView android:text="@{user.name}"/>
<TextView android:text="@{user.lastName}"/>
<TextView android:text="@{"" + user.age}"/>
</LinearLayout>
</layout>
DataBinding的工作模式:
一、进入处理布局文件:在程序编译期间,将程序的中的布局文件找到获取里边的信息,并且删除视图里无关视图的信息(系统对此视图不能解析和识别);
二:通过语法解析表达式:
<TextView android:visibility="@{user.isAdmin ? View.VISIBLE : View.GONE}"/>
databinding将这些数据解析出来
三;编译时解决相关依赖问题,自动生成相关类文件;
题外:有人说:当你在view层进行数据绑定的时候,会减少view的复用,增加视图的数量,看到这里你会发现并不会产生这样的问题,例如我们当前视图绑定的是一个用户的个人界面的数据,我们只需要传入当前用户的对象,并对视图绑定,和我们在java代码层绑定视图是一个效果。
未编译时
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<!--数据-->
<data>
<variable name="user" type="com.android.example.User"/>
</data>
<!--视图-->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:text="@{user.name}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView android:text="@{user.lastname}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
</layout>
编译过程进入(工作模式一):读出数据并且丢掉视图的不认识的内容,并且为视图设置tag;向下兼容,适应旧系统设备
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:tag="binding_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView android:tag="binding_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
布局表达式与java代码转换:
<TextView android:text="@{myVariable}"/>
textView.setText(myVariable);
<ImageView android:src="@{user.image}"/>
imageView.setSrc(user.image);
如何处理设置图片(使用注解的方式添加对应关系,告知databinding如何处理这类view,只需书写一次):
<ImageView android:src="@{user.image}"/>
imageView.setImageResource(user.image);
@BindingMethod(
type = android.widget.ImageView.class,
attribute = "android:src",
method = "setImageResource")
databinding的亮点之处:
1.允许变量数据访问、方法调用、参数传递、比较、通过索引访问数组,甚至还支持三目运算表达式(旧:user==null?0:user.size()
简便写法: user.size()??0);
2.自动检查是否为null,null值并不会报错;
3.使用中括号来操作和访问list和map; list[]可能为一个集合或者数组
使用资源内容:
表达式:
android:padding="@{isBig ? @dimen/bigPadding : @dimen/smallPadding}"
内联字符串格式:(字符串格式化)
android:text="@{@string/nameFormat(firstName, lastName)}"
内联复数:
android:text="@{@plurals/banana(bananaCount)}"
自动属性:
我们为一个布局添加自定义属性时,databinding自动查找此类的set属性方法,继续判断他的参数类型是否匹配布局中的值(对应类型),示例:DrawaerLayout中大量的setter方法
<android.support.v4.widget.DrawerLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:scrimColor="@{@color/scrim}"/>
drawerLayout.setScrimColor(
resources.getColor(R.color.scrim))
事件处理:(还需了解)
<Button android:onClick="clicked" …/>
<Button android:onClick="@{handlers.clicked}" …/>
<Button android:onClick="@{isAdult ? handlers.adultClick : handlers.childClick}" …/>
<Button android:onTextChanged="@{handlers.textChanged}" …/>
数据与View联动改变(详细的观测性)
Observable Binding
设置可被观测对象(继承BaseObservabkle),我们当前的datbinding是正向绑定(即单向绑定),当数据变化时,view层会同步改变文字,示例:
public class Item extends BaseObservable {
private String price;
@Bindable(会自动产生一个 BR.price 类似R.java)
public String getPrice() {
return this.name;
}
public void setPrice(String price) {
this.price = price;
//数据改变更新通知
notifyChange();
}}
如何自定义可被观测的类:(还需了解)
public class Item implements Observable {
private PropertyChangeRegistry callbacks = new …
…
@Override
public void addOnPropertyChangedCallback(
OnPropertyChangedCallback callback) {
callbacks.add(callback);
}
@Override
public void removeOnPropertyChangedCallback(
OnPropertyChangedCallback callback) {
callbacks.remove(callback);
}}
双向绑定: 就是"@{}"改成了"@={}"
那么,如何开启双向绑定:
项目的build.gradle:
classpath 'com.android.tools.build:gradle:2.1.0-alpha3'
moudle的build.gradle中:
android {
dataBinding.enabled = true
}
如何自定义双向绑定;
性能问题:
1.databinding基本上是0反射;反射会影响较多性能;
2.在使用findviewbyid的流程是每次调用,都会从父布局中开始查找,找到当前id的view并返回view,可想而知性能较差;
databinding :将布局文件中所有view保存,只需遍历布局一次;
<TextView android:text="@{user.address.street}"/>
<TextView android:text="@{user.address.city}2/>
你有一个 user.address 和另一个 user.address,Data Binding 将会为此生成如下代码:
Address address = user.getAddress();
String street = address.getStreet();
String city = address.getCity();
RecyclerView 和 Data Binding
使用 ViewHolders 对于 ListView 是很常见的,在 RecyclerView 中也是强制实施这个模式。如果你看看 Data Binding 生成的代码,你会发现它实际上会产生 ViewHolder。并带有变量属性,它绑定着那些 View。你也可以很容易地在 RecyclerView 里面中使用。我们创建了一个 ViewHolder,有一些基本方法,和一个静态方法,它传递参数给 UserItemBinding(根据用户的布局文件自动生成的)。你需要调用 UserItemBinding 的 inflate。现在你有一个非常简单的 ViewHolder 类,绑定方法类似这样:
public class UserViewHolder extends RecyclerView.ViewHolder {
static UserViewHolder create(LayoutInflater inflater, ViewGroup parent) {
UserItemBinding binding = UserItemBinding .inflate(inflater, parent, false);
return new UserViewHolder(binding);
}
private UserItemBinding mBinding;
private UserViewHolder(UserItemBinding binding) {
super(binding.getRoot());
mBinding = binding;
}
public void bindTo(User user) {
mBinding.setUser(user);
mBinding.executePendingBindings();
}
}
其中有一个小细节要小心,就是调用这个executePendingBindings, 当你的数据还无效的时候,数据绑定是等到下一个动画帧之前才设置布局。这就让我们不可以一次性批量绑定完所有的数据内容,因为 RecyclerView 的机制并不是这样。当要绑定一个数据的时候,RecyclerView 会调用 BindView,让你去准备测量这个布局。这就是为什么我们叫这个方法为 executePendingBindings,它使数据绑定刷新所有挂起的更改。否则,它将视为另一个布局失效了。
对于 onCreateViewHolder,它只是调用第一个方法,和 onBind 传到 ViewHolder 对象。就是这样,我们没有写 findViewById,没有设置。一切都已经在你的布局文件中封装好了。
public UserViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
return UserViewHolder.create(mLayoutInflater, viewGroup);}
public void onBindViewHolder(UserViewHolder userViewHolder, int position) {
userViewHolder.bindTo(mUserList.get(position));
在前面的代码中,我们显示了一个非常简单直接的实现。比如说,用户 对象的名称更改了。绑定系统将关联它,并重新布局在下一个动画帧。下一个动画帧开始,计算出发生了什么变化,和更新 TextView。然后,TextView 说,“好吧,我的文字已经变了,我已经重新布局,现在的情况是我不知道我的新尺寸。让我们去告诉 RecyclerView 它的这个孩子有点困难吧,它需要重新布局它本身。”当这一切发生的时候,你不会得到任何的动画,因为你在一切都发生了之后才告诉 RecyclerView。RecyclerView 将尝试修复自身。结果:就没有动画了,但这不是我们想要的。
我 们希望发生的是,当用户的对象是无效的,我们告诉适配器项目已经改变了。反过来,它会告诉 RecyclerView,“嘿,你的一个孩子要改变,自己做好准备。”RecyclerView 知道了布局和那些子 View 已经改变了,它会指导他们重新绑定。当他们重新绑定,TextView 会说,“好吧,我的文字设置好了,我需要布局。”RecyclerView 会说,“好了,别担心,我准备好了,让我量量你。”结果:很多动画。你会得到所有的动画,因为一切都发生 RecyclerView 控制下。
绑定回调和有效载荷
public UserViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
//recyclerviewAdapter中的创建viewholder方法中,添加绑定回调,
final UserViewHolder holder = UserViewHolder.create(mLayoutInflater,
viewGroup);
holder.getBinding().addOnRebindCallback(new OnRebindCallback() {
public boolean onPreBind(ViewDataBinding binding) {
//如果recyclerview不计算布局,返回false,view不需要更新
return mRecyclerView != null && mRecyclerView.isComputingLayout();
}
public void onCanceled(ViewDataBinding binding) {
//当recyclerview重新计算布局的时候,回调cancled并且告诉recyclerview那一条改变了,让recyclerview去更新它
if (mRecyclerView == null || mRecyclerView.isComputingLayout()) {
return;
}
int position = holder.getAdapterPosition();
if (position != RecyclerView.NO_POSITION) {
notifyItemChanged(position, DATA_INVALIDATION);
}
}
});
return holder;}
玩转Android之MVVM开发模式实战,炫酷的DataBinding!
版权声明:本文为博主原创文章,转载请注明出处。若有错误地方,还望批评指正,不胜感激。
C# 很早就有了MVVM的开发模式,Android手机中的MVVM一直到去年Google的I\O大会上才推出,姗姗来迟。MVVM这中开发模式的优点自不必多说,可以实现视图和逻辑代码的解耦,而且,按照Google的说法,使用了MVVM的开发模式,还可以提高布局文件的解析速度,个人觉得这一点非常重要。我们在安卓开发中经常需要写很多个findViewById,让人心烦,很多人不想写这个于是用了一些注解框架,可是注解框架无论性能多好,效率总是要低于findViewById的,因此,Android中的MVVM也即databinding可以帮助我们彻底解决这个问题。OK,废话不多说,我们来看看具体要怎么在Android开发中使用MVVM。
在低版本的AndroidStudio中使用DataBinding稍微有点麻烦,这里不做介绍。我这里以AndroidStuido2.1为例来介绍DataBinding。本文主要包含以下几方面内容:
1.基本使用
2.绑定ImageView
3.绑定ListView
4.点击事件处理
5.数据更新处理
好了,那就开始吧!
1.基本使用
创建好一个Android Project之后,在gradle文件中添加如下几行代码,表示开启databinding:
1. android {
2. dataBinding{
3. enabled
android {
...
...
...
dataBinding{
enabled true
}
}
就是这么简单,一个简单的databinding配置之后,就可以开始使用数据绑定了。
要使用数据绑定,我们得首先创建一个实体类,比如User实体类,如下:
1. * Created by 王松 on 2016/7/31.
2. publicclass UserEntity {
3. private String username;
4. private String nickname;
5. private
6. public UserEntity() {
7. public getAge() {
8. return
9. public setAge( age) {
10. .age = age;
11. public String getNickname() {
12. return nickname;
13. public setNickname(String nickname) {
14. .nickname = nickname;
15. public String getUsername() {
16. return username;
17. public setUsername(String username) {
18. .username = username;
19. public UserEntity( age, String nickname, String username) {
20. .age = age;
21. .nickname = nickname;
22. .username = username;
/**
* Created by 王松 on 2016/7/31.
*/
public class UserEntity {
private String username;
private String nickname;
private int age;
public UserEntity() {
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public UserEntity(int age, String nickname, String username) {
this.age = age;
this.nickname = nickname;
this.username = username;
}
}
然后我们来看看布局文件该怎么写,首先布局文件不再是以传统的某一个容器作为根节点,而是使用<layout></layout>作为根节点,在<layout>节点中我们可以通过<data>节点来引入我们要使用的数据源,如下:
1. <?xml version="1.0" encoding="utf-8"?>
2. <layout
3. xmlns:android="http://schemas.android.com/apk/res/android"
4. <data>
5. <variable
6. name="user"
7. type="org.lenve.databinding1.UserEntity"/>
8. </data>
9. <LinearLayout
10. xmlns:tools="http://schemas.android.com/tools"
11. android:layout_width="match_parent"
12. android:layout_height="match_parent"
13. android:orientation="vertical"
14. tools:context="org.lenve.databinding1.MainActivity"
15. <TextView
16. android:layout_width="wrap_content"
17. android:layout_height="wrap_content"
18. android:text="@{user.username}"/>
19. <TextView
20. android:layout_width="wrap_content"
21. android:layout_height="wrap_content"
22. android:text="@{user.nickname}"/>
23. <TextView
24. android:layout_width="wrap_content"
25. android:layout_height="wrap_content"
26. android:text="@{String.valueOf(user.age)}"/>
27. </LinearLayout>
28. </layout>
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
>
<data>
<variable
name="user"
type="org.lenve.databinding1.UserEntity"/>
</data>
<LinearLayout
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="org.lenve.databinding1.MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.username}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.nickname}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueOf(user.age)}"/>
</LinearLayout>
</layout>
在data中定义的variable节点,name属性表示变量的名称,type表示这个变量的类型,实例就是我们实体类的位置,当然,这里你也可以换一种写法,如下:
1. <data>
2. <import type="org.lenve.databinding1.UserEntity"/>
3. <variable
4. name="user"
5. type="UserEntity"/>
6. </data>
<data>
<import type="org.lenve.databinding1.UserEntity"/>
<variable
name="user"
type="UserEntity"/>
</data>
先使用import节点将UserEntity导入,然后直接使用即可。但是如果这样的话又会有另外一个问题,假如我有两个类都是UserEntity,这两个UserEntity分属于不同的包中,又该如何?看下面:
1. <data>
2. <import type="org.lenve.databinding1.UserEntity" alias="Lenve"/>
3. <variable
4. name="user"
5. type="Lenve"/>
6. </data>
<data>
<import type="org.lenve.databinding1.UserEntity" alias="Lenve"/>
<variable
name="user"
type="Lenve"/>
</data>
在import节点中还有一个属性叫做alias,这个属性表示我可以给该类取一个别名,我给UserEntity这个实体类取一个别名叫做Lenve,这样我就可以在variable节点中直接写Lenve了。
看完data节点我们再来看看布局文件,TextView的text属性被我直接设置为了@{user.username},这样,该TextView一会直接将UserEntity实体类的username属性的值显示出来,对于显示age的TextView,我用了String.valueOf来显示,因为大家知道TextView并不能直接显示int型数据,所以需要一个简单的转换,事实上,我们还可以在{}里边进行一些简单的运算,这些我一会再说。
最后,我们来看看Activity中该怎么写,setContentView方法不能够再像以前那样来写了,换成下面的方式:
1. DataBindingUtil.setContentView(, R.layout.activity_main)
DataBindingUtil.setContentView(this, R.layout.activity_main)
该方法有一个返回值,这个返回值就是系统根据我们的activity_main.xml布局生成的一个ViewModel类,所以完整写法如下:
1. ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(, R.layout.activity_main);
ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
有了ViewModel,再把数据绑定上去就可以了,如下:
1. @Override
2. protected onCreate(Bundle savedInstanceState) {
3. super.onCreate(savedInstanceState);
4. ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(, R.layout.activity_main);
5. UserEntity user = UserEntity();
6. user.setAge(
7. user.setUsername("zhangsan"
8. user.setNickname(
9. activityMainBinding.setUser(user);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
UserEntity user = new UserEntity();
user.setAge(34);
user.setUsername("zhangsan");
user.setNickname("张三");
activityMainBinding.setUser(user);
}
运行,显示效果如下:
OK,那我们刚才还说到可以在@{}进行简单的计算,都有哪些计算呢?我们来看看:
1.基本的三目运算
1. <TextView
2. android:layout_width="wrap_content"
3. android:layout_height="wrap_content"
4. android:text="@{user.username??user.nickname}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.username??user.nickname}"/>
两个??表示如果username属性为null则显示nickname属性,否则显示username属性。
2.字符拼接
1. <TextView
2. android:layout_width="wrap_content"
3. android:layout_height="wrap_content"
4. android:text="@{`username is :`+user.username}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{`username is :`+user.username}"/>
大家注意,这里的字符拼接不是用单引号哦,用的是ESC按键下面那个按键按出来的。目前DataBinding中的字符拼接还不支持中文。
3.根据数据来决定显示样式
1. <TextView
2. android:layout_width="wrap_content"
3. android:layout_height="wrap_content"
4. android:background="@{user.age < 30 ? 0xFF0000FF:0xFFFF0000}"
5. android:text="@{String.valueOf(user.age)}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@{user.age < 30 ? 0xFF0000FF:0xFFFF0000}"
android:text="@{String.valueOf(user.age)}"/>
我在这里给TextView设置背景的时候,做了一个简单的判断,如果用户的年龄小于30,背景就显示为蓝色,否则背景就显示为红色,DataBinding里支持小于号但是不支持大于号,索性,大于小于号我都用转义字符来表示。
另外,DataBinding对于基本的四则运算、逻辑与、逻辑或、取反位移等都是支持的,我这里不再举例。
2.绑定ImageView
OK,上文只是一个简单的绑定文本,下面我们来看看怎么样绑定图片,这里我们还得介绍DataBinding的另一项新功能,就是关于DataBinding自定义属性的问题,事实上,在我们使用DataBinding的时候,可以给一个控件自定义一个属性,比如我们下面即将说的这个绑定ImageView的案例。假设我现在想要通过Picasso显示一张网络图片,正常情况下这个显示很简单,可是如果我要通过DataBinding来实现,该怎么做呢?我们可以使用
1. @BindingAdapter
@BindingAdapter
注解来创建一个自定义属性,同时还要有一个配套的注解的方法。当我们在布局文件中使用这个自定义属性的时候,会触发这个被我们注解的方法,这样说大家可能还有一点模糊,我们来看看新的实体类:
1. * Created by 王松 on 2016/7/31.
2. publicclass User {
3. private String username;
4. private String userface;
5. public User() {
6. public User(String userface, String username) {
7. .userface = userface;
8. .username = username;
9. @BindingAdapter"bind:userface"
10. publicstatic getInternetImage(ImageView iv, String userface) {
11. Picasso.with(iv.getContext()).load(userface).into(iv);
12. public String getUserface() {
13. return userface;
14. public setUserface(String userface) {
15. .userface = userface;
16. public String getUsername() {
17. return username;
18. public setUsername(String username) {
19. .username = username;
/**
* Created by 王松 on 2016/7/31.
*/
public class User {
private String username;
private String userface;
public User() {
}
public User(String userface, String username) {
this.userface = userface;
this.username = username;
}
@BindingAdapter("bind:userface")
public static void getInternetImage(ImageView iv, String userface) {
Picasso.with(iv.getContext()).load(userface).into(iv);
}
public String getUserface() {
return userface;
}
public void setUserface(String userface) {
this.userface = userface;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
新类里边只有两个属性,分别是用户名和用户图像,用户图像中存储的实际上是一个网络图片地址,这里除了基本的get/set方法之外还多了一个叫做getInternetImage的网络方法,这个方法有一个注解@BindAdapter("bind:userface"),该注解表示当用户在ImageView中使用自定义属性userface的时候,会触发这个方法,我在这个方法中来为这个ImageView加载一张图片,这里有一点需要注意,就是该方法必须为静态方法。OK,我们再来看看这次的布局文件:
1. <?xml version="1.0" encoding="utf-8"?>
2. <layout
3. xmlns:android="http://schemas.android.com/apk/res/android"
4. xmlns:app="http://schemas.android.com/apk/res-auto"
5. <data>
6. <variable
7. name="user"
8. type="org.lenve.databinding2.User"/>
9. </data>
10. <LinearLayout
11. xmlns:tools="http://schemas.android.com/tools"
12. android:layout_width="match_parent"
13. android:layout_height="match_parent"
14. android:orientation="vertical"
15. tools:context="org.lenve.databinding2.MainActivity"
16. <ImageView
17. android:id="@+id/iv"
18. android:layout_width="wrap_content"
19. android:layout_height="wrap_content"
20. app:userface="@{user.userface}"></ImageView>
21. <TextView
22. android:layout_width="wrap_content"
23. android:layout_height="wrap_content"
24. android:text="@{user.username}"/>
25. </LinearLayout>
26. </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>
<variable
name="user"
type="org.lenve.databinding2.User"/>
</data>
<LinearLayout
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="org.lenve.databinding2.MainActivity">
<ImageView
android:id="@+id/iv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:userface="@{user.userface}"></ImageView>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.username}"/>
</LinearLayout>
</layout>
大家注意我在ImageView控件中使用userface属性的时候,使用的前缀不是android而是app哦。再来看看Activity中的代码:
1. @Override
2. protected onCreate(Bundle savedInstanceState) {
3. super.onCreate(savedInstanceState);
4. ActivityMainBinding dataBinding = DataBindingUtil.setContentView(, R.layout.activity_main);
5. dataBinding.setUser( User("http://img2.cache.netease.com/auto/2016/7/28/201607282215432cd8a.jpg"
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding dataBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
dataBinding.setUser(new User("http://img2.cache.netease.com/auto/2016/7/28/201607282215432cd8a.jpg", "张三"));
}
就是这么简单,加上网络权限就可以运行了,运行效果如下:
3.绑定ListView
好了,看完了简单使用之后,不知道你有没有喜欢上DataBinding,如果还没有,那就再来看看使用DataBinding来给ListView绑定数据吧,这个你一定会喜欢上的。因为使用这中方式来绑定太简单了。
先来看看我们要做的效果吧:
就是一个ListView,左边显示图片,右边显示文本,这样一个效果。OK,那就一步一步来吧,先是主布局:
1. <?xml version="1.0" encoding="utf-8"?>
2. <RelativeLayout
3. xmlns:android="http://schemas.android.com/apk/res/android"
4. xmlns:tools="http://schemas.android.com/tools"
5. android:layout_width="match_parent"
6. android:layout_height="match_parent"
7. tools:context="org.lenve.databinding3.MainActivity"
8. <ListView
9. android:id="@+id/lv"
10. android:layout_width="match_parent"
11. android:layout_height="match_parent"></ListView>
12. </RelativeLayout>
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="org.lenve.databinding3.MainActivity">
<ListView
android:id="@+id/lv"
android:layout_width="match_parent"
android:layout_height="match_parent"></ListView>
</RelativeLayout>
主布局很简单,就是一个ListView,再来看看ListView的item布局:
1. <?xml version="1.0" encoding="utf-8"?>
2. <layout
3. xmlns:android="http://schemas.android.com/apk/res/android"
4. xmlns:app="http://schemas.android.com/apk/res-auto"
5. <data>
6. <variable
7. name="food"
8. type="org.lenve.databinding3.Food"/>
9. </data>
10. <RelativeLayout
11. android:layout_width="match_parent"
12. android:layout_height="96dp"
13. android:orientation="vertical"
14. <ImageView
15. android:id="@+id/iv"
16. android:layout_width="96dp"
17. android:layout_height="96dp"
18. android:padding="6dp"
19. app:img="@{food.img}"/>
20. <TextView
21. android:id="@+id/description"
22. android:layout_width="match_parent"
23. android:layout_height="wrap_content"
24. android:layout_marginLeft="8dp"
25. android:layout_toRightOf="@id/iv"
26. android:ellipsize="end"
27. android:maxLines=
28. android:text="@{food.description}"/>
29. <TextView
30. android:layout_width="wrap_content"
31. android:layout_height="wrap_content"
32. android:layout_marginLeft="8dp"
33. android:layout_toRightOf="@id/iv"
34. android:layout_alignParentBottom="true"
35. android:layout_marginBottom="2dp"
36. android:text="@{food.keywords}"
37. android:textStyle="bold"/>
38. </RelativeLayout>
39. </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>
<variable
name="food"
type="org.lenve.databinding3.Food"/>
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="96dp"
android:orientation="vertical">
<ImageView
android:id="@+id/iv"
android:layout_width="96dp"
android:layout_height="96dp"
android:padding="6dp"
app:img="@{food.img}"/>
<TextView
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_toRightOf="@id/iv"
android:ellipsize="end"
android:maxLines="3"
android:text="@{food.description}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_toRightOf="@id/iv"
android:layout_alignParentBottom="true"
android:layout_marginBottom="2dp"
android:text="@{food.keywords}"
android:textStyle="bold"/>
</RelativeLayout>
</layout>
图片加载、文本加载前两节都已经说过了,这里的东西就没有什么难度了,我们再来看看实体类Food:
1. * Created by 王松 on 2016/7/31.
2. publicclass Food {
3. private String description;
4. private String img;
5. private String keywords;
6. private String summary;
7. public Food() {
8. public Food(String description, String img, String keywords, String summary) {
9. .description = description;
10. .img = img;
11. .keywords = keywords;
12. .summary = summary;
13. @BindingAdapter"bind:img"
14. publicstatic loadInternetImage(ImageView iv, String img) {
15. Picasso.with(iv.getContext()).load(img).into(iv);
16. public String getDescription() {
17. return description;
18. public setDescription(String description) {
19. .description = description;
20. public String getImg() {
21. return
22. public setImg(String img) {
23. .img = img;
24. public String getKeywords() {
25. return keywords;
26. public setKeywords(String keywords) {
27. .keywords = keywords;
28. public String getSummary() {
29. return summary;
30. public setSummary(String summary) {
31. .summary = summary;
/**
* Created by 王松 on 2016/7/31.
*/
public class Food {
private String description;
private String img;
private String keywords;
private String summary;
public Food() {
}
public Food(String description, String img, String keywords, String summary) {
this.description = description;
this.img = img;
this.keywords = keywords;
this.summary = summary;
}
@BindingAdapter("bind:img")
public static void loadInternetImage(ImageView iv, String img) {
Picasso.with(iv.getContext()).load(img).into(iv);
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getImg() {
return img;
}
public void setImg(String img) {
this.img = img;
}
public String getKeywords() {
return keywords;
}
public void setKeywords(String keywords) {
this.keywords = keywords;
}
public String getSummary() {
return summary;
}
public void setSummary(String summary) {
this.summary = summary;
}
}
这个实体类中有一个加载图片的方法,加载方式我们上文都已经介绍过了,不多说。好了,再来看看我们的终极Adapter类:
1. * Created by 王松 on 2016/7/31.
2. publicclass MyBaseAdapter<T> extends BaseAdapter {
3. private Context context;
4. private LayoutInflater inflater;
5. private layoutId;
6. private variableId;
7. private List<T> list;
8. public MyBaseAdapter(Context context, layoutId, List<T> list, resId) {
9. .context = context;
10. .layoutId = layoutId;
11. .list = list;
12. .variableId = resId;
13. inflater = LayoutInflater.from(context);
14. @Override
15. public getCount() {
16. return list.size();
17. @Override
18. public Object getItem( position) {
19. return list.get(position);
20. @Override
21. public getItemId( position) {
22. return position;
23. @Override
24. public View getView( position, View convertView, ViewGroup parent) {
25. ViewDataBinding dataBinding;
26. (convertView ==
27. dataBinding = DataBindingUtil.inflate(inflater, layoutId, parent, false
28. dataBinding = DataBindingUtil.getBinding(convertView);
29. dataBinding.setVariable(variableId, list.get(position));
30. return dataBinding.getRoot();
/**
* Created by 王松 on 2016/7/31.
*/
public class MyBaseAdapter<T> extends BaseAdapter {
private Context context;
private LayoutInflater inflater;
private int layoutId;
private int variableId;
private List<T> list;
public MyBaseAdapter(Context context, int layoutId, List<T> list, int resId) {
this.context = context;
this.layoutId = layoutId;
this.list = list;
this.variableId = resId;
inflater = LayoutInflater.from(context);
}
@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 dataBinding;
if (convertView == null) {
dataBinding = DataBindingUtil.inflate(inflater, layoutId, parent, false);
}else{
dataBinding = DataBindingUtil.getBinding(convertView);
}
dataBinding.setVariable(variableId, list.get(position));
return dataBinding.getRoot();
}
}
这个大概算是Adapter的终极写法了,如果你按这种方式来写Adapter,那么如果没有非常奇葩的需求,你这个App中可能就只有这一个给ListView使用的Adapter了,为什么这么说呢?因为这个Adapter中没有一个变量和我们的ListView沾边,解释一下几个变量吧:layoutId这个表示item布局的资源id,variableId是系统自动生成的,根据我们的实体类,直接从外部传入即可。另外注意布局加载方式为DataBindingUtil类中的inflate方法。OK,最后再来看看Activity:
1. publicclass MainActivity extends AppCompatActivity {
2. private Handler mHandler = Handler(){
3. @Override
4. public handleMessage(Message msg) {
5. MyBaseAdapter<Food> adapter = MyBaseAdapter<>(MainActivity., R.layout.listview_item, foods, org.lenve.databinding3.BR.food);
6. lv.setAdapter(adapter);
7. private List<Food> foods;
8. private ListView lv;
9. @Override
10. protected onCreate(Bundle savedInstanceState) {
11. super.onCreate(savedInstanceState);
12. setContentView(R.layout.activity_main);
13. lv = ((ListView) findViewById(R.id.lv));
14. initData();
15. private initData() {
16. OkHttpClient client = OkHttpClient.Builder().build();
17. Request request = Request.Builder().url("http://www.tngou.net/api/food/list?id=1").build();
18. client.newCall(request).enqueue( Callback() {
19. @Override
20. public onFailure(Call call, IOException e) {
21. @Override
22. public onResponse(Call call, Response response) throws IOException {
23. (response.isSuccessful()) {
24. parseJson(response.body().string());
25. private parseJson(String jsonStr) {
26. foods = ArrayList<>();
27. JSONObject jo = JSONObject(jsonStr);
28. JSONArray tngou = jo.getJSONArray("tngou"
29. ; i < tngou.length(); i++) {
30. JSONObject item = tngou.getJSONObject(i);
31. String description = item.getString("description"
32. String img = "http://tnfs.tngou.net/image"+item.getString("img"
33. String keywords = "【关键词】 "+item.getString("keywords"
34. String summary = item.getString("summary"
35. foods.add( Food(description, img, keywords, summary));
36. mHandler.sendEmptyMessage(
37. } catch (JSONException e) {
38. e.printStackTrace();
public class MainActivity extends AppCompatActivity {
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
MyBaseAdapter<Food> adapter = new MyBaseAdapter<>(MainActivity.this, R.layout.listview_item, foods, org.lenve.databinding3.BR.food);
lv.setAdapter(adapter);
}
};
private List<Food> foods;
private ListView lv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
lv = ((ListView) findViewById(R.id.lv));
initData();
}
private void initData() {
OkHttpClient client = new OkHttpClient.Builder().build();
Request request = new Request.Builder().url("http://www.tngou.net/api/food/list?id=1").build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
parseJson(response.body().string());
}
}
});
}
private void parseJson(String jsonStr) {
foods = new ArrayList<>();
try {
JSONObject jo = new JSONObject(jsonStr);
JSONArray tngou = jo.getJSONArray("tngou");
for (int i = 0; i < tngou.length(); i++) {
JSONObject item = tngou.getJSONObject(i);
String description = item.getString("description");
String img = "http://tnfs.tngou.net/image"+item.getString("img");
String keywords = "【关键词】 "+item.getString("keywords");
String summary = item.getString("summary");
foods.add(new Food(description, img, keywords, summary));
}
mHandler.sendEmptyMessage(0);
} catch (JSONException e) {
e.printStackTrace();
}
}
}
OkHttp下载数据和Json解析自不用多说,在构造MyAdapter的时候传入的最后一个参数,是BR中的,这个BR和我们项目中的R文件类似,都是系统自动生成的。
至此,我们使用DataBinding的方式来给ListView加载数据就算完成了。so easy~~~
4.点击事件处理
如果你使用DataBinding,我们的点击事件也会有新的处理方式,首先以ListView为例来说说如何绑定点击事件,在listview_item布局文件中每一个item的根节点添加如下代码:
1. <?xml version="1.0" encoding="utf-8"?>
2. <layout
3. xmlns:android="http://schemas.android.com/apk/res/android"
4. xmlns:app="http://schemas.android.com/apk/res-auto"
5. <RelativeLayout
6. android:layout_width="match_parent"
7. android:layout_height="96dp"
8. android:onClick="@{food.onItemClick}"
9. android:orientation="vertical"
10. <ImageView
11. android:id="@+id/iv"
12. android:layout_width="96dp"
13. android:layout_height="96dp"
14. android:padding="6dp"
15. app:img="@{food.img}"/>
16. </RelativeLayout>
17. </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"
>
....
....
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="96dp"
android:onClick="@{food.onItemClick}"
android:orientation="vertical">
<ImageView
android:id="@+id/iv"
android:layout_width="96dp"
android:layout_height="96dp"
android:padding="6dp"
app:img="@{food.img}"/>
....
....
....
</RelativeLayout>
</layout>
OK,我给RelativeLayout容器添了onClick属性,属性的值为food.onItemClick,那么这个onItemClick到底是什么呢?其实就是在实体类Food中定义的一个方法,如下:
1. public onItemClick(View view) {
2. Toast.makeText(view.getContext(), getDescription(), Toast.LENGTH_SHORT).show();
public void onItemClick(View view) {
Toast.makeText(view.getContext(), getDescription(), Toast.LENGTH_SHORT).show();
}
点击item获取当前position的数据,获取方式也是非常简单,直接get方法获取即可,比传统的ListView的点击事件通过position来获取数据方便多了。如果我想为关键字这个TextView添加点击事件也很简单,和上面一样,这里我就不再贴代码了,文末可以下载源码。
5. 数据更新处理
单纯的更新Food对象并不能改变ListView的UI显示效果,那该怎么做呢?Google给我们提供了三种解决方案,分别如下:
1.让实体类继承自BaseObservable
让实体类继承自BaseObservable,然后给需要改变的字段的get方法添加上@Bindable注解,然后给需要改变的字段的set方法加上notifyPropertyChanged(org.lenve.databinding3.BR.description);一句即可,比如我想点击item的时候把description字段的数据全部改为111,我可以修改Food类变为下面的样子:
1. publicclassextends BaseObservable {
2. private String description;
3. private String img;
4. private String keywords;
5. private String summary;
6. public Food() {
7. public Food(String description, String img, String keywords, String summary) {
8. .description = description;
9. .img = img;
10. .keywords = keywords;
11. .summary = summary;
12. @BindingAdapter"bind:img"
13. publicstatic loadInternetImage(ImageView iv, String img) {
14. Picasso.with(iv.getContext()).load(img).into(iv);
15. public onItemClick(View view) {
16. // Toast.makeText(view.getContext(), getDescription(), Toast.LENGTH_SHORT).show();
17. setDescription("111"
18. public clickKeywords(View view) {
19. Toast.makeText(view.getContext(), getKeywords(), Toast.LENGTH_SHORT).show();
20. @Bindable
21. public String getDescription() {
22. return description;
23. public setDescription(String description) {
24. .description = description;
25. notifyPropertyChanged(org.lenve.databinding3.BR.description);
26. public String getImg() {
27. return
28. public setImg(String img) {
29. .img = img;
30. public String getKeywords() {
31. return keywords;
32. public setKeywords(String keywords) {
33. .keywords = keywords;
34. public String getSummary() {
35. return summary;
36. public setSummary(String summary) {
37. .summary = summary;
public class Food extends BaseObservable {
private String description;
private String img;
private String keywords;
private String summary;
public Food() {
}
public Food(String description, String img, String keywords, String summary) {
this.description = description;
this.img = img;
this.keywords = keywords;
this.summary = summary;
}
@BindingAdapter("bind:img")
public static void loadInternetImage(ImageView iv, String img) {
Picasso.with(iv.getContext()).load(img).into(iv);
}
public void onItemClick(View view) {
// Toast.makeText(view.getContext(), getDescription(), Toast.LENGTH_SHORT).show();
setDescription("111");
}
public void clickKeywords(View view) {
Toast.makeText(view.getContext(), getKeywords(), Toast.LENGTH_SHORT).show();
}
@Bindable
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
notifyPropertyChanged(org.lenve.databinding3.BR.description);
}
public String getImg() {
return img;
}
public void setImg(String img) {
this.img = img;
}
public String getKeywords() {
return keywords;
}
public void setKeywords(String keywords) {
this.keywords = keywords;
}
public String getSummary() {
return summary;
}
public void setSummary(String summary) {
this.summary = summary;
}
}
OK,这是第一种解决方案,也是比较简单常用的一种。
2.使用DataBinding提供的ObservableFields来创建实体类
这种方式使用起来略微麻烦,除了继承BaseObservable之外,创建属性的方式也变成下面这种:
1. privatefinal ObservableField<String> description = ObservableField<>();
private final ObservableField<String> description = new ObservableField<>();
属性的读写方式也变了,读取方式如下:
1. description.get()
description.get()
写入方式如下:
1. .description.set(description);
this.description.set(description);
OK,依据上面几个规则,我新定义的实体类如下:
1. * Created by 王松 on 2016/7/31.
2. publicclassextends BaseObservable {
3. privatefinal ObservableField<String> description = ObservableField<>();
4. privatefinal ObservableField<String> img = ObservableField<>();
5. privatefinal ObservableField<String> keywords = ObservableField<>();
6. privatefinal ObservableField<String> summary = ObservableField<>();
7. public Food() {
8. public Food(String description, String img, String keywords, String summary) {
9. .description.set(description);
10. .keywords.set(keywords);
11. .img.set(img);
12. .summary.set(summary);
13. @BindingAdapter"bind:img"
14. publicstatic loadInternetImage(ImageView iv, String img) {
15. Picasso.with(iv.getContext()).load(img).into(iv);
16. public onItemClick(View view) {
17. // Toast.makeText(view.getContext(), getDescription(), Toast.LENGTH_SHORT).show();
18. setDescription("111"
19. public clickKeywords(View view) {
20. Toast.makeText(view.getContext(), getKeywords(), Toast.LENGTH_SHORT).show();
21. @Bindable
22. public String getDescription() {
23. return description.get();
24. public setDescription(String description) {
25. .description.set(description);
26. notifyPropertyChanged(org.lenve.databinding3.BR.description);
27. public String getImg() {
28. return img.get();
29. public setImg(String img) {
30. .img.set(img);
31. public String getKeywords() {
32. return keywords.get();
33. public setKeywords(String keywords) {
34. .keywords.set(keywords);
35. public String getSummary() {
36. return summary.get();
37. public setSummary(String summary) {
38. .summary.set(summary);
/**
* Created by 王松 on 2016/7/31.
*/
public class Food extends BaseObservable {
private final ObservableField<String> description = new ObservableField<>();
private final ObservableField<String> img = new ObservableField<>();
private final ObservableField<String> keywords = new ObservableField<>();
private final ObservableField<String> summary = new ObservableField<>();
public Food() {
}
public Food(String description, String img, String keywords, String summary) {
this.description.set(description);
this.keywords.set(keywords);
this.img.set(img);
this.summary.set(summary);
}
@BindingAdapter("bind:img")
public static void loadInternetImage(ImageView iv, String img) {
Picasso.with(iv.getContext()).load(img).into(iv);
}
public void onItemClick(View view) {
// Toast.makeText(view.getContext(), getDescription(), Toast.LENGTH_SHORT).show();
setDescription("111");
}
public void clickKeywords(View view) {
Toast.makeText(view.getContext(), getKeywords(), Toast.LENGTH_SHORT).show();
}
@Bindable
public String getDescription() {
return description.get();
}
public void setDescription(String description) {
this.description.set(description);
notifyPropertyChanged(org.lenve.databinding3.BR.description);
}
public String getImg() {
return img.get();
}
public void setImg(String img) {
this.img.set(img);
}
public String getKeywords() {
return keywords.get();
}
public void setKeywords(String keywords) {
this.keywords.set(keywords);
}
public String getSummary() {
return summary.get();
}
public void setSummary(String summary) {
this.summary.set(summary);
}
}
这种方式实现的功能和第一个实体类实现的功能一模一样。
3.使用DataBinding中提供的集合来存储数据即可
DataBinding中给我们提供了一些现成的集合,用来存储数据,比如ObservableArrayList,ObservableArrayMap,因为这些用的少,我这里就不做介绍了。
本文共涉及到三个Demo,由于CSDN对上传文件大小的限制,我分三次上传,下载地址如下:
1.http://download.csdn.net/detail/u012702547/9591142
2.http://download.csdn.net/detail/u012702547/9591150
3.http://download.csdn.net/detail/u012702547/9591160