Android Jetpack(2):DataBinding的基本使用
DataBinding 介绍
DataBinding 是谷歌官方发布的一个框架,顾名思义即为数据绑定,是 MVVM 模式在 Android 上的一种实现,用于降低布局和逻辑的耦合性,使代码逻辑更加清晰。MVVM 相对于 MVP,其实就是将 Presenter 层替换成了 ViewModel 层。DataBinding 能够省去我们一直以来的 findViewById() 步骤,大量减少 Activity 内的代码,数据能够单向或双向绑定到 layout 文件中,有助于防止内存泄漏,而且能自动进行空检测以避免空指针异常
DataBinding入门
1. 启用 DataBinding
启用 DataBinding 的方法是在对应 Model 的 build.gradle 文件里加入以下代码,同步后就能引入对 DataBinding 的支持
android {
dataBinding {
enabled = true
}
}
就是这么简单,一个简单的databinding配置之后,就可以开始使用数据绑定了。
2. 布局文件:生成 DataBinding 需要的布局规则
我们来看看布局文件该怎么写,首先布局文件不再是以传统的某一个容器作为根节点,而是使用layout作为根节点,在layout节点中我们可以通过data节点来引入我们要使用的数据源。
打开布局文件,选中根布局的 ViewGroup,按住 Alt + 回车键,点击 “Convert to data binding layout”,就可以生成 DataBinding 需要的布局规则。
<?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"
xmlns:tools="http://schemas.android.com/tools">
<data>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
和原始布局的区别在于多出了一个 layout 标签将原布局包裹了起来,data 标签用于声明要用到的变量以及变量类型,要实现 MVVM 的 ViewModel 就需要把数据(Model)与 UI(View)进行绑定,data 标签的作用就像一个桥梁搭建了 View 和 Model 之间的通道。
3. 定义一个实体类
要使用数据绑定,我们得首先创建一个实体类:
data class User(val name:String,val age:Int)
4. 在布局文件的data 标签里声明要使用到的变量名、类的全路径,设置数据
在data中定义的variable节点,name属性表示变量的名称,type表示这个变量的类型,实例就是我们实体类的类的全路径。
这里声明了一个 User 类型的变量 user,我们要做的就是使这个变量与TextView 控件挂钩,通过设置 user的变量值同时使 TextView 显示相应的文本。
通过 @{user.name} 使 TextView 引用到相关的变量,DataBinding 会将之映射到相应的 getter 方法。
<?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"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="user"
type="com.example.jetpack.bean.User" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/tvName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="@{`名字`+user.name}"
android:textSize="16sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!--注意:这里age是int类型,必须转化为String,否则会运行时异常-->
<TextView
android:id="@+id/tvAge"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="@{String.valueOf(user.age)}"
android:textSize="16sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvName" />
<TextView
android:id="@+id/tvPhoneNum"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="@{user.phoneNum==null?user.phoneNum:`17817318877`}"
android:textSize="16sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvAge" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
5. Activity 中通过 DataBindingUtil 设置布局文件
Activity 中通过 DataBindingUtil 设置布局文件,省略原先 Activity 的 setContentView() 方法。
每个数据绑定布局文件都会生成一个绑定类,ViewDataBinding 的实例名是根据布局文件名来生成,将之改为首字母大写的驼峰命名法来命名,并省略布局文件名包含的下划线。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding=DataBindingUtil.setContentView<ActivityMainBinding>(this,R.layout.activity_main)
binding.user= User("赵丽颖",20,"17817318859")
}
}
一个简单的dataBinding案例就已经完成。
其他用法
在data中定义的variable节点,name属性表示变量的名称,type表示这个变量的类型,实例就是我们实体类的类的全路径。
import
如果 User 类型要多处用到,也可以直接将之 import 进来,这样就不用每次都指明整个包名路径了,而 java.lang.* 包中的类会被自动导入,所以可以直接使用
<data>
<import type="com.example.jetpack.bean.User"/>
<variable
name="user"
type="User" />
</data>
alias
先使用import节点将User导入,然后直接使用即可。但是如果这样的话又会有另外一个问题,假如我有两个类都是User,这两个UserBean分属于不同的包中,又该如何?这时候就要用到alias了。
在import节点中还有一个属性叫做alias,这个属性表示我可以给该类取一个别名,我给User这个实体类取一个别名叫做Lenve,这样我就可以在variable节点中直接写Lenve了。
<data>
<import type="com.example.jetpack.bean.User" alias="Lenve"/>
<variable
name="user"
type="Lenve" />
</data>
设置默认值
由于 TextView在布局文件中并没有明确的值,所以在预览视图中什么都不会显示,不便于观察文本的大小和字体颜色等属性,此时可以为之设定默认值(文本内容或者是字体大小等属性都适用),默认值将只在预览视图中显示,且默认值不能包含引号。
<TextView
android:id="@+id/tvName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="@{`名字`+user.name,default=morenzhi}"
android:textSize="16sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
自定义 ViewDataBinding 的实例名
可以通过如下方式自定义 ViewDataBinding 的实例名:
<data class="CustomBinding">
</data>
字符串拼接
如activity_main中如下属性
android:text="@{`名字`+user.name}"
三目运算
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="@{user.phone==null?user.phone:`147522444`}"
android:textSize="18sp" />
代码中使用控件
使用binding对象可获取布局文件中的各个对象,根据控件设置的id来获取。如
<Button
android:id="@+id/btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="按钮"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvPhoneNum" />
binding.btn.setOnClickListener {
Toast.makeText(this,"点击了按钮",Toast.LENGTH_SHORT).show()
}
BindingAdapter
dataBinding 提供了 BindingAdapter 这个注解用于支持自定义属性,或者是修改原有属性。注解值可以是已有的 xml 属性,例如 android:src、android:text等,也可以自定义属性然后在 xml 中使用。
对于一个 ImageView ,我们希望在某个变量值发生变化时,可以动态改变显示的图片,此时就可以通过 BindingAdapter 来实现。
通过 BindingAdapter 来实现加载图片
在java中使用:
在java中使用dataBinding展示图片很简单,只需要配置一个静态的BindingAdapter就可以了。
需要先定义一个静态方法,为之添加 BindingAdapter 注解,注解值是为 ImageView 控件自定义的属性名,而该静态方法的两个参数可以这样来理解:
当 ImageView 控件的 url 属性值发生变化时,dataBinding 就会将 ImageView 实例以及新的 url 值传递给 loadImage() 方法,从而可以在此动态改变 ImageView 的相关属性。
public class DataBindingUtils {
@BindingAdapter("imageUrl") //imageUrl:控件的属性名
public static void loadImage(ImageView imageView, String url) {
Glide.with(imageView.getContext())
.load(url)
.placeholder(R.mipmap.ic_launcher)
.error(R.mipmap.ic_launcher)
.into(imageView);
}
}
在kotlin中使用:
首先:kotlin中没有static关键字,但是提供了companion object{}代码块和使用object关键字。
object关键字声明一种特殊的类,这个类只有一个实例,因此看起来整个类就好像是一个对象一样,这里把类声明时的class关键字改成了object,这个类里面的成员默认都是static的。
@JvmStatic注解:与伴生对象搭配使用,将变量和函数声明为真正的JVM静态成员。
要加上kapt插件:
apply plugin: 'kotlin-kapt'
第一种方式:使用 companion object
class DataBindingUtils {
companion object {
@BindingAdapter("imageUrl")
@JvmStatic
fun loadImage(view: ImageView, url: String) {
Glide.with(view.context).load(url).into(view)
}
}
}
第二种方式:使用object
object DataBindingUtils {
@BindingAdapter("imageUrl")
@JvmStatic
fun loadImage(view: ImageView, url: String) {
Glide.with(view.context).load(url).into(view)
}
}
val url="https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3796319594,2802761532&fm=26&gp=0.jpg"
binding.user= User("赵丽颖",20,"17817318859",url)
在 xml 文件中关联变量值,当中, app 这个名称可以自定义:
<ImageView
android:id="@+id/ivNet"
android:layout_width="match_parent"
android:layout_height="300dp"
app:imageUrl="@{user.url}"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/btn" />
BindingAdapter 更为强大的一点是可以覆盖 Android 原先的控件属性
例如,可以设定每一个 Button 的文本都要加上后缀:“-Button”
@BindingAdapter("android:text")
public static void setText(TextView view, String text) {
view.setText(text + "赵丽颖");
}
<TextView
android:id="@+id/tvName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="@{`名字`+user.name,default=morenzhi}"
android:textSize="16sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
这样,整个工程中使用到了 “android:text” 这个属性的控件,其显示的文本就会多出一个后缀-赵丽颖。
可以用这个属性来加载本地图片:
public class ImageViewAdapter {
@BindingAdapter("android:src")
public static void setSrc(ImageView view, Bitmap bitmap) {
view.setImageBitmap(bitmap);
}
@BindingAdapter("android:src")
public static void setSrc(ImageView view, int resId) {
view.setImageResource(resId);
}
@BindingAdapter("imageUrl")
public static void setSrc(ImageView imageView, String url) {
Glide.with(imageView.getContext()).load(url)
.placeholder(R.mipmap.ic_launcher)
.into(imageView);
}
@BindingAdapter({"app:imageUrl", "app:placeHolder", "app:error"})
public static void loadImage(ImageView imageView, String url, Drawable holderDrawable, Drawable errorDrawable) {
Glide.with(imageView.getContext())
.load(url)
.placeholder(holderDrawable)
.error(errorDrawable)
.into(imageView);
}
}
Databinding 同样是支持在 Fragment
class DemoFrgament : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding = DataBindingUtil.inflate<FragmentDemoBinding>(
inflater,
R.layout.fragment_demo,
container,
false
)
return binding.root
}
}
DataBinding事件绑定
严格意义上来说,事件绑定也是一种变量绑定,只不过设置的变量是回调接口而已,事件绑定可用于以下多种回调事件:
- android:onClick
- android:onLongClick
- android:afterTextChanged
- android:onTextChanged
- …
Databinding事件绑定,分两种方式:方法引用和监听绑定,下面分别用案例介绍两种事件绑定的异同。
方式1:直接获取控件设置点击事件
binding.btn1.setOnClickListener {
Toast.makeText(this,"点击了按钮1",Toast.LENGTH_SHORT).show()
}
方式2:方法引用
传入OnClickListener的变量:
<variable
name="listener"
type="android.view.View.OnClickListener" />
在Button中给android:onClick设置listener的变量:
<Button
android:id="@+id/btn1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{listener}"
android:text="按钮"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvPhoneNum" />
<Button
android:id="@+id/btn2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{listener}"
android:text="按钮"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/btn1" />
通过setListener传入点击监听给listener对象:
binding.setListener {
when (it.id) {
R.id.btn1 -> Toast.makeText(this, "点击了按钮1", Toast.LENGTH_SHORT).show()
R.id.btn2 -> Toast.makeText(this, "点击了按钮2", Toast.LENGTH_SHORT).show()
}
}
方式3:方法引用
<variable
name="handlers"
type="com.example.jetpack.MainActivity" />
调用语法可以是@{handlers::onClickFriend}或者@{handlers.onClickFriend}:
<Button
android:id="@+id/btn1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{handlers::onClickFriend}"
android:text="按钮"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvPhoneNum" />
<Button
android:id="@+id/btn2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{handlers::onClickFriend}"
android:text="按钮"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/btn1" />
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding =
DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
val url =
"https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3796319594,2802761532&fm=26&gp=0.jpg"
binding.user = User("赵丽颖", 20, "17817318859", url)
binding.handlers = this
}
fun onClickFriend(view: View) {
when (view.id) {
R.id.btn1 -> Toast.makeText(this, "点击了按钮1", Toast.LENGTH_SHORT).show()
R.id.btn2 -> Toast.makeText(this, "点击了按钮2", Toast.LENGTH_SHORT).show()
}
}
}
方式4:方法引用
<data>
<variable
name="user"
type="com.example.jetpack.bean.User" />
<variable
name="handlers"
type="com.example.jetpack.MainActivity.MyClickHandlers" />
</data>
<Button
android:id="@+id/btn1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="改变name属性"
android:onClick="@{handlers.onClickChangeName}"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvPhoneNum" />
<Button
android:id="@+id/btn2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{handlers::onClickChangAage}"
android:text="改变age属性"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/btn1" />
在 Activity 内部新建一个类来声明 onClickChangeName() 和 onClickChangAage() 事件相应的回调方法:
class MainActivity : AppCompatActivity() {
lateinit var user: User
lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
val url =
"https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3796319594,2802761532&fm=26&gp=0.jpg"
user = User("赵丽颖", 20, "17817318859", url)
binding.user = user
binding.handlers = MyClickHandlers()
}
inner class MyClickHandlers {
fun onClickChangeName(v: View?) {
user.name = "赵丽颖2"
binding.user=user
}
fun onClickChangAage(v: View?) {
user.age = 18
binding.user=user
}
}
}
方式5:方法引用
把回调方法单独写到一个接口。
<variable
name="handlers"
type="com.example.jetpack.UserClickListener" />
<Button
android:id="@+id/btn1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="按钮"
android:onClick="@{handlers.userClicked}"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvPhoneNum" />
<Button
android:id="@+id/btn2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{handlers::userClicked}"
android:text="按钮"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/btn1" />
interface UserClickListener {
fun userClicked(view: View?)
}
class MainActivity : AppCompatActivity(), UserClickListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
val url =
"https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3796319594,2802761532&fm=26&gp=0.jpg"
val user = User("赵丽颖", 20, "17817318859", url)
binding.user = user
binding.handlers = this
}
override fun userClicked(view: View?) {
Toast.makeText(this, "方法引用",Toast.LENGTH_SHORT).show();
}
}
方式6:监听绑定(重要)
将对象直接传回点击方法中。
<variable
name="handlers"
type="com.example.jetpack.MainActivity.MyClickHandlers" />
<Button
android:id="@+id/btn1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{()->handlers.showUser(user)}"
android:text="按钮"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvPhoneNum" />
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding =
DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
val url =
"https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3796319594,2802761532&fm=26&gp=0.jpg"
var user = User("赵丽颖", 20, "17817318859", url)
binding.user = user
binding.handlers = MyClickHandlers()
}
inner class MyClickHandlers {
fun showUser(user: User) {
Toast.makeText(this@MainActivity, user.name, Toast.LENGTH_SHORT).show()
}
}
}
下一篇: 图片加载框架Glide的简单使用
推荐阅读
-
android开发教程之ubuntu使用adb连接小米2的步骤和adb调试方法
-
iOS开发中Quartz2D的基本使用方式举例
-
Jetpack之Lifecycle的基本使用
-
Android Jetpack-Navigation 使用中参数的传递方法
-
Android使用Service实现IPC通信的2种方式
-
如何使用Android ViewPager2(解决ViewPager库的问题)
-
Android ViewPager、ViewPager2的基本使用详解及区别
-
Android开发之广播的基本使用说明
-
Android 一份详细的Retrofit2.0基本使用总结
-
Android开发之基本组件的使用