Updated on 2016-12-03
Model(数据),View(界面),Controller(业务逻辑)
MVC(Model - View - Controller)
MVP(Model - View - Presenter)
MVVM(Model - View - ViewModel)
Model(数据),View(界面),ViewModel(双向绑定)
https://developer.android.com/topic/libraries/data-binding/index.html
双向绑定
- View Listeners:将 View 响应事件产生的数据设置到 Model 中。
- 将 View 的事件映射到 Model 可以承载的数据格式。
- Data Bindings:在 Model 发生变化时通知 View 作出响应。
- 将 Model 的数据映射到 View 的界面上。
启用 Data Binding
build.gradle
⇳
android {
dataBinding {
enabled = true
}
}
Binding Data
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
User user = new User("A", 25); Data Objects ↳ activity_main.xml ➜ ActivityMainBinding.java
activityMainBinding.setUser(user); Binding Data Objects
}
<layout xmlns:android="http://schemas.android.com/apk/res/android"> <layout> 节点
<data> <data> 节点(相当于 ViewModel,是 Model 和 View 之间的桥梁)
<import type="com.example.myapp.myapplication.User"/>
<variable
name="user" user 初始为 null,所以各属性为各自的初始值,以防止因 NullPointerException 而 Crash
type="User"/>
</data>
<LinearLayout <ViewGroup> 节点
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:text='@{""+user.name}' @{表达式}
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/textView1"/>
<TextView
android:text="@{``+user.age,default=35}" 设计阶段预览值(,default=35)
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/textView2"/>
</LinearLayout>
</layout>
Data Objects(Data Bindings)
可观测对象
public class User extends BaseObservable { 继承已实现 Observable 接口的 BaseObservable
private String name;
private int age;
public User(String name, int age) { 构造函数
this.name = name;
this.age = age;
}
@Bindable 绑定数据(生成 BR.name)
public String getName() {
return name;
}
@Bindable 绑定数据(生成 BR.age)
public int getAge() {
return age;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name); 通知数据改变
}
public void setAge(int age) {
this.age = age;
notifyPropertyChanged(BR.age); 通知数据改变
}
}
-------------------------------------------------------
user.setName("B");
String s = user.getName();
<data>
<import type="com.example.myapp.myapplication.User"/>
<variable
name="user"
type="User"/>
</data>
----
android:text='@{""+user.name}'
android:text='@{""+user.age}'
可观测字段
public class User {
public final ObservableField<String> name = new ObservableField<>(); ObservableField(可观测对象)
public final ObservableInt age = new ObservableInt(); ObservableInt(可观测对象)
public User(String name, int age) { 构造函数
this.name.set(name);
this.age.set(age);
}
}
-------------------------------------------------------
user.name.set("B");
String s = user.name.get();
<data>
<import type="com.example.myapp.myapplication.User"/>
<variable
name="user"
type="User"/>
</data>
----
android:text='@{""+user.name}'
android:text='@{""+user.age}'
可观测集合
ObservableArrayMap
索引为对象
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("name", "A");
user.put("age", 25);
-------------------------------------------------------
user.put("name", "B");
Object o = user.get("name");
<data>
<import type="android.databinding.ObservableMap"/>
<variable
name="user"
type="ObservableMap"/>
</data>
----
android:text='@{""+user["name"]}'
android:text='@{""+user["age"]}'
ObservableArrayList
索引为整数
ObservableArrayList<Object> user = new ObservableArrayList<>();
user.add("A");
user.add(25);
-------------------------------------------------------
user.set(0, "B");
Object o = user.get(0);
<data>
<import type="android.databinding.ObservableList"/>
<variable
name="user"
type="ObservableList"/>
</data>
----
android:text='@{""+user[0]}'
android:text='@{""+user[1]}'
Event Handling(View Listeners)
Method References
数据绑定时就评估表达式,方法签名需相同。
public class A {
public void a(View view) { 对应接口 OnClickListener 中的 onClick 方法
Log.w("Tag", "单击_" + view.getResources().getResourceEntryName(view.getId()));
}
public boolean b(View view) { 对应接口 OnLongClickListener 中的 onLongClick 方法
Log.w("Tag", "长按_" + view.getResources().getResourceEntryName(view.getId()));
return true;
}
public void c(CharSequence s, int i1, int i2, int i3) { 对应接口 TextWatcher 中的 onTextChanged 方法
Log.w("Tag", s.toString());
}
}
<data>
<import type="com.example.myapp.myapplication.A"/>
<variable
name="a"
type="A"/>
</data>
----
<EditText
android:onClick="@{a.a}" 传入对应方法即可,也可表达为 "@{a::a}"
android:onLongClick="@{a.b}"
android:onTextChanged="@{a.c}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/editText"/>
Listener Bindings
事件发生时才评估表达式,只需返回值相同。
public class A {
public void a(User user) { onClick 返回 void
Log.w("Tag", String.format("单击_%s_%s", user.name.get(), user.age.get()));
}
public boolean b() { onLongClick 返回 boolean
Log.w("Tag", "长按");
return true;
}
public void c(User user, CharSequence s) { onTextChanged 返回 void
Log.w("Tag", String.format("%s_%s_%s", s, user.name.get(), user.age.get()));
}
}
<data>
<import type="com.example.myapp.myapplication.A"/>
<import type="com.example.myapp.myapplication.User"/>
<variable
name="a"
type="A"/>
<variable
name="user"
type="User"/>
</data>
----
<EditText Listener Bindings 本质是回调方法,使用 Lambda 表达式传递参数,若无需自动生成的监听器提供的参数,则可省略参数列表
android:onClick="@{()->a.a(user)}"
android:onLongClick="@{()->a.b()}"
android:onTextChanged="@{(s,i1,i2,i3)->a.c(user,s)}" 未省略参数列表
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/editText"/>
@=
仅支持 text、checked、year、mouth、hour、rating、progress 等属性。
<data>
<variable
name="s"
type="String"/> lang 包下的类自动导入
<variable
name="b"
type="Boolean"/> lang 包下的类自动导入
</data>
----
<TextView
android:text="@{s}" 自动同步
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<TextView
android:text="@{``+b}" 自动同步
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<EditText
android:text="@={s}" 自动更新
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<CheckBox
android:checked="@={b}" 自动更新
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
OnPropertyChangedCallback
Observable.OnPropertyChangedCallback propertyChangedCallback = new Observable.OnPropertyChangedCallback() { 抽象类
@Override
public void onPropertyChanged(Observable sender, int propertyId) { 属性改变时调用
Log.w("Tag", propertyId + "_" + sender.toString());
}
};
user.name.addOnPropertyChangedCallback(propertyChangedCallback); 添加回调
user.name.removeOnPropertyChangedCallback(propertyChangedCallback); 移除回调
注解
BindingAdapter
将 XML 中定义的属性值与对应的实现方法绑定在一起。
绑定自定义属性
添加 新的 XML 属性及其实现。
@BindingAdapter("abc") 注解(该方法与自定义属性 app:abc 关联)
public static void a(TextView view, int height) { 为控件设置高度(该方法可以写在任意位置)
ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
layoutParams.height = height;
view.setLayoutParams(layoutParams);
}
-------------------------------------------------------
@BindingAdapter("abc")
public static void a(TextView view, int oldHeight, int newHeight) { 另外也可获取旧值(View,oldValue,newValue)(oldValue 最初为对应初始值,以防止 NullPointerException)
Log.w("Tag", String.format("旧值:%d,新值:%d", oldHeight, newHeight));
}
<data>
<variable
name="height"
type="Integer"/> lang 包下的类自动导入
</data>
----
<TextView
app:abc="@{height}" 使用自定义属性,传入 int,控件高度
android:text='@{``+user.name}'
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/textView1"/>
绑定系统属性
对原有 XML 属性进行 重写。
@BindingAdapter("android:src") 注解(该方法与系统属性 android:src 关联)
public static void b(ImageView view, String url) { 为 ImageView 加载图片(该方法可以写在任意位置)
Single
.just(url)
.map(s -> {
Bitmap bitmap = null;
try (BufferedInputStream bufferedInputStream = new BufferedInputStream(new URL(s).openStream())) {
bitmap = BitmapFactory.decodeStream(bufferedInputStream);
} catch (IOException e) {
e.printStackTrace();
}
return bitmap;
})
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(view::setImageBitmap);
}
<data>
<variable
name="url"
type="String"/> lang 包下的类自动导入
</data>
----
<ImageView
android:src="@{url}" @{} 表达式中传入 String
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<ImageView
android:src="@mipmap/ic_launcher" 普通表达式不受影响
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
组合
注解(同时关联自定义属性 i1,i2,false 表示控件使用二者其一便可匹配该方法,缺失的属性用其初始值替代,默认为 true)
例:
若控件只使用了 i1,则 pic 为 0;
若控件只使用了 i2,则 url 为 null。
@BindingAdapter(value = {"i1", "i2"}, requireAll = false)
public static void a(ImageView view, String url, int pic) { 图片加载完成之前显示占位图(该方法可以写在任意位置)
Single
.just(url)
.map(s -> {
Bitmap bitmap = null;
try (BufferedInputStream bufferedInputStream = new BufferedInputStream(new URL(url).openStream())) {
bitmap = BitmapFactory.decodeStream(bufferedInputStream);
} catch (IOException e) {
e.printStackTrace();
}
return bitmap;
})
.doOnSubscribe(() -> view.setImageResource(pic)) 设置 resId 占位图(可在非 UI 线程)
.observeOn(AndroidSchedulers.mainThread()) 切换至 UI 线程 (2)
.subscribeOn(Schedulers.io()) 指定最开始在IO线程中运行 (1)
.subscribe(view::setImageBitmap); 设置 Bitmap(必须在 UI 线程)
}
<data>
<import type="com.example.myapp.myapplication.R"/> 导入资源类 R
<variable
name="url"
type="String"/>
</data>
----
<ImageView
app:i1="@{url}" 使用自定义属性,传入 String,图片链接
app:i2="@{R.mipmap.ic_launcher}" 使用自定义属性,传入 int,占位图
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
BindingConversion
@BindingConversion
public static ColorDrawable a(int color) { 接收 int,转换为 ColorDrawable 对象(该方法可以写在任意位置)
Log.w("Tag", String.format("方法被调用,color = %d", color));
return new ColorDrawable(color);
}
<EditText
android:background="@{@color/colorAccent}" @{} 表达式中传入 int,background 属性接收 ColorDrawable 对象
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<EditText
android:background="@color/colorAccent" 普通表达式不受影响
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
Code
使用静态方法
-------------------------------------------------------
public class A {
public static String a(String s) {
return "你好:" + s;
}
}
<data>
<import type="com.example.myapp.myapplication.A"/>
<import type="com.example.myapp.myapplication.User"/>
<variable
name="user"
type="User"/>
</data>
----
android:text="@{A.a(user.name)}"
自定义 Binding 类名及生成位置
(默认为 activity_main.xml ➜ com.example.myapp.myapplication.databinding.ActivityMainBinding.java)
-------------------------------------------------------
<data class="ABC"> com.example.myapp.myapplication.databinding.ABC
...
</data>
<data class=".ABC"> com.example.myapp.myapplication.ABC
...
</data>
<data class="com.example.ABC"> com.example.ABC
...
</data>
运算符
(只有 this,super,new,<>泛型不支持)
-------------------------------------------------------
android:text='@{user.name ?? "无名氏"}' "A" ?? "B" 选取第一个非空值作为结果
等同于
android:text='@{user.name != null ? user.name : "无名氏"}' "A" != null ? "A" : "B"
android:text="@{``+user.age}" "``"
等同于
android:text='@{""+user.age}' '""'
使用 ID
-------------------------------------------------------
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<EditText
android:onTextChanged="@{(s,i1,i2,i3)->textView.setText(s)}" textView 同步显示输入
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
-------------------------------------------------------
<data>
<variable
name="b"
type="Boolean"/> Boolean 初始值为 false
</data>
----
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<EditText
android:id="@+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:enabled="@{b}" 自动同步
android:onClick="@{()->textView.setText(editText.getText())}" 使用 ID,直接在布局中处理逻辑
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="New Button"/>
<CheckBox
android:checked="@={b}" 自动更新(勾选启用按钮)
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
-------------------------------------------------------
<data>
<import type="android.view.View"/>
</data>
----
<CheckBox
android:id="@+id/checkBox" 勾选显示图片并启用按钮
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<ImageView
android:visibility="@{checkBox.checked?View.VISIBLE:View.GONE}" 显示或隐藏(CheckBox 隐式自动更新)
android:src="@mipmap/ic_launcher"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:enabled="@{checkBox.checked}" 启用或禁用(CheckBox 隐式自动更新)
android:text="New Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
所有 EditText 的字段不为空才启用按钮
-------------------------------------------------------
public class Presenter {
public static boolean a(String s1, String s2) {
if (s1 == null || s2 == null) {
return false;
}
return !s1.equals("") && !s2.equals(""); 所有 EditText 的字段不为空才启用按钮
}
}
-------------------------------------------------------
<data>
<import type="com.example.myapp.myapplication.Presenter"/>
<variable
name="s1"
type="String"/>
<variable
name="s2"
type="String"/>
</data>
----
<EditText
android:text="@={s1}" 自动更新
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<EditText
android:text="@={s2}" 自动更新
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:enabled='@{Presenter.a(s1,s2)}' 所有 EditText 的字段不为空才启用按钮
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="New Button"/>