Android 中 JetPack(二)DataBinding(视图绑定)(下)

858 阅读6分钟

关于DataBinding的内容比较多,所以我准备分上下两篇.

整体的逻辑内容包含:

  • 什么是视图绑定
  • 视图绑定的使用
  • 表达式的使用
  • 常见的事件绑定
  • Adapter适配器的处理
  • 自定义绑定适配器

上篇主要讲怎么使用,如果已经对DataBinding有点了解的话,可以直接从这篇开始看.如果对基础知识还没有了解的话,可以点击上篇进行学习.

Android 中 JetPack(二)DataBinding(视图绑定)(上)

本文知识点

  • 表达式的使用
  • 常见的事件绑定
  • Adapter适配器的处理
  • 自定义绑定适配器

1. 表达式的使用

关于表达式我还是多啰嗦几句,虽然支持表达式,但是尽力别用复杂的表达式.要不然错了你真的不太好排查...切记!!!

官网上说支持的表达式:

  • 算术运算符 + - / * %
  • 字符串连接运算符 +
  • 逻辑运算符 && ||
  • 二元运算符 & | ^
  • 一元运算符 + - ! ~
  • 移位运算符 >> >>> <<
  • 比较运算符 == > < >= <=(请注意,< 需要转义为 <)
  • instanceof
  • 分组运算符 ()
  • 字面量运算符 - 字符、字符串、数字、null
  • 类型转换
  • 方法调用
  • 字段访问
  • 数组访问 []
  • 三元运算符 ?:

先来一段代码开开胃...

android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age > 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'

还是很好理解的,相信只要写过java的应该都能看懂.

还有一点需要注意的,当你使用的时候出现null或者0的时候莫方

例如,在表达式 @{user.name}中,如果user为Null,则为user.name分配默认值null.如果您引用 user.age,其中age的类型为int,则数据绑定使用默认值0.

以下是一些特殊的用法:

    1. NUll的合并
android:text="@{user.displayName ?? user.lastName}"
<!--等价于-->
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
    1. 集合
<variable
    name="testList"
    type="java.util.ArrayList&lt;String>" />
<!--以下是使用-->
<TextView
      android:id="@+id/list"
      android:layout_width="match_parent"
      android:layout_height="40dp"
      android:gravity="center"
      android:text="@{testList[0]}"
      app:layout_constraintTop_toBottomOf="@id/name" />

划重点 千万不要写成List 而是要写成List&lt;String> 千万注意!!!

Map的话和这个类似,但是map的key要用单引号例如android:text="@{map[`firstName`]}" 剩下的自己尝试一下就好了.

    1. 资源

关于资源可以结合字符串资源一起看,这样可以更好的理解.

android:layout_marginLeft="@{name!=null ? @dimen/largePadding : @dimen/smallPadding}"

例如上面这个样子,其实就是字符串加上一些相应的组合而已...没有什么好说的.一般也都用不到

    1. 默认参数 因为xml在赋值的时候有相应的null和0等默认的参数,但是有不想看到页面上显示null,这个时候就有用了一个默认参数的概念了
android:text='@{userData.name, default="Placeholder text"}'
android:text="@{userData.name, default=`Placeholder text`}"
android:text="@{userData.name, default=&quot;Placeholder text&quot;}"
android:text="@{userData.name, default=@string/app_name}"

但是我看还是很多人喜欢这种写法

android:text="@{userData.name ?? @string/app_name}"

看个人爱好吧... 尝试一下就好了.如果还是不明白看看StackOverflow的文章

    1. 包含

在开发中难免用到include标签进行布局,这个时候问题就产生了,我的一个参数怎么从主xml传递到include的xml中去?

开始的时候我看官方文档,里面说直接说是按照下面的方式进行处理:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:bind="http://schemas.android.com/apk/res-auto">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <include layout="@layout/name"
           bind:binduser="@{user}"/>
   </LinearLayout>
</layout>

但是我想了好久都没想明白这个user是从哪里蹦出来的.于是乎就...

其实这个是在相应的include的布局中添加了一个

<data>
    <variable
        name="binduser"
        type="com.example.User" />
</data>

这样是不是就一下子明白了,其实就是在指定的布局中设置一个相应的参数,然后在include的时候导入就好了.

2. 常见的事件绑定

关于事件绑定这块官方的例子里面 都是单独创建一个类进行事件处理.我个人觉得这样有个弊端,因为这个点击事件一定是和Activity之间交换的,如果里面存在的参数过多的话,那么就很难传值了,所以我个人觉得这几最好创建一个匿名内部类去处理逻辑,这样就可以拿到Activity中的数据.

好了,说了上面这些,就可以撸码了.

kotlin代码示例:

inner class TaskKotlinOpt {
    private val TAG = TaskKotlinOpt::class.simpleName

    fun click() {
        Log.e(TAG, "点击事件触发了")
    }
}

java代码示例:

public class TaskJavaOpt {
    public void click() {
        Log.e("TAG", "点击事件触发了");
    }
}

xml中的代码,注意这里的onClick

<variable
    name="kotlinOpt"
    type="com.angle.android.databinding.TestKotlinActivity.TaskKotlinOpt" />
    
<variable
    name="javaOpt"
    type="com.angle.android.databinding.TestJavaActivity.TaskJavaOpt" />

<!--下面是代码引用-->
<Button
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:onClick="@{()->kotlinOpt/javaOpt.click()}"
    android:text="点击事件"
    app:layout_constraintTop_toBottomOf="@id/source" />

这里说明一下,我还是把主要的内容都写在一块了,记得区分,到时候别说我代码有问题哈!!!因为没有参数,所以前面就是一个()紧接着跟了一个->kotlinOpt是一个参数名称,后面跟的方法.如果有参数的话,可以写成这个样子android:onClick="@{()->kotlinOpt.click(userData)}" 然后修改下面的代码:

kotlin代码示例:

fun click(userKotlin: UserKotlin) {
    Log.e(TAG, "点击事件触发了${userKotlin.name}")
}

java代码示例:

public void click(UserJava userJava) {
    Log.e("TAG", "点击事件触发了" + userJava.getName());
}

关于事件绑定这块还有一个问题需要说明一下,尽量不要处理Textwatch这种复杂的监听.千万不要...

3. Adapter适配器的处理

关于适配器的处理,其实和Activity差不多.只是生成Binding对象的方式不一样.

我这里把所有的代码都贴到里面

kotlin代码示例:

class TestKotlinAdapter(private val context: Context) : RecyclerView.Adapter<TestKotlinAdapter.TestHolder>() {

    private var list: List<String>? = null

    fun setData(list: List<String>) {
        this.list = list
        notifyDataSetChanged()
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TestHolder {
        val itemViewBinding = DataBindingUtil.inflate<ItemViewBinding>(
            LayoutInflater.from(context),
            R.layout.item_view,
            parent,
            false
        )
        return TestHolder(itemViewBinding.root)
    }

    override fun onBindViewHolder(holder: TestHolder, position: Int) {
        val itemViewBinding = DataBindingUtil.getBinding<ItemViewBinding>(holder.itemView)
        itemViewBinding?.showTv?.text = list?.get(position)
        itemViewBinding?.executePendingBindings()
    }

    override fun getItemCount(): Int {
        return list?.size ?: 0
    }

    class TestHolder(view: View) : RecyclerView.ViewHolder(view)
}

java代码示例:

class TestJavaAdapter extends RecyclerView.Adapter<TestJavaAdapter.TestHolder> {

    private Context mContext;
    private List<String> mList;

    public TestJavaAdapter(Context mContext) {
        this.mContext = mContext;
    }

    public void setData(List<String> list) {
        mList = list;
        notifyDataSetChanged();
    }

    @NonNull
    @Override
    public TestHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        ItemViewBinding itemViewBinding = DataBindingUtil.inflate(LayoutInflater.from(mContext), R.layout.item_view, parent, false);
        return new TestHolder(itemViewBinding.getRoot());
    }

    @Override
    public void onBindViewHolder(@NonNull TestHolder holder, int position) {
        ItemViewBinding itemViewBinding = DataBindingUtil.getBinding(holder.itemView);
        if (itemViewBinding != null) {
            String showStr = mList.get(position);
            itemViewBinding.showTv.setText(showStr);
            itemViewBinding.executePendingBindings();
        }
    }

    @Override
    public int getItemCount() {
        return mList != null ? mList.size() : 0;
    }

    public class TestHolder extends RecyclerView.ViewHolder {
        public TestHolder(@NonNull View itemView) {
            super(itemView);
        }
    }
}

这里只要看onBindViewHolder方法中的绑定就可以.其他的可以不用关注.但是后面的一句executePendingBindings()一定要加,这句是更新列表的语句.剩下的就没有什么好说的了.其实这里面也从侧面讲述了kotlin的代码比java的代码简洁.

4. 自定义绑定适配器

这个绑定适配器的方式其实还挺好用的,最典型的例子就是加载图片.

下面看看代码的示例: kotlin代码示例:

class AdapterKotlinUtils {

    @BindingAdapter("kotlinImageUrl", "kotlinLoadUrl")
    fun loadImage(view: ImageView, url: String) {
        //todo 加载图片的操作
    }
}

java代码示例:

public class AdapterJavaUtils {

    @BindingAdapter({"javaImageUrl", "javaLoadUrl"})
    public void loadImage(View view, String url) {
        //todo 加载图片的操作
    }
}

在使用的时候需要在xml中以auto设置属性就可以了.

<xmlns:app="http://schemas.android.com/apk/res-auto">
<!--上面的是使用-->
<ImageView app:kotlinLoadUrl="@{xxx.imageUrl}" />

这里说明一下哈,view这个属性是当前的View,是必须的.后面的参数直接赋值就好了.其实这里还可以传相应的实体类,例如对象,回调方法等... 这个写法和之间介绍的表达式一样.感兴趣的可以尝试一下.