美文网首页
Android JetPack系列之——DataBinding

Android JetPack系列之——DataBinding

作者: 乌托邦式的爱情 | 来源:发表于2021-08-30 16:41 被阅读0次

    在传统的Android应用开发中,布局文件通常只负责应用界面的布局工作,如果需要实现页面交互就需要调用setContentView()将Activity、fragment和XML布局文件关联起来。然后通过控件的id找到控件,接着在页面中通过代码对控件进行逻辑处理。在这种传统的开发方式中,页面承担了大部分的工作量,大量的逻辑处理需要在Activity、Fragment中进行处理,因此页面显得臃肿不堪,维护起来也很困难,为了减轻页面的工作量,Google提出了DataBinding(视图绑定)。

    一、DataBinding的优点?

    (1)解耦。将部分原属于Activity/Fragment去实现的功能交由Model实体类去实现,从而实现了部分功能代码的分离,既保证了Activity/Fragment过于臃肿,也便于后期代码的维护和扩展。
    (2)避免重复无效代码。不再需要findViewById操作(其实这条不是那么重要,因为采用kotlin编码的话本身就没有findViewById)。

    总结:Databinding的作用就是在XML文件里面实现数据的部分绑定从而避免将数据的全部展示交由Controller层去实现从而达到代码的易于扩展和后期的维护目的。

    二、DataBinding的基本使用

    先来了解一下DataBinding常用的几个类:

    • DataBindingUtil:在Activity/Fragment中获取相关的Binding对象。
    • BaseObservable:Bean可以继承该抽象类,实现可观察的模式,在set属性的时候调用notifyPropertyChanged方法,唤起刷新操作,也可以调用notifyChange方法全部刷新。
    • Observable:Bean可以实现该接口,实现可观察的模式,在set属性的时候调用notifyPropertyChanged方法,唤起刷新操作,也可以调用notifyChange方法全部刷新。
    • ObservableFloat:这不是一个类,而是一类类的代表,如ObservableShort、ObservableParcelable等等,可观察的属性,通过get和set方法操作相关的值。
    • BaseObservableField<>:和上述类似,泛型可以传入String等类型,比上述定义的基类型更加自由。

    1.添加依赖

    android {
        ...
        dataBinding {
            enabled = true
        }
    }
    

    2.修改布局文件
    <layout>为头,以</layout>为尾。

    <layout>
        <data>
            <variable
                name="login"
                type="com.jack.androidjetpack.login.Login" />
        </data>
    
        <androidx.constraintlayout.widget.ConstraintLayout 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"
            android:id="@+id/container"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:paddingLeft="@dimen/activity_horizontal_margin"
            android:paddingTop="@dimen/activity_vertical_margin"
            android:paddingRight="@dimen/activity_horizontal_margin"
            android:paddingBottom="@dimen/activity_vertical_margin">
        </androidx.constraintlayout.widget.ConstraintLayout>
    </layout>
    

    3.点击Build->Rebuild Project生成对应的Binding

    4.使用DataBindingUtil类来进行视图的绑定
    Activity的处理方式

    class LoginActivity : AppCompatActivity() {
    
        var binding: ActivityLoginBinding? = null
        var login: Login? = null
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            binding = DataBindingUtil.setContentView(this, R.layout.activity_login)
            login = Login("jack", "123456")
            binding?.setLogin(login)
        }
    }
    

    Fragment的处理方式

    class PhoneCodeFragment : Fragment() {
    
        private var param1: String? = null
        private var param2: String? = null
        private var binding: FragmentPhoneCodeBinding? = null
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            arguments?.let {
                param1 = it.getString(ARG_PARAM1)
                param2 = it.getString(ARG_PARAM2)
            }
        }
    
        override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View? {
            binding = FragmentPhoneCodeBinding.inflate(inflater, container, false)
            return binding?.root
        }
    
        companion object {
            @JvmStatic
            fun newInstance(param1: String, param2: String) =
                PhoneCodeFragment().apply {
                    arguments = Bundle().apply {
                        putString(ARG_PARAM1, param1)
                        putString(ARG_PARAM2, param2)
                    }
                }
        }
    }
    

    5.在layout布局中添加data标签
    经过前面的几个步骤,我们已经将Databinding和我们的XML文件绑定起来了,现在你点击Databinding会发现直接可以跳转到对应的XML文件里面去了,现在我们就来看看如何给我们的XML文件里面的View设置值。

    在XML文件的layout标签下,创建data标签,在data标签中再创建variable标签,variable标签主要用到的就是name属性和type属性,类似于Java语言声明变量时,需要为该变量指定类型和名称。新建一个名为Login的数据类。

    data class Login(var userName: String, var password: String):BaseObservable()
    

    然后在布局的 data 标签里声明要使用到的变量名、类的全路径等信息,如下所示:

    <layout>
    
        <data>
    
            <variable
                name="login"
                type="com.jack.androidjetpack.login.Login" />
    
        </data>
        ...
    </layout>
    

    如果 Login有多处用到,也可以直接将之 import 进来,这样就不用每次都指明整个包名路径了,而 java.lang.* 包中的类会被自动导入,所以可以直接使用。

    <layout>
    
        <data>
    
            <import type="com.jack.androidjetpack.login.Login" />
    
            <variable
                name="login"
                type="Login" />
    
        </data>
        ...
    </layout>
    

    在XML文件中声明好variable属性后,接下来就可以在XML使用它了。使用variable属性时需要使用到布局表达式: @{ }。可以在布局表达式@{ }中获取传入variable对象的值,如下所示:

     <androidx.constraintlayout.widget.ConstraintLayout 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"
            android:id="@+id/container"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:paddingLeft="@dimen/activity_horizontal_margin"
            android:paddingTop="@dimen/activity_vertical_margin"
            android:paddingRight="@dimen/activity_horizontal_margin"
            android:paddingBottom="@dimen/activity_vertical_margin">
    
            <EditText
                android:id="@+id/username"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginStart="24dp"
                android:layout_marginTop="96dp"
                android:layout_marginEnd="24dp"
                android:hint="@string/prompt_name"
                android:inputType="textEmailAddress"
                android:selectAllOnFocus="true"
                android:text="@{login.userName}"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />
    
            <EditText
                android:id="@+id/password"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginStart="24dp"
                android:layout_marginTop="8dp"
                android:layout_marginEnd="24dp"
                android:hint="@string/prompt_password"
                android:imeActionLabel="@string/action_sign_in_short"
                android:imeOptions="actionDone"
                android:inputType="textPassword"
                android:selectAllOnFocus="true"
                android:text="@{login.password}"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/username" />
        
        </androidx.constraintlayout.widget.ConstraintLayout>
    

    最后在我们的Controller层将我们的datamodel相关联。

        var binding: ActivityLoginBinding? = null
        var login: Login? = null
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            binding = DataBindingUtil.setContentView(this, R.layout.activity_login)
            login = Login("jack", "123456")
            binding?.setLogin(login)
        }
    

    到这里,一个最基础的DataBinding的例子就结束了,接下来我们继续往下看。

    三、DataBinding的进阶

    进阶1:给控件添加响应事件
    方式一:直接在Controller层通过原来的方式添加

    binding?.login?.setOnClickListener {
              
    }
    

    方式二:创建一个工具类,在类中定义响应的点击事件
    第一步:创建点击的工具类

    /**
     * @author: zhoufan
     * @date:   2021/8/30 11:14
     */
    class ButtonClickListener {
    
        fun click(view: View) {
            Log.e("click","响应登录的点击事件")
        }
    }
    

    第二步:在XML文件中添加工具类

    <variable
          name="btnHandler"
          type="com.jack.androidjetpack.login.ButtonClickListener" />
    

    第三步:在XML文件中添加响应事件

    <Button
         android:id="@+id/login"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:onClick="@{btnHandler::click}"
         android:text="@string/action_sign_in"
    />
    

    第四步:在Controller里面进行关联

    class LoginActivity : AppCompatActivity() {
    
        var binding: ActivityLoginBinding? = null
        var login: Login? = null
        var clickListener:ButtonClickListener?=null
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            binding = DataBindingUtil.setContentView(this, R.layout.activity_login)
            login = Login("jack", "123456")
            clickListener= ButtonClickListener()
            binding?.setLogin(login)
            // 这一步必须要,否则点击没反应
            binding?.btnHandler = clickListener
        }
    }
    

    进阶2:BindingAdapter
    使用DataBinding库时,DataBinding会针对控件属性生成对应的XXXBindingAdapter类,如TextViewBindingAdapter类,其对TextView的每个可以使用DataBinding的属性都生成了对应的方法,而且每个方法都使用了@BindingAdapter注解,注解中的参数就是对应View的属性。

    自定义BindingAdapter
    编写一个处理图片的自定义BindingAdapter类。然后定义一个静态方法,主要用于添加 BindingAdapter 注解,注解值是 ImageView 控件自定义的属性名,如下所示。

    class ImageBindingAdapter {
    
        companion object {
            @BindingAdapter("url")
            @JvmStatic
            fun loadImage(view: ImageView?, url: String?) {
                var realValue: String? = null
                if (url.equals("null")) {
                    realValue = "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fattach.bbs.miui.com%2Fforum%2F201412%2F27%2F111335whdlgodddosl6swq.jpg&refer=http%3A%2F%2Fattach.bbs.miui.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1632887161&t=f6a13d2953ea7be364a111344b72a654"
                }
                if (!TextUtils.isEmpty(realValue)) {
                    Glide.with(view!!)
                        .load(realValue)
                        .into(view)
                }
            }
        }
    }
    

    在XML文件里面直接引用

    <ImageView
          android:layout_width="300dp"
          android:layout_height="200dp"
          android:layout_below="@+id/login"
          android:layout_marginTop="16dp"
          app:url="@{`null`}" />
    

    有时候,我们需要自定义多个属性,那如何处理呢?和一个参数一样,我们只需要使用BindingAdapter添加参数即可,如下所示:

    class ImageBindingAdapter {
    
        companion object {
            @BindingAdapter(value = ["url", "placeholder", "error"])
            @JvmStatic
            fun loadImage(view: ImageView?, url: String?, placeholder: Drawable?, error: Drawable?) {
                var realValue: String? = null
                if (url.equals("null")) {
                    realValue =
                        "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fattach.bbs.miui.com%2Fforum%2F201412%2F27%2F111335whdlgodddosl6swq.jpg&refer=http%3A%2F%2Fattach.bbs.miui.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1632887161&t=f6a13d2953ea7be364a111344b72a654"
                }
                if (!TextUtils.isEmpty(realValue)) {
                    val options = RequestOptions()
                    options.placeholder(placeholder)
                    options.error(error)
                    Glide.with(view!!)
                        .load(realValue)
                        .apply(options)
                        .into(view)
                }
            }
        }
    }
    

    对应的XML文件为:

    <ImageView
           android:layout_width="300dp"
           android:layout_height="200dp"
           android:layout_below="@+id/login"
           android:layout_marginTop="16dp"
           app:placeholder="@{ContextCompat.getDrawable(context, R.mipmap.ic_launcher)}"
           app:error="@{ContextCompat.getDrawable(context, R.mipmap.ic_launcher)}"
           app:url="@{`null`}" />
    

    别忘记导入对应的依赖

    <import type="androidx.core.content.ContextCompat" />
    <import type="com.jack.androidjetpack.R"/>
    

    进阶3:双向绑定
    DataBinding的本身是对View层状态的一种观察者模式的实现,通过让View与ViewModel层可观察的对象进行绑定,当ViewModel层数据发生改变时,View层也会自动进行UI的更新,这种场景称之为单向绑定。

    但是在实际的开发过程中,单向绑定并不能满足所有的需求。例如有下面的场景:如果布局中有一个EditText,当用户在输入框中输入内容时,我们希望对应的Model类能够实时更新,这就需要双向绑定,DataBinding同样支持这样的能力。

    实现双向绑定需要用到ObservableField类,它能够将普通的数据对象包装成一个可观察的数据对象,数据类型可以是基本数据类型、变量、集合,也可以是自定义类型。

    第一步:修改我们的实体类

    class Login : BaseObservable() {
    
        @get:Bindable
        var userName: String? = null
            set(userName) {
                field = userName
                notifyPropertyChanged(BR.userName)
            }
    
        @get:Bindable
        var password: String? = null
            set(password) {
                field = password
                 notifyPropertyChanged(BR.userName)
            }
    }
    

    第二步:修改我们的实体类

      <EditText
                android:id="@+id/username"
                android:layout_width="match_parent"
                android:layout_height="40dp"
                android:layout_marginStart="24dp"
                android:layout_marginTop="96dp"
                android:layout_marginEnd="24dp"
                android:hint="@string/prompt_name"
                android:inputType="textEmailAddress"
                android:selectAllOnFocus="true"
                android:text="@={login.userName}" />
    
            <EditText
                android:id="@+id/password"
                android:layout_width="match_parent"
                android:layout_height="40dp"
                android:layout_below="@+id/username"
                android:layout_marginStart="24dp"
                android:layout_marginTop="8dp"
                android:layout_marginEnd="24dp"
                android:hint="@string/prompt_password"
                android:imeActionLabel="@string/action_sign_in_short"
                android:imeOptions="actionDone"
                android:inputType="textPassword"
                android:selectAllOnFocus="true"
                android:text="@={login.password}" />
    

    将我们原来的@{login.userName}换成@={login.userName}就可以了。

    四、DataBinding的实战

    在我们的实际开发过程中,RecyclerView算是使用非常频繁的,接下来我们就看看如何使用DataBinding对RecyclerView进行处理。
    第一步:定义我们的实体类

    class UserModel: BaseObservable() {
    
        @get:Bindable
        var name: String? = null
            set(name) {
                field = name
                notifyPropertyChanged(BR.name)
            }
    
        @get:Bindable
        var address: String? = null
            set(address) {
                field = address
                notifyPropertyChanged(BR.address)
            }
    
        @get:Bindable
        var age: String? = null
            set(age) {
                field = age
                notifyPropertyChanged(BR.age)
            }
    }
    

    第二步:创建我们的适配器的布局

    <layout xmlns:android="http://schemas.android.com/apk/res/android">
    
        <data>
            <variable
                name="user"
                type="com.jack.androidjetpack.list.UserModel" />
        </data>
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:orientation="horizontal">
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{user.name}" />
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{user.address}" />
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{user.age}" />
        </LinearLayout>
    </layout>?
    

    第三步:创建我们的适配器

    class UserAdapter : RecyclerView.Adapter<UserAdapter.ViewHolder>() {
    
        var dataList: MutableList<UserModel> = mutableListOf()
            set(value) {
                field = value
                notifyDataSetChanged()
            }
    
    
        inner class ViewHolder(binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root) {
    
            var adapterBinding: AdapterListBinding? = null
    
            init {
                adapterBinding = binding as AdapterListBinding
            }
        }
    
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
            val binding: ViewDataBinding = DataBindingUtil.inflate(
                LayoutInflater.from(parent.context),
                R.layout.adapter_list,
                parent,
                false
            )
            return ViewHolder(binding)
        }
    
        override fun onBindViewHolder(holder: ViewHolder, position: Int) {
            val userModel = dataList[position]
            holder.adapterBinding?.user = userModel
        }
    
        override fun getItemCount() = dataList.size
    
    }
    

    第四步:在Activity里面完成功能

    class ListActivity : AppCompatActivity() {
    
        private var userModelList: MutableList<UserModel>? = mutableListOf()
        private var activityListBinding: ActivityListBinding? = null
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            activityListBinding = DataBindingUtil.setContentView(this, R.layout.activity_list)
            initData()
            initRecyclerView()
        }
    
        private fun initData() {
            for (i in 0..9) {
                val userModel = UserModel()
                userModel.name = "jack" + 1
                userModel.address = "beijing$i"
                userModel.age = "age$i"
                userModelList?.add(userModel)
            }
        }
    
        private fun initRecyclerView() {
            val layoutManager = LinearLayoutManager(this)
            activityListBinding?.recyclerView?.layoutManager = layoutManager
            val adapter = UserAdapter()
            activityListBinding?.recyclerView?.adapter = adapter
            adapter.dataList = userModelList!!
        }
    }
    

    好了,关于DataBinding的内容就介绍到这里了,当然,实际开发过程中DataBinding使用的远远不止这些,需要我们平时多总结,多思考才能有更多的收获。

    相关文章

      网友评论

          本文标题:Android JetPack系列之——DataBinding

          本文链接:https://www.haomeiwen.com/subject/bzvniltx.html