一看就会 Android框架DataBinding的使用与封装

3,581 阅读7分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

Android中DataBinding的封装

先简单的介绍DataBinding

DataBinding 是谷歌官方发布的一个框架,是 MVVM 模式在 Android 上的一种实现,减少了布局和逻辑的耦合,数据能够单向或双向绑定到 layout 文件中,有助于防止内存泄漏,而且能自动进行空检测以避免空指针异常。

虽然现在Xml中可以写逻辑代码了,但是还是推荐不要直接在xml里面写复杂的逻辑,如果有必要的需求,我们可以用BindingAdapter 自定义属性。

话不多说,快来看看怎么用!

一. 如何使用

1.1.数据的绑定

gradle开启功能, 4.0以上和以下的有区别。现在很少有4.0以下的吧。

android {
    viewBinding {
        enabled = true
    }
    dataBinding{
        enabled = true
    }
}

// Android Studio 4.0
android {
    buildFeatures {
        dataBinding = true
        viewBinding = true
    }
}

开启DataBinding之后,xml会默认编译为Java对象,如果不想自己的非DB的xml被编译,可以在xml添加忽略

tools:viewBindingIgnore="true" 

正常的xml中需要用layout布局包裹,模板如下:
<?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:binding="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="testBean"
            type="com.guadou.kt_demo.demo.demo12_databinding_texing.TestBindingBean" />

        <variable
            name="click"
            type="com.guadou.kt_demo.demo.demo12_databinding_texing.Demo12Activity.ClickProxy" />

    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/white"
        android:orientation="vertical">

         <!--    注意双向绑定的写法    -->
           <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@={click.etLiveData}" />

           <Button
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="绑定(获取值)"
                binding:clicks="@{click.showETText}" />

         <include
            layout="@layout/include_databinding_test"
            binding:click="@{click}"
            binding:testBean="@{testBean}" />

         <com.guadou.kt_demo.demo.demo12_databinding_texing.CustomTestView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            binding:clickProxy="@{click}"
            binding:testBean="@{testBean}" />

    </LinearLayout>

</layout>

xml中data闭包是数据源,我们定义了类型之后需要在Activity/Fragment中设置,例如:
    val view = CommUtils.inflate(R.layou.include_databinding_test)
    //绑定DataBinding 并赋值自定义的数据
    DataBindingUtil.bind<IncludeDatabindingTestBinding>(view)?.apply {
          testBean = TestBindingBean("haha", "heihei", "huhu")
         click = clickProxy
    }

绑定数据的类型我们可以用LiveData或Flow都可以:

     val etLiveData: MutableLiveData<String> = MutableLiveData()
     val etFlow: MutableStateFlow<String?> = MutableStateFlow(null)

直接XML中双向绑定数据或者单向的绑定数据即可

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@={click.etLiveData}" />

1.2 布局的绑定

inflate/include/ViewStub/CustomView如何绑定布局与DataBinding

include/ViewStub这样用:

     <include
            layout="@layout/include_databinding_test"
            binding:click="@{click}"
            binding:testBean="@{testBean}" />

include_databinding_test:

<?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:binding="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="testBean"
            type="com.guadou.kt_demo.demo.demo12_databinding_texing.TestBindingBean" />

        <variable
            name="click"
            type="com.guadou.kt_demo.demo.demo12_databinding_texing.Demo12Activity.ClickProxy" />

        <import
            alias="textUtlis"
            type="android.text.TextUtils" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <TextView
            android:layout_marginTop="15dp"
            android:text="下面是赋值的数据"
            binding:clicks="@{click.testToast}"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{testBean.text1}" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{testBean.text2}" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{testBean.text3}" />

    </LinearLayout>

</layout>

include:

        //Activity中动态的加载布局
        fun inflateXml() {
            //给静态的xml,赋值数据,赋值完成之后 include的布局也可以自动显示
            mBinding.testBean = TestBindingBean("haha", "heihei", "huhu")

            //获取View
            val view = CommUtils.inflate(R.layout.include_databinding_test)
            //绑定DataBinding 并赋值自定义的数据
            DataBindingUtil.bind<IncludeDatabindingTestBinding>(view)?.apply {
                testBean = TestBindingBean("haha1", "heihei1", "huhu1")
                click = clickProxy
            }
            //添加布局
            mBinding.flContent.apply {
                removeAllViews()
                addView(view)
            }
        }

自定义View,在Xml中定义和在Activity中手动定义是一样的。这里演示手动定义赋值:

      fun customView() {
            //给静态的xml,赋值数据,赋值完成之后 include的布局也可以自动显示
            mBinding.testBean = TestBindingBean("haha2", "heihei2", "huhu2")

            //动态的添加自定义View
            val customTestView = CustomTestView(mActivity)
            customTestView.setClickProxy(clickProxy)
            customTestView.setTestBean(TestBindingBean("haha3", "heihei3", "huhu3"))

            mBinding.flContent2.apply {
                removeAllViews()
                addView(customTestView)
            }
        }

自定义Viewr中绑定属性:

class CustomTestView @JvmOverloads constructor(context: Context?, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
    LinearLayout(context, attrs, defStyleAttr) {
    
    init {
        orientation = VERTICAL

        //传统的方式添加
        val view = CommUtils.inflate(R.layout.layout_custom_databinding_test)
        addView(view)

    }

    //设置属性
    fun setTestBean(bean: TestBindingBean?) {

        bean?.let {
            findViewById<TextView>(R.id.tv_custom_test1).text = it.text1
            findViewById<TextView>(R.id.tv_custom_test2).text = it.text2
            findViewById<TextView>(R.id.tv_custom_test3).text = it.text3
        }
        
    }

    fun setClickProxy(click: Demo12Activity.ClickProxy?) {
        findViewById<TextView>(R.id.tv_custom_test1).click {
            click?.testToast()
        }
    }

}

1.3 事件的绑定

比较常见的是Click和控件的事件监听回调:

     <EditText
            android:id="@+id/et_redpack_money"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginLeft="@dimen/d_9dp"
            android:layout_weight="1"
            android:background="@null"
            android:hint="0.00"
            android:inputType="numberDecimal"
            android:paddingLeft="@dimen/d_9dp"
            android:singleLine="true"
            android:textColor="@color/black_33"
            android:textCursorDrawable="@null"
            android:textSize="@dimen/d_25sp"
            binding:typefaceSemiBold="@{true}"
            binding:onTextChanged="@{click.onAmountChanged}"
            binding:setDecimalPoints="@{2}" />

Click封装的代码中使用高阶函数来接收回调

   inner class ClickProxy {

        //金额变化
        val onAmountChanged: (String) -> Unit = {
            calculationTotalAmount(it)
        }
       ...
    }

点击事件的封装: 注意一个是我的封装clicks,一个是远程android的属性click

         <Button
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="双向绑定(获取值)"
                binding:clicks="@{click.showETText}" />

            <Button
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginLeft="@dimen/d_20dp"
                android:layout_weight="1"
                android:text="双向绑定(赋值)"        
               android:onClick="@{(v)->click.setData2ET(v)}"/>

xml中原生的onClick有多种方法如下:

   //默认写法 
   android:onClick="@{(v)->click.setData2ET(v)}"
   
   //简写
    android:onClick="@{click::setData2ET}"
 
   //带参数
    android:onClick="@{(v) -> click.onSaveClick(v, task)}"
    
    //判断逻辑调用不同的fun
    android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"
    

回调的方法也不同:

    inner class ClickProxy {

        fun showETText() {
            toast(etLiveData.value)
        }

        fun setData2ET(view: View) {
            etLiveData.value = "设置数据给ET"
        }

        fun testToast() {
            toast("测试吐司")
        }
    }

1.4 自定义事件的封装-BindingAdapter

我自己封装的比较多,有些和业务比较关联,常见的一些事件封装如下:

/**
 * 设置控件的隐藏与显示
 */
@BindingAdapter("isVisibleGone")
fun isVisibleGone(view: View, isVisible: Boolean) {
    view.visibility = if (isVisible) View.VISIBLE else View.GONE
}

/**
 * 点击事件防抖动的点击
 */
@BindingAdapter("clicks")
fun clicks(view: View, action: () -> Unit) {
    view.click { action() }
}

/**
 * 下划线
 */
@BindingAdapter("isUnderline")
fun setUnderline(textView: TextView, isUnderline: Boolean) {
    if (isUnderline) {
        textView.paint.flags = Paint.UNDERLINE_TEXT_FLAG//下划线
        textView.paint.isAntiAlias = true //抗锯齿
    } else {
        textView.paint.flags = 0
    }
}

@BindingAdapter("isCenterLine")
fun isCenterLine(textView: TextView, isUnderline: Boolean) {
    if (isUnderline) {
        textView.paint.flags = Paint.STRIKE_THRU_TEXT_FLAG
        textView.paint.isAntiAlias = true //抗锯齿
    } else {
        textView.paint.flags = 0
    }
}

@BindingAdapter("setRightDrawable")
fun setRightDrawable(textView: TextView, drawable: Drawable?) {
    if (drawable == null) {
        textView.setCompoundDrawables(null, null, null, null)
    } else {
        drawable.setBounds(0, 0, drawable.minimumWidth, drawable.minimumHeight)
        textView.setCompoundDrawables(null, null, drawable, null)
    }
}

@BindingAdapter("setLeftDrawable")
fun setLeftDrawable(textView: TextView, drawable: Drawable?) {
    if (drawable == null) {
        textView.setCompoundDrawables(null, null, null, null)
    } else {
        drawable.setBounds(0, 0, drawable.minimumWidth, drawable.minimumHeight)
        textView.setCompoundDrawables(drawable, null, null, null)
    }
}

@BindingAdapter("text", "default", requireAll = false)
fun setText(view: TextView, text: CharSequence?, default: String?) {
    if (text == null || text.trim() == "" || text.contains("null")) {
        view.text = default
    } else {
        view.text = text
    }
}

图片加载相关:使用图片加载引擎加载图片满足各种圆角,和常用属性:

/**
 * 设置图片的加载
 */
@BindingAdapter("imgUrl", "placeholder", "roundRadius", "isCircle", requireAll = false)
fun loadImg(
    view: ImageView,
    url: Any?,
    placeholder: Drawable? = null,
    roundRadius: Int = 0,
    isCircle: Boolean = false
) {
    url?.let {
        view.extLoad(
            it,
            placeholder = placeholder,
            roundRadius = CommUtils.dip2px(roundRadius),
            isCircle = isCircle
        )
    }
}


@BindingAdapter("loadBitmap")
fun loadBitmap(view: ImageView, bitmap: Bitmap?) {
    view.setImageBitmap(bitmap)
}

图片的属性使用:

      <ImageView
            android:id="@+id/iv_img_2"
            android:layout_width="@dimen/d_100dp"
            android:layout_height="@dimen/d_100dp"
            android:layout_marginLeft="@dimen/d_50dp"
            android:scaleType="centerCrop"
            binding:imgUrl="@{viewModel.img2LiveData}"
            binding:placeholder="@{@drawable/home_list_plachholder}"
            binding:roundRadius="@{10}" />

EditText的监听和属性封装:

/**
 * EditText的简单监听事件
 */
@BindingAdapter("onTextChanged")
fun EditText.onTextChanged(action: (String) -> Unit) {
    addTextChangedListener(object : TextWatcher {
        override fun afterTextChanged(s: Editable?) {
        }

        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
        }

        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
            action(s.toString())
        }
    })
}

var _viewClickFlag = false
var _clickRunnable = Runnable { _viewClickFlag = false }

/**
 * Edit的确认按键事件
 */
@BindingAdapter("onKeyEnter")
fun EditText.onKeyEnter(action: () -> Unit) {
    setOnKeyListener { _, keyCode, _ ->
        if (keyCode == KeyEvent.KEYCODE_ENTER) {
            KeyboardUtils.closeSoftKeyboard(this)

            if (!_viewClickFlag) {
                _viewClickFlag = true
                action()
            }
            removeCallbacks(_clickRunnable)
            postDelayed(_clickRunnable, 1000)
        }
        return@setOnKeyListener false
    }
}

/**
 * Edit的失去焦点监听
 */
@BindingAdapter("onFocusLose")
fun EditText.onFocusLose(action: (textView: TextView) -> Unit) {
    setOnFocusChangeListener { _, hasFocus ->
        if (!hasFocus) {
            action(this)
        }
    }
}

/**
 * Edit的得到焦点监听
 */
@BindingAdapter("onFocusGet")
fun EditText.onFocusGet(action: (textView: TextView) -> Unit) {
    setOnFocusChangeListener { _, hasFocus ->
        if (hasFocus) {
            action(this)
        }
    }
}

/**
 * 设置ET智能小数点2位
 */
@BindingAdapter("setDecimalPoints")
fun setDecimalPoints(editText: EditText, num: Int) {
    editText.filters = arrayOf<InputFilter>(ETMoneyValueFilter(num))
}

EditText的使用:

     <EditText
            android:id="@+id/et_redpack_money"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginLeft="@dimen/d_9dp"
            android:layout_weight="1"
            android:background="@null"
            android:hint="0.00"
            android:inputType="numberDecimal"
            android:paddingLeft="@dimen/d_9dp"
            android:singleLine="true"
            android:textColor="@color/black_33"
            android:textCursorDrawable="@null"
            android:textSize="@dimen/d_25sp"
            binding:typefaceSemiBold="@{true}"
            binding:onTextChanged="@{click.onAmountChanged}"
            binding:setDecimalPoints="@{2}" />

还有一些和业务关联的Adapter,比如比较常见的,根据不同的type展示不同的图片:

@BindingAdapter("setShareIcon")
fun setShareIcon(imageView: ImageView, firendGroup: MyFirendGroups) {

    imageView.setImageResource(if (firendGroup.type ==0) R.drawable.dialog_share_group_icon else R.drawable.dialog_share_more_icon)

}

已经说了讲下简单的使用,还是写了这么多。还有很多地方没写到,不过常用的都在上面了。 下面讲下如何封装DataBinding


二. DataBinding的封装

封装之后在Activity/Fragment/RecyclerView等常用的场景更加方便的使用。

2.1 基于Adapter的封装,方便在RV中使用

这里用的是BRVAH,如果是自己封装的Adapter是一样的用法,不复杂。

**
 * 基类的BRVAH的DataBinding封装
 */
open class BaseDataBindingAdapter<T>(layoutResId: Int, br: Int, list: MutableList<T> = mutableListOf()) :
    BaseQuickAdapter<T, BaseViewHolder>(layoutResId, list), LoadMoreModule {

    private val _br: Int = br

    override fun onItemViewHolderCreated(viewHolder: BaseViewHolder, viewType: Int) {
        // 绑定databinding
        DataBindingUtil.bind<ViewDataBinding>(viewHolder.itemView)
    }

    override fun convert(holder: BaseViewHolder, item: T, payloads: List<Any>) {
        super.convert(holder, item, payloads)
    }

    override fun convert(holder: BaseViewHolder, item: T) {
        if (item == null) {
            return
        }

        holder.getBinding<ViewDataBinding>()?.run {
            if (_br > 0) {
                setVariable(_br, item)
            }
            executePendingBindings()
        }

    }

}

多布局的封装

/**
 * 基类的BRVAH的DataBinding封装(多布局)
 * 需要再子类实现多布局逻辑,这里只是实现了Item的DataBinding
 */
open class BaseMultiDataBindingAdapter<T : MultiItemEntity>(
    br: Int,
    list: MutableList<T> = mutableListOf()
) : BaseMultiItemQuickAdapter<T, BaseViewHolder>(list), LoadMoreModule {

    val _br: Int = br

    override fun onItemViewHolderCreated(viewHolder: BaseViewHolder, viewType: Int) {
        // 绑定databinding
        DataBindingUtil.bind<ViewDataBinding>(viewHolder.itemView)
    }


    @Suppress("SENSELESS_COMPARISON")
    override fun convert(holder: BaseViewHolder, item: T) {
        if (item == null) {
            return
        }

        holder.getBinding<ViewDataBinding>()?.run {
            setVariable(_br, item)
            executePendingBindings()
        }

    }
    
}

使用:
比如是好友列表,三行代码就能实现:

    var mDatas = mutableListOf<MyFriends>()
    val mAdapter by lazy { BaseDataBindingAdapter(R.layout.item_friends, BR.item, mDatas) }

这样就有可以了,当然RV还是要设置的

    private fun initRV() {
        mBinding.recyclerView.vertical().adapter = mViewModel.mAdapter
    }

xml中绑定对应的数据就行了

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:binding="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="item"
            type="com.guadou.cs_cptservices.commbean.MyFriends" />

    </data>

    <LinearLayout>
     //xxx省略

       <com.guadou.lib_baselib.view.CircleImageView
                android:layout_width="45dp"
                android:layout_height="45dp"
                binding:imgUrl="@{item.friend_avatar}"
                binding:placeholder="@{@drawable/yypay_default_head}"
                android:scaleType="centerCrop"
                android:src="@drawable/yypay_default_head" />

         <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:ellipsize="end"
                android:singleLine="true"
                android:text="@{item.friend_name}"
                android:textColor="@color/black"
                android:textSize="16sp"
                tools:text="User Name" />

    </LinearLayout>

这里大家应该都会,就不贴太多没用的代码,效果图如下:

RV的多布局则要写一个Adapter,注册多布局的类型:

class MyRewardsActiveAdapter(br: Int, list: MutableList<MyRewardsListData>) : BaseMultiDataBindingAdapter<MyRewardsListData>(br, list) {

    init {
        // 绑定 layout 对应的 type --  分Rewards和Voucher
        addItemType(Constants.ITEM_MORE, R.layout.item_voucher_active)
        addItemType(Constants.ITEM, R.layout.item_rewards_active)
    }

}

使用Adapter:

   private fun initRV() {
        mViewModel.mAdapter = MyRewardsActiveAdapter(BR.item, mViewModel.mDatas)
        mBinding.recyclerView.vertical().adapter = mViewModel.mAdapter
    }

2.2 Activity/Fragment的封装

对DataBinding的绑定布局,添加数据功能进行封装:

//DataBinding的封装数据
class DataBindingConfig(private val layout: Int, private val vmVariableId: Int, private val viewModel: BaseViewModel?) {

    constructor(layout: Int) : this(layout, 0, null)

    private var bindingParams: SparseArray<Any> = SparseArray()

    fun getLayout(): Int = layout

    fun getVmVariableId(): Int = vmVariableId

    fun getViewModel(): BaseViewModel? = viewModel

    fun getBindingParams(): SparseArray<Any> = bindingParams

    fun addBindingParams(variableId: Int, objezt: Any): DataBindingConfig {
        if (bindingParams.get(variableId) == null) {
            bindingParams.put(variableId, objezt)
        }
        return this
    }

}

BaseActivity的封装中:

    override fun setContentView() {
        mViewModel = createViewModel()

        val config = getDataBindingConfig()
        mBinding = DataBindingUtil.setContentView(this, config.getLayout())
        mBinding.lifecycleOwner = this

        if (config.getVmVariableId() != 0) {
            mBinding.setVariable(
                config.getVmVariableId(),
                config.getViewModel()
            )
        }

        val bindingParams = config.getBindingParams()
        bindingParams.forEach { key, value ->
            mBinding.setVariable(key, value)
        }
    }

    abstract fun getDataBindingConfig(): DataBindingConfig

使用的时候我们封装填充自己的DataBindingConfig即可:

class MyRewardsDetailFragment(private val myRewardsId: String?) :
    YYBaseVDBFragment<MyRewardsDetailViewModel, FragmentMyRewardsDetailBinding>() {

    override fun getDataBindingConfig(): DataBindingConfig {
        return DataBindingConfig(R.layout.fragment_my_rewards_detail, BR.viewModel, mViewModel)
            .addBindingParams(BR.click, ClickProxy())  //可以自己添加数据源
            .addBindingParams(BR.item,ItmBean())
    }

    override fun init(savedInstanceState: Bundle?) {

        initData()
    }

    private fun initData() {
        mViewModel.getMyRewardsDetail(myRewardsId)
    }

    /**
     * DataBinding事件处理
     */
    inner class ClickProxy {

        fun gotoRedeemPage() {
            navigator.push({ applySlideInOut() }) {
                RewardsRedeemFragment(myRewardsId)
            }
        }
    }
}

xml如下:

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:binding="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    tools:ignore="RtlHardcoded">

    <data>

        <variable
            name="viewModel"
            type="com.guadou.cpt_rewards.mvvm.vm.MyRewardsDetailViewModel" />

        <variable
            name="click"
            type="com.guadou.cpt_rewards.ui.fragment.my.MyRewardsDetailFragment.ClickProxy" />

        <variable
            name="item"
            type="com.guadou.cpt_rewards.entity.ItemBean" />
        
        <import type="android.text.TextUtils" />

        <import type="com.guadou.lib_baselib.utils.DateAndTimeUtil" />

    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
         xxx
    </LinearLayout>

当然你可以不在DataBindingConfig中绑定数据和ViewModel:
class RewardsRedeemFragment(private val myRewardsId: String?) : YYBaseVDBFragment<RewardsRedeemViewModel, FragmentRewardsRedeemBinding>() {

    override fun getDataBindingConfig(): DataBindingConfig {
        return DataBindingConfig(R.layout.fragment_rewards_redeem)
    }

    @ExperimentalCoroutinesApi
    override fun init(savedInstanceState: Bundle?) {
      
        //获取支付码展示
        mViewModel.getRedeemDetail(myRewardsId)
    }

    override fun startObserve() {
        mViewModel.mRedeemCodeLiveData.observe(this) {
            it?.let { popupData(it) }
        }
    }

    private fun popupData(code: MyRewardsCode) {
        //没有绑定ViewModel和Click对象,无法直接在xml绑定数据,那么直接拿到控件setText也是可以的
        mBinding.ivQrCode.extLoad(code.url)

        mBinding.tvVertyCode.text = code.code
    }

    private fun gotoRedeemSuccess(scanResult: RewardsScanResult) {
        navigator.push({ applySlideInOut() }) {
            RewardsRedeemSuccessFragment(scanResult)
        }
    }

}

到此DataBinding常用用法和封装就完成了,更多代码可以查看源码
完结啦