乐趣区

关于android:JetpackData-Binding入门指南

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)的表达式差不多。为什么说是大多数,因为不能应用thissupernewExplicit 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&lt;String>"/>
    <variable name="sparse" type="SparseArray&lt;String>"/>
    <variable name="map" type="Map&lt;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)

本文转自 https://juejin.cn/post/6844903872520011784,如有侵权,请分割删除。

相干视频举荐:

【2021 最新版】Android studio 装置教程 +Android(安卓)零基础教程视频(适宜 Android 0 根底,Android 初学入门)含音视频_哔哩哔哩_bilibili

Android 架构设计原理与实战——Jetpack 联合 MVP 组合利用开发一个优良的 APP!_哔哩哔哩_bilibili

Android 进阶必学:jetpack 架构组件—Navigation_哔哩哔哩_bilibili

Android 进阶零碎学习——Jetpack 先天优良的基因能够防止数据内存透露_哔哩哔哩_bilibili

Android 进阶必学:jetpack 架构组件—Navigation_哔哩哔哩_bilibili

退出移动版