goods.setPrice(new Random().nextInt(100));
}
public void changeGoodsDetails() {
goods.setDetails(“hi” + new Random().nextInt(100));
goods.setPrice(new Random().nextInt(100));
}
}
}
[图片上传失败…(image-e9261-1600669682607)]
可以看到,name 视图的刷新没有同时刷新 price 视图,而 details 视图刷新的同时也刷新了 price 视图
实现了 Observable 接口的类允许注册一个监听器,当可观察对象的属性更改时就会通知这个监听器,此时就需要用到 OnPropertyChangedCallback
当中 propertyId
就用于标识特定的字段
goods.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
@Override
public void onPropertyChanged(Observable sender, int propertyId) {
if (propertyId == com.leavesc.databinding_demo.BR.name) {
Log.e(TAG, “BR.name”);
} else if (propertyId == com.leavesc.databinding_demo.BR.details) {
Log.e(TAG, “BR.details”);
} else if (propertyId == com.leavesc.databinding_demo.BR._all) {
Log.e(TAG, “BR._all”);
} else {
Log.e(TAG, “未知”);
}
}
});
2.2、ObservableField
继承于 Observable 类相对来说限制有点高,且也需要进行 notify 操作,因此为了简单起见可以选择使用 ObservableField。ObservableField 可以理解为官方对 BaseObservable 中字段的注解和刷新等操作的封装,官方原生提供了对基本数据类型的封装,例如 ObservableBoolean、ObservableByte、ObservableChar、ObservableShort、ObservableInt、ObservableLong、ObservableFloat、ObservableDouble 以及 ObservableParcelable ,也可通过 ObservableField 泛型来申明其他类型
public class ObservableGoods {
private ObservableField name;
private ObservableField details;
private ObservableFloat price;
public ObservableGoods(String name, String details, float price) {
this.name = new ObservableField<>(name);
this.details = new ObservableField<>(details);
this.price = new ObservableFloat(price);
}
···
}
对 ObservableGoods 属性值的改变都会立即触发 UI 刷新,概念上与 Observable 区别不大,具体效果可看下面提供的源代码,这里不再赘述
2.3、ObservableCollection
DataBinding 也提供了包装类用于替代原生的 List
和 Map
,分别是 ObservableList
和 ObservableMap
,当其包含的数据发生变化时,绑定的视图也会随之进行刷新
private ObservableMap<String, String> map;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMain5Binding binding = DataBindingUtil.setContentView(this, R.layout.activity_main5);
map = new ObservableArrayMap<>();
map.put(“name”, “leavesC”);
map.put(“age”, “24”);
binding.setMap(map);
ObservableList list = new ObservableArrayList<>();
list.add(“Ye”);
list.add(“leavesC”);
binding.setList(list);
binding.setIndex(0);
binding.setKey(“name”);
}
public void onClick(View view) {
map.put(“name”, “leavesC,hi” + new Random().nextInt(100));
}
[图片上传失败…(image-44a9b4-1600669391023)]
三、双向数据绑定
双向绑定的意思即为当数据改变时同时使视图刷新,而视图改变时也可以同时改变数据
看以下例子,当 EditText 的输入内容改变时,会同时同步到变量 goods
,绑定变量的方式比单向绑定多了一个等号:android:text="@={goods.name}"
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMain2Binding binding = DataBindingUtil.setContentView(this, R.layout.activity_main2);
ObservableGoods goods = new ObservableGoods(“code”, “hi”, 23);
binding.setGoods(goods);
}
[图片上传失败…(image-120bfd-1600669391023)]
四、事件绑定
严格意义上来说,事件绑定也是一种变量绑定,只不过设置的变量是回调接口而已
事件绑定可用于以下多种回调事件
- android:onClick
- android:onLongClick
- android:afterTextChanged
- android:onTextChanged
- …
在 Activity 内部新建一个 UserPresenter 类来声明 onClick() 和 afterTextChanged() 事件相应的回调方法
public class UserPresenter {
public void onUserNameClick(User user) {
Toast.makeText(Main6Activity.this, “用户名:” + user.getName(), Toast.LENGTH_SHORT).show();
}
public void afterTextChanged(Editable s) {
user.setName(s.toString());
binding.setUserInfo(user);
}
public void afterUserPasswordChanged(Editable s) {
user.setPassword(s.toString());
binding.setUserInfo(user);
}
}
<?xml version="1.0" encoding="utf-8"?>
方法引用的方式与调用函数的方式类似,既可以选择保持事件回调方法的签名一致:@{userPresenter.afterTextChanged},此时方法名可以不一样,但方法参数和返回值必须和原始的回调函数保持一致。也可以引用不遵循默认签名的函数:@{()->userPresenter.onUserNameClick(userInfo)},这里用到了 Lambda 表达式,这样就可以不遵循默认的方法签名,将userInfo
对象直接传回点击方法中。此外,也可以使用方法引用 :: 的形式来进行事件绑定
[图片上传失败…(image-793b04-1600669391023)]
五、使用类方法
首先定义一个静态方法
public class StringUtils {
public static String toUpperCase(String str) {
return str.toUpperCase();
}
}
在 data 标签中导入该工具类
然后就可以像对待一般的函数一样来调用了
六、运算符
6.1、基础运算符
DataBinding 支持在布局文件中使用以下运算符、表达式和关键字
- 算术 + - / * %
- 字符串合并 +
- 逻辑 && ||
- 二元 & | ^
- 一元 + - ! ~
- 移位 >> >>> <<
- 比较 == > < >= <=
- Instanceof
- Grouping ()
- character, String, numeric, null
- Cast
- 方法调用
- Field 访问
- Array 访问 []
- 三元 ?:
目前不支持以下操作
- this
- super
- new
- 显示泛型调用
此外,DataBinding 还支持以下几种形式的调用
6.2、Null Coalescing
空合并运算符 ?? 会取第一个不为 null 的值作为返回值
等价于
android:text=“@{user.name != null ? user.name : user.password}”
6.3、属性控制
可以通过变量值来控制 View 的属性
6.4、避免空指针异常
DataBinding 也会自动帮助我们避免空指针异常
例如,如果 “@{userInfo.password}” 中 userInfo 为 null 的话,userInfo.password 会被赋值为默认值 null,而不会抛出空指针异常
七、include 和 viewStub
7.1、include
对于 include 的布局文件,一样是支持通过 dataBinding 来进行数据绑定,此时一样需要在待 include 的布局中依然使用 layout 标签,声明需要使用到的变量
view_include.xml
<android.support.constraint.ConstraintLayout
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:background=“#acc”>
</android.support.constraint.ConstraintLayout>
在主布局文件中将相应的变量传递给 include 布局,从而使两个布局文件之间共享同一个变量
<?xml version="1.0" encoding="utf-8"?>7.2、viewStub
dataBinding 一样支持 ViewStub 布局
在布局文件中引用 viewStub 布局
获取到 ViewStub 对象,由此就可以来控制 ViewStub 的可见性
ActivityMain7Binding binding = DataBindingUtil.setContentView(this, R.layout.activity_main7);
View view = binding.viewStub.getViewStub().inflate();
如果需要为 ViewStub 绑定变量值,则 ViewStub 文件一样要使用 layout 标签进行布局,主布局文件使用自定义的 bind 命名空间将变量传递给 ViewStub
如果在 xml 中没有使用 bind:userInfo="@{userInf}"
对 ViewStub 进行数据绑定,则可以等到当 ViewStub Inflate 时再绑定变量,此时需要为 ViewStub 设置 setOnInflateListener
回调函数,在回调函数中进行数据绑定
binding.viewStub.setOnInflateListener(new ViewStub.OnInflateListener() {
@Override
public void onInflate(ViewStub stub, View inflated) {
//如果在 xml 中没有使用 bind:userInfo=“@{userInf}” 对 viewStub 进行数据绑定
//那么可以在此处进行手动绑定
ViewStubBinding viewStubBinding = DataBindingUtil.bind(inflated);
viewStubBinding.setUserInfo(user);
Log.e(TAG, “onInflate”);
}
});
八、BindingAdapter
dataBinding 提供了 BindingAdapter 这个注解用于支持自定义属性,或者是修改原有属性。注解值可以是已有的 xml 属性,例如 android:src
、android:text
等,也可以自定义属性然后在 xml 中使用
例如,对于一个 ImageView ,我们希望在某个变量值发生变化时,可以动态改变显示的图片,此时就可以通过 BindingAdapter 来实现
需要先定义一个静态方法,为之添加 BindingAdapter 注解,注解值是为 ImageView 控件自定义的属性名,而该静态方法的两个参数可以这样来理解:当 ImageView 控件的 url 属性值发生变化时,dataBinding 就会将 ImageView 实例以及新的 url 值传递给 loadImage() 方法,从而可以在此动态改变 ImageView 的相关属性
@BindingAdapter({“url”})
public static void loadImage(ImageView view, String url) {
Log.e(TAG, "loadImage url : " + url);
}
在 xml 文件中关联变量值,当中,bind 这个名称可以自定义
<?xml version="1.0" encoding="utf-8"?><android.support.constraint.ConstraintLayout
android:layout_width=“match_parent”
android:layout_height=“match_parent”>
</android.support.constraint.ConstraintLayout>
BindingAdapter 更为强大的一点是可以覆盖 Android 原先的控件属性。例如,可以设定每一个 Button 的文本都要加上后缀:“-Button”
@BindingAdapter(“android:text”)
public static void setText(Button view, String text) {
view.setText(text + “-Button”);
}
这样,整个工程中使用到了 “android:text” 这个属性的控件,其显示的文本就会多出一个后缀
[图片上传失败…(image-da8bbb-1600669391023)]
九、BindingConversion
dataBinding 还支持对数据进行转换,或者进行类型转换
与 BindingAdapter 类似,以下方法会将布局文件中所有以@{String}
方式引用到的String
类型变量加上后缀-conversionString
@BindingConversion
public static String conversionString(String text) {
return text + “-conversionString”;
}
xml 文件
[图片上传失败…(image-5c68cd-1600669391023)]
可以看到,对于 Button 来说,BindingAdapter 和 BindingConversion 同时生效了,而 BindingConversion 的优先级要高些
此外,BindingConversion 也可以用于转换属性值的类型
看以下布局,此处在向 background
和 textColor
两个属性赋值时,直接就使用了字符串,按正常情况来说这自然是会报错的,但有了 BindingConversion 后就可以自动将字符串类型的值转为需要的 Drawable
和 Color
了
@BindingConversion
public static Drawable convertStringToDrawable(String str) {
if (str.equals(“红色”)) {
return new ColorDrawable(Color.parseColor(“#FF4081”));
}
if (str.equals(“蓝色”)) {
return new ColorDrawable(Color.parseColor(“#3F51B5”));
}
return new ColorDrawable(Color.parseColor(“#344567”));
}
@BindingConversion
public static int convertStringToColor(String str) {
if (str.equals(“红色”)) {
return Color.parseColor(“#FF4081”);
}
if (str.equals(“蓝色”)) {
return Color.parseColor(“#3F51B5”);
}
return Color.parseColor(“#344567”);
}
[图片上传失败…(image-c153d-1600669391023)]
十、Array、List、Set、Map …
dataBinding 也支持在布局文件中使用 数组、Lsit、Set 和 Map,且在布局文件中都可以通过 list[index]
的形式来获取元素
而为了和 variable 标签的尖括号区分开,在声明 Lsit 之类的数据类型时,需要使用尖括号的转义字符
<?xml version="1.0" encoding="utf-8"?><TextView
···
android:text=“@{array[1]}” />
<TextView
···
android:text=“@{sparse[index]}” />
<TextView
···
android:text=“@{list[index]}” />
<TextView
···
android:text=“@{map[key]}” />
<TextView
···
android:text=‘@{map[“leavesC”]}’ />
<TextView
···
android:text=‘@{set.contains(“xxx”)?“xxx”:key}’ />
十一、资源引用
dataBinding 支持对尺寸和字符串这类资源的访问
dimens.xml
190dp
150dp
strings.xml
%s is %s
十二、与 RecyclerView 搭配使用
dataBinding 与 RecyclerView 搭配使用的话可以让代码更加简洁明了
先声明需要的 item 布局文件
<?xml version="1.0" encoding="utf-8"?>对应的 RecyclerView.Adapter
public class UserAdapter extends RecyclerView.Adapter<UserAdapter.UserAdapterHolder> {
private List userList;
public UserAdapter(List userList) {
this.userList = userList;
}
@NonNull
@Override
public UserAdapterHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemUserBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.item_user, parent, false);
return new UserAdapterHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull UserAdapterHolder holder, int position) {
holder.getBinding().setUser(userList.get(position));
}
@Override
public int getItemCount() {
if (userList == null) {
return 0;
}
return userList.size();
}
class UserAdapterHolder extends RecyclerView.ViewHolder {
private ItemUserBinding binding;
UserAdapterHolder(ItemUserBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
public ItemUserBinding getBinding() {
return binding;
}
}
}
十三、RecyclerView Adapter 高效率刷新
前文讲到了 ObservableList ,此处就可以通过 ObservableList 的实现类 ObservableArrayList 来实现 RecyclerView Adapter 的高效刷新,而不是每次都是直接 notifyDataSetChanged
可以先看下 ObservableArrayList 的源码,可以发现在每次增删改数据时,都会触发到 ListChangeRegistry 内的 OnListChangedCallback 回调,且 OnListChangedCallback 是把每次改动到的数据位置都给透传到外部,我们可以通过这些信息来只刷新 Adapter 的特定位置,从而实现高效刷新,并且获得一些动画效果
public class ObservableArrayList extends ArrayList implements ObservableList {
private transient ListChangeRegistry mListeners = new ListChangeRegistry();
···
@Override
public boolean add(T object) {
super.add(object);
notifyAdd(size() - 1, 1);
return true;
}
@Override
public void clear() {
int oldSize = size();
super.clear();
if (oldSize != 0) {
notifyRemove(0, oldSize);
}
}
@Override
public T remove(int index) {
T val = super.remove(index);
notifyRemove(index, 1);
return val;
}
@Override
public T set(int index, T object) {
T val = super.set(index, object);
if (mListeners != null) {
mListeners.notifyChanged(this, index, 1);
}
return val;
}
private void notifyAdd(int start, int count) {
if (mListeners != null) {
mListeners.notifyInserted(this, start, count);
}
}
private void notifyRemove(int start, int count) {
if (mListeners != null) {
mListeners.notifyRemoved(this, start, count);
}
}
···
}
此处通过 DynamicChangeCallback 来实现对 Adapter 的刷新操作
public class DynamicChangeCallback extends ObservableList.OnListChangedCallback<ObservableList> {
private RecyclerView.Adapter adapter;
public DynamicChangeCallback(RecyclerView.Adapter adapter) {
this.adapter = adapter;
}
@Override
public void onChanged(ObservableList sender) {
adapter.notifyDataSetChanged();
}
@Override
public void onItemRangeChanged(ObservableList sender, int positionStart, int itemCount) {
adapter.notifyItemRangeChanged(positionStart, itemCount);
}
@Override
public void onItemRangeInserted(ObservableList sender, int positionStart, int itemCount) {
adapter.notifyItemRangeInserted(positionStart, itemCount);
}
@Override
public void onItemRangeMoved(ObservableList sender, int fromPosition, int toPosition, int itemCount) {
adapter.notifyItemRangeRemoved(fromPosition, itemCount);
adapter.notifyItemRangeInserted(toPosition, itemCount);
尾声
最后,我再重复一次,如果你想成为一个优秀的 Android 开发人员,请集中精力,对基础和重要的事情做深度研究。
对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。
最后想要拿高薪实现技术提升薪水得到质的飞跃。最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。
当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。
进阶学习视频
附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!
erride
public void onItemRangeMoved(ObservableList sender, int fromPosition, int toPosition, int itemCount) {
adapter.notifyItemRangeRemoved(fromPosition, itemCount);
adapter.notifyItemRangeInserted(toPosition, itemCount);
尾声
最后,我再重复一次,如果你想成为一个优秀的 Android 开发人员,请集中精力,对基础和重要的事情做深度研究。
对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。
最后想要拿高薪实现技术提升薪水得到质的飞跃。最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。
当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。
进阶学习视频
[外链图片转存中…(img-FfVrDox0-1715582814679)]
附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
[外链图片转存中…(img-rr0AxZYz-1715582814683)]
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!