内容简介:又到周末好时光,开始嗨之前再抽点时间看看本文,阅读本文入门需要5分钟,进阶需要20分钟,掌握需要30分钟,运用需要时间就有点长了......能看到最后的都是大佬,收下我的膜拜。本文技术内容讲的是关于Data Binding Library的那点事,有的同学可能解过了,有的娃可能都不知道是什么东东...。为了不落伍,和大家一样优秀,决定写Jetpack方面的文章。与别人不一样的是,会加入自己的理解和栗子,而不是简单的翻译(我英文水平也不行)。如果大家发现有误的地方,希望多加指点,在此谢过。一年前有缘看了一下J
又到周末好时光,开始嗨之前再抽点时间看看本文,阅读本文入门需要5分钟,进阶需要20分钟,掌握需要30分钟,运用需要时间就有点长了......能看到最后的都是大佬,收下我的膜拜。本文技术内容讲的是关于Data Binding Library的那点事,有的同学可能解过了,有的娃可能都不知道是什么东东...。为了不落伍,和大家一样优秀,决定写Jetpack方面的文章。与别人不一样的是,会加入自己的理解和栗子,而不是简单的翻译(我英文水平也不行)。如果大家发现有误的地方,希望多加指点,在此谢过。
About Jetpack
一年前有缘看了一下Jetpack,但并没有过多的去关注,最近在看Google IO 2019相关资料,看到了Jetpack的身影,不得不陷入深思,无法自拔。
JetPack的官方说法:
Jetpack 是 Android 软件组件的集合,使您可以更轻松地开发出色的 Android 应用。这些组件可帮助您遵循最佳做法、让您摆脱编写样板代码的工作并简化复杂任务,以便您将精力集中放在所需的代码上。
总结性
- 加速开发:以组件的形式供我们依赖使用。
- 消除样板代码:还记得在
Activity
中一大堆findViewById
么?能做的不止这么多。 - 构建高质量应用:现在化设计、避开bug、向后兼容。
Android Jetpack 组件是库的集合,这些库是为协同工作而构建的,不过也可以单独采用,同时利用 Kotlin 语言功能帮助提高工作效率。可全部使用,也可混合搭配!
以上是对官网的摘录。作为开山之篇,先从架构方向的 数据绑定库 入门开始,让同学感受它的魅力。
Data Binding Library(数据绑定库)
借助数据绑定库(Data Binding Library),可以使用声明性格式(而非程序化地)将布局中的界面组件绑定到应用中的数据源。数据绑定库要求在Android 4.0以上,Gradle 1.5.0以上。实践证明Android SDK和Gradle版本越高,对Data Binding的支持越好,越简单,速度越快。
举个栗子,这个栗子不重,两只手指可以举起来:
findViewById<TextView>(R.id.sample_text).apply { text = viewModel.userName } 复制代码
栗子中通过findViewById找到TextView组件,并将其绑定到 viewModel 变量的 userName 属性。而下面在布局文件中使用数据绑定库将文本直接分配到TextView组件上,这样就无需调用上述任何 Java 代码。
<TextView android:text="@{viewmodel.userName}" /> 复制代码
竟然这么好用,为啥不了解看看呢?
配置
在我们的项目 build.gradle
文件下配置如下代码。
android { ... dataBinding { enabled = true } } 复制代码
如果Gradle插件版本在 3.1.0-alpha06
以上,可以使用新的Data Binding编译器,有利于加速绑定数据文件的生成。在项目的 gradle.properties
文件添加如下配置。
android.databinding.enableV2=true 复制代码
同步一下,没什么问题的话,配置已经成功了~
入门
- 定义一个数据对象
data class User(var name: String, var age: Int) 复制代码
- 布局绑定
我们创建名为activity_main.xml的布局文件,内容如下:
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="user" type="com.gitcode.jetpack.User"/> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent"> //在TextView中使用 <TextView android:layout_width="match_parent" android:gravity="center" android:text="@{user.name}" android:layout_height="match_parent"/> </LinearLayout> </layout> 复制代码
布局文件的根元素不再是以往的LinearLayout、RelativeLayout等等,而是layout。在 data
元素内添加 variable
,其属性表示声明一个 com.gitcode.jetpack.User
类型的变量 user
。如果多个变量的话,可在 data
元素下添加多个 varialbe
元素,格式是一致的。
<data> <variable name="user" type="com.gitcode.jetpack.User"/> <variable name="time" type="com.gitcode.jetpack.Time"/> </data> 复制代码
在 @{}
语法中使用表达式将变量赋值给view的属性。例如:这里将user变量的firstName属性赋值给TextView的text属性。
android:text="@{user.firstName}" 复制代码
- 绑定数据
此时布局声明的user变量值还是初始值,我们需要为其绑定数据。
默认情况下,会根据目前布局文件名称来生成一个绑定类(binding class),例如当前布局文件名是activity_main,那么生成的类名就是ActivityMainBinding。
绑定类会拥有当前布局声明变量,并声明getter或者setter方法,也就是说ActivityMainBinding类会带有user属性和getUser、setUser方法,变量的默认初始化与Java一致:引用类型为null,int为0,bool为false。
在MainActivity的onCreate()方法中添加如下代码,将数据绑定到布局上。
val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main); binding.user = User("GitCode", 3) 复制代码
经典代码是这样的:
setContentView(R.layout.activity_main) val user=User("GitCode",3) val tvName=findViewById<TextView>(R.id.tvName) tvName.text = user.name 复制代码
可有看出,使用数据绑定库会使代码简洁很多,可读性也很高。 运行一下项目,既可以考到效果了~
如果是在Fragment、Adapter中使用,那就要换个姿势了。
val listItemBinding = ListItemBinding .inflate(layoutInflater, viewGroup, false) //或者 val listItemBinding = DataBindingUtil .inflate(layoutInflater, R.layout.list_item, viewGroup, false) 复制代码
恭喜,你已经入门了
可以选择继续学习,
看下文
也可以当做了解
点个赞
看看其他文章了~
布局与绑定表达式
在一开始介绍Data Binding Libaray时,就使用了 @{}
语法,花括号里面的内容称为绑定表达式,绑定表达式其实并不复杂,跟我们正常使用Java和Kotlin语言的表达式没多大区别。那我们可以在表达式中使用什么类型的运算符或者关键字呢?
常用运算符
运算符 | 符号 |
---|---|
算术 | 加、减、乘、除、求余(+ 、 - 、* 、/、 %) |
逻辑 | 与、或(&&、||) |
一元 | + 、-、 !、 ~ |
移位 | >>、 >>>、 << |
关系 | == 、> 、<、 >= 、<=(使用符号<时,要换成<) |
其他常用的
同时也支持字符拼接 +
, instanceof
,分组、属性访问、数组访问、 ?:
、转型、访问调用,基本类型等等等。 也就是说,绑定表达式语言大多数跟宿主代码(Java or Kotlin)的表达式差不多。为什么说是大多数,因为不能使用 this
、 super
、 new
和 Explicit generic invocation
(明确的泛型调用)等。
丢个栗子:
android:text="@{String.valueOf(index + 1)}" android:visibility="@{age > 13 ? View.GONE : View.VISIBLE}" android:transitionName='@{"image_" + id}' 复制代码
再举丢个栗子:
android:text="@{user.displayName ?? user.lastName}" 复制代码
如果 user.displayName
不为null则使用,否则使用 user.lastName
.在这里也看得出,可以通过表达式访问类的属性。绑定类会自动检查当前变量是否为null,以避免发生空指针异常。栗子:如果 user
变量为null,那么 user.lastName
也会是null。
集合
像数组,链表,Maps等常见的集合,都可以采用下标 []
访问它们的元素。
<data> <import type="android.util.SparseArray"/> <import type="java.util.Map"/> <import type="java.util.List"/> <variable name="list" type="List<String>"/> <variable name="sparse" type="SparseArray<String>"/> <variable name="map" type="Map<String, String>"/> <variable name="index" type="int"/> <variable name="key" type="String"/> </data> … android:text="@{list[index]}" … android:text="@{sparse[index]}" … android:text="@{map[key]}" //或者 android:text="@{map.key}" 复制代码
注意在 data
元素内添加了 import
元素,表示导入该类型的定义,这样表达式中引用属性可读性高点,使用也方便。
来个容易掰的栗子:
<data> <import type="android.view.View"/> </data> <TextView android:text="@{user.lastName}" android:layout_width="wrap_content" android:layout_height="wrap_content" android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/> 复制代码
通过导入View类型,就可以使用相关属性,例如这里的 View.VISIBLE
。
有时导入的类全名太长了或者存在相同类型的类名,我们就可以给它取个别名,然后就可用别名进行coding~
<import type="android.view.View"/> <import type="com.gitcode.jetpack.View" alias="JView"/> 复制代码
使用资源
使用下面语法:
android:padding="@{@dimen/largePadding}" 复制代码
相关资源的的表达式引用,贴张官网截图:
事件处理
数据绑定库允许我们在事件到View时候通过表达式去处理它。 在数据绑定库中支持两种机制:方法调用和监听器绑定。
好想一笔带过,因为原文看不明白~~~~(>_<)~~~~ 复制代码
方法调用
点击事件会直接绑定到处理方法上,当一个事件发生,会直接传给绑定的方法。类似我们在布局上使用 android:onclick
与Activity 的方法绑定。在编译的时候已经绑定,在 @{}
表达式中的方法如果在Activity找不到或者方法名错误,就会在编译时期报错,方法签名(返回类型和参数相同)一致。
丢个栗子:
定义一个接口,用于处理事件。
//定义一个处理点击事件的类 interface MethodHandler { fun onClick(view: View) } 复制代码
在布局声明了methodHandler变量,并在Button的 onClick
方法使用表达式 @{methodHandler::onClick}
, onClick
方法需要与上面接口一致,不然编译器期报错。
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> ... <variable name="methodHandler" type="com.gitcode.jetpack.MethodHandler"/> </data> <LinearLayout android:layout_width="match_parent" android:orientation="vertical" android:gravity="center_horizontal" android:layout_height="match_parent"> ... <Button android:layout_width="wrap_content" android:text="Method references" android:layout_marginTop="10dp" android:onClick="@{methodHandler::onClick}" android:layout_height="wrap_content"/> </LinearLayout> </layout> 复制代码
然后在Activity中实现MethodHandler,并赋值给绑定类的变量。
class MainActivity : AppCompatActivity(), MethodHandler{ lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { ... binding.methodHandler = this } override fun onClick(view: View) { Log.i(TAG, "Method handling") } } 复制代码
因此,当我们点击Button的时候,Activity的onClick方法就会被回调。
监听器绑定
监听器绑定与方法调用不同的是,监听器不再编译器与处理方法绑定,而是在点击事件传递到当前view时,才与处理方法绑定,而且监听器并不要表达式方法名与处理方法同名,只要返回类型一致即可,如果有返回值得话。
来个栗子:
- 定义接口用于处理事件
interface ListenerHandler { fun onClickListener(view: View) } 复制代码
- 在布局中定义变量和表达式
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="listener" type="com.gitcode.jetpack.ListenerHandler"/> </data> <Button android:layout_width="wrap_content" android:text="Listener" android:layout_marginTop="10dp" android:onClick="@{(view)->listener.onClickListener(view)}" android:layout_height="wrap_content"/> </LinearLayout> <layout> 复制代码
注意到使用lambda表达式,因此可以在 @{}
内做更多操作,如预处理数据等。
- 处理方法 同样在Activity实现ListenerHandler方法,并赋值给绑定类的变量。
class MainActivity : AppCompatActivity(), ListenerHandler { lateinit var binding: ActivityMainBinding override fun onClickListener(view: View) { Log.i(TAG, "Listener handling") } override fun onCreate(savedInstanceState: Bundle?) { ... binding.listener=this } } 复制代码
点击Button,就能看到onClickListener回调了~
不过瘾的,看官网吧
好了,讲到这里,大家喝杯奶茶续命,休息会吧~
吃完瓜了没?吃完了就该继续撸文了,毕竟革命尚未成功~
绑定类
前面讲的大多数是在布局中去使用表达式,从这开始,讲点代码中的操作。在一开始入门时候,讲到会根据当前布局生成绑定类,绑定类类名由布局名称根据Pascal规则和添加Binding后缀生成。举个栗子就明白了,当前布局名称:activity_shared.xml。生成绑定类名称:ActivitySharedBinding。
那么绑定类的作用是什么?
绑定类是数据绑定库为让我们可以 访问布局中的变量和视图 而生成的类。
如何创建或者定制绑定类呢?
创建绑定类
- 使用静态inflate()方法
ActivityMainBinding.inflate(LayoutInflater.from(this)) 复制代码
重载版本
ActivityMainBinding.inflate(getLayoutInflater(), viewGroup, false) 复制代码
- 使用静态bind()方法
//一般这种情况是布局有作其他用途 ActivityMainBinding.bind(viewRoot) 复制代码
- 在Fragment,ListView,或RecyclerView的adapter使用
val listItemBinding = ListItemBinding.inflate(layoutInflater, viewGroup, false) // 或者 val listItemBinding = DataBindingUtil .inflate(layoutInflater, R.layout.list_item, viewGroup, false) 复制代码
定制绑定类
通过修改 data
元素的class属于达到定制不同名称的绑定类,和其所存储位置。
//生成绑定类名为:ContactItem,存放在当前组件的绑定类包中 <data class="ContactItem"> … </data> //生成绑定类名为:ContactItem,存放在当前组件包中 <data class=".ContactItem"> … </data> //生成绑定类名为:ContactItem,存放在com.gitcode包中 <data class="com.gitcode.ContactItem"> … </data> 复制代码
访问Views
如果需要访问布局中Views,需要给Views添加id,数据绑定库会尽快通过findViewById去绑定。并在Activity中通过绑定类使用。例如:
binding.tvName.text="GitCode" 复制代码
访问变量
数据绑定库会为在布局中声明的变量在绑定类中生成setter和getter。例如:
binding.user=User("GitCode",3) 复制代码
绑定类官网
绑定适配器
每个布局表达式都对应着一个绑定适配器,用于进行设置相应属性或监听器所需的框架调用.通俗点说,我们通过调用什么方法去给属性赋值?我们在代码通过setText()方法给view的text属性赋值。讲的就是下面的代码:
binding.tvAge.text="20" //通过tvAge的setText()给TextView的android:text属性赋值 复制代码
好像跟我们平常调用的没什么区别:
tvAge.text="20" 复制代码
这里讲的就是这个,当数据变化时,我们调用合适的方法(例如setText方法),去给view的属性赋值(例如android:text的text属性)。还不懂的话,继续看~
给View的属性赋值
数据绑定库提供三种方式让我们去给View的属性赋值:库自己决定选择调用方法;明确指定调用方法;自定义调用逻辑方法。
库自动选择
假如View有个属性color,库会尝试去查找setColor(args)方法,参数args的类型需要和表达式的返回类型一致。例如 android:color=@{"black"}
,因为 "black"
是字符串类型,所以args的参数类型就是String。命名空间 android
并没有作强制要求,也可以是 gitcode:color=@{"black"}
。库查找方法的标准是 setXXX()
方法名和参数类型,这里的 XXX
是指属性名。
明确指定
虽然库自动选择已经很智能了,但有时view的属性和方法名并不一致,这是就需要我们明确指定,避免库自动选择找不到。例如ImageView的 android:tint
属性是关联到 setImageTintList(ColorStateList)
方法,而不是 setTint()
,这时,就需要明确指定了。
@BindingMethods(value = [ BindingMethod( type = android.widget.ImageView::class, attribute = "android:tint", method = "setImageTintList")]) 复制代码
BindingMethods是注解在类上的,例如Activity。可以包含一个到多个BindingMethod注解。BindingMethod中type表示当前方法(method)匹配到到哪个View的属性(attribute)上。
定制逻辑方法
虽然上面两者已经满足了大多数情况,但一些特殊情况还是需要自己处理逻辑的。例如,view的 android:paddingLeft
属性,没有 setPaddingLeft(int)
方法,但提供了 setPadding(left, top, right, bottom)
方法。这时候就需要我们自定义逻辑了。
@BindingAdapter("android:paddingLeft") fun setPaddingLeft(view: View, padding: Int) { view.setPadding(padding, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()) } 复制代码
BindingAdapter注解允许定制属性的setter逻辑。setPaddingLeft方法的第一个参数必须是我们要处理属性的逻辑的View,后面的参数是根据BindingAdapter注解的属性来定位的。例如这里BindingAdapter注解只声明了 android:paddingLeft
属性,那么参数padding就是paddigLeft对应的值。设置多个属性是这样子的:
@BindingAdapter("imageUrl", "error") fun loadImage(view: ImageView, url: String, error: Drawable) { Picasso.get().load(url).error(error).into(view) } <ImageView app:imageUrl="@{venue.imageUrl}" app:error="@{@drawable/venueError}" /> 复制代码
从这里可以看出,库对命名空间并没有作要求。注解的值imageUrl和error类型必须对应方法参数url和error的类型String和Drawable,只有ImageView同时匹配到两个属性,上述方法才会生效。为此,可以通过设置 requireAll = false
,匹配一个值也会生效。
@BindingAdapter(value = ["imageUrl", "placeholder"], requireAll = false) fun setImageUrl(imageView: ImageView, url: String, placeHolder: Drawable) { if (url == null) { imageView.setImageDrawable(placeholder); } else { MyImageLoader.loadInto(imageView, url, placeholder); } } 复制代码
类型转换
在绑定表达式返回一个对象时,库会选择一个方法来设置属性的值,而该对象会转型为方法参数的类型。这种机制可以方便使用ObservableMap来存储数据。
<TextView android:text='@{userMap["lastName"]}' android:layout_width="wrap_content" android:layout_height="wrap_content" /> 复制代码
绑定表达式的 userMap["lastName"]
会返回值,该值会查找setText(CharSequence) 方法中自动转型为字符串并设置给TextView的text属性。但参数类型不确定的时候,就需要进行强制类型转换了,以表明类型。
有时候,绑定表达式返回的类型与设置属性方法的参数类型并不一致。例如: android:background
属性期待的是Drawable(setBackground(drawable),但设置color值时确实一个Int。
<View android:background="@{@color/red}" android:layout_width="wrap_content" android:layout_height="wrap_content"/> 复制代码
这时候我们需要使用BindingConversion注解将返回值类型Int转换成期待的类型Drawable。
@BindingConversion fun convertColorToDrawable(color: Int) = ColorDrawable(color) 复制代码
总结
写本文的时候,参考官网,看英文文档,对一个英语刚过四级的人...词我都认识,但组成句子,我就一脸懵逼了...
写到一半的时候,想放弃,或者想一笔带过...但,说过,要打造高质量文章,和对读者负责,所以熬了几个夜...夜太黑,没人担心明天会不会后悔~
看了一下别人的文章,基本都是支持参考官网翻译的,并没有加入个人理解和筛选。而本文是在多次参考阅读官网文章之下加入个人理解,让本文更加通俗易懂,更清晰表达官网的意图。
能看到结尾的同学也是很牛逼,需要很大的耐心,给你点个:+1:。那能不能举个爪,让我看看你们的:open_hands:。
Data Binding还有其他知识点,我发现的英语水平已经不够用,大家可以看看原汁原味的官网,或者等到后面我再把它写完...
Data Binding Library可能需要翻墙,不会翻墙找我...
要不要点个赞支持一下呢? 必须点,支持老铁写更多优质文章
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Letting Go of the Words
Janice (Ginny) Redish / Morgan Kaufmann / 2007-06-11 / USD 49.95
"Redish has done her homework and created a thorough overview of the issues in writing for the Web. Ironically, I must recommend that you read her every word so that you can find out why your customer......一起来看看 《Letting Go of the Words》 这本书的介绍吧!
URL 编码/解码
URL 编码/解码
XML、JSON 在线转换
在线XML、JSON转换工具