Jetpack 之 DataBinding 小白入手

声明 : https://www.jianshu.com/p/714062a9af75
目录:

简介
原理
使用方法

1,一节界面数据绑定(基础使用)
2,二级界面的绑定
3,响应事件
4,BindAdapter
5,RecycleView 绑定机制
6,双向绑定

简介:

  DataBinding 是一种库,借助该库,可以使用声明性格式(而非程序化地)将布局中的界面组件绑定到应用中的数据源。简单来说,就是帮我们实现 view 和 data 绑定的工具.DataBinding 的出现让布局文件承担了部分原本属于界面的工作,使页面与布局之间的耦合度进一步降低.
  :项目简洁,可读性高,部分与 ui 控件的代码都在布局文件里完成.
  :不再需要 findViewById()
  :布局文件可以包含简单的业务逻辑.ui 控件能够直接与数据模型中的字段绑定,甚至能响应用户的交互.

原理:

  先略过去吧....太复杂了....,就看看下面的使用方法吧.....

使用方法:

一级界面数据绑定
1,build.gradle
android {
    //....
    dataBinding {
        enabled = true
    }
}

  启动绑定数据

2,创建 Person 对象
class Person(var name: String?, var age: Int, var sex: String?)
3,修改布局文件 activity_data_binding.xml

  在布局文件外层加入标签,可以手动添加,也可以将鼠标移到文件的根目录,单击小灯泡的下来三角框,选中 Convert to data binding layout,AS会自动生成代码.

image.png



 

 

 

 


  我们所做的修改是在 ui 布局的最外层加上一个 layout 的标签,并将命名空间 xmlns 从ConstraintLayout移到了标签中.这样做的目的是,告诉 DataBinding 库,我们要对该布局进行绑定,此时,rebuild 该项目,DataBinding 库会为我们生成绑定该布局文件所需要的类.

4,实例化布局

  有了 DataBinding 之后,就可以告别 findViewById()了,我们可以通过DataBindingUtil.setContentView()方法实例化布局文件,该方法返回实例化后布局文件对象,名字和布局文件的名字一样,并在后面加上 Binding.

class DataBindingActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        var activityDataBindingBinding: ActivityDataBindingBinding =
            DataBindingUtil.setContentView(
                this,
                R.layout.activity_data_binding
            )
    }
}

比如,我的布局文件名字叫 activity_data_binding.xml ,布局文件对象叫 ActivityDataBindingBinding.

5,将数据传递到布局文件

  为了减轻 Activity 的工作量,让布局文件也承担一部分工作,所以要将 Person 对象传递到布局文件.具体做法如下
首先在布局文件标签中定义个布局变量,指定类型和名字,名字可以随意定义.

    
        
    

  标签 用于放置 ui 控件所需要的数据,数据类型可以自定义,比如代码中是的 Person 类,也可以是基本类型.

    
        
    

然后在 Activty 中通过 setPersonD()方法,将 Person 对象传递给布局文件中对应的布局变量.

        activityDataBindingBinding.personD = Person("张三", 30, "男")

绑定布局文件和成员变量

     

        

   布局文件用@{} 表达式为控件赋值
在布局文件引用静态类
  有时候我们需要在布局文件中引用一些 java/kotlin 工具类,帮助我们处理简单的逻辑.

class Utils {
    companion object {
        @JvmStatic
        fun getStr(a: Int): String {
            return a.toString()
        }
    }
}

  我们可以再布局文件中通过 标签导入静态工具类

    

        
    

  接着在控件中使用

        
6,完整的布局和 Activty 文件



  

      

      
  

  

      

      


      
  

class DataBindingActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        var activityDataBindingBinding: ActivityDataBindingBinding =
           DataBindingUtil.setContentView(
                this,
                R.layout.activity_data_binding
            )
        //单向
        activityDataBindingBinding.personD = Person("张三", 30, "男")
    }
}

运行截图:


图一.png
二级界面的绑定

   在一级界面布局中,设置好布局变量personD之后,便可以接受来自 Activty 的数据,进而将数据和控件进行绑定,不仅如此,布局变量personD同时也是命名空间 xmlns:app 的一个属性,一级界面正是通过命名空间xmlns:app引用布局变量 personD ,将数据对象传递给二级页面.具体代码如下:
二级页面 layout_second.xml
   将 sex 的 view 挪到二级页面去了




   

       
   

   

       
   

一级页面 activity_data_binding.xml




    
        
    

    
          
        

    

运行出来的截图和图一是一样的啦,就粘贴过来啦

响应事件
1,编写一个类,用于接受和响应 Button 的 onClick 事件.

命名为 HandleClickListener可以单独写一个文件,也可以写在 Activity 中,作为内部类.

    inner class HandleClickListener {
        fun showToast(view: View) {
            Toast.makeText(view.context, "哈哈哈", Toast.LENGTH_LONG).show()
        }
    }

⚠️⚠️⚠️**在定义事件方法名称时需要注意:方法的名称可以和原始函数名称不一样,方法参数和返回值必须和原始的回调函数保持一致。不然会报错,比如:clickFirst(View view)必须要有view参数,如果没有会报错.**⚠️⚠️

2,在布局文件中定义并使用


    
        
    

    
        
        

   也可以用双冒号android:onClick="@{handleC::showToast}"

3,在 Avtivity 中实例化HandleClickListener类
class DataBindingActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        var activityDataBindingBinding: ActivityDataBindingBinding =
            DataBindingUtil.setContentView(
                this,
                R.layout.activity_data_binding
            )
        //监听
        activityDataBindingBinding.handleC = HandleClickListener()
      }
}

运行截图


image.png
自定义 BindingAdapter

   在 gradle 启动 DataBinding 库的时候,就会为我们生成所需要的各种类,其中包括大量针对 ui 控件的,名为 XXXBindingAdapter 的类,这些类中包含各种静态方法,并且在这些静态方法前都有@BindingAdapter 标签,标签中的别名对应于 ui 控件在布局文件中的属性.
   看个例子,TextView的 TextViewBindingAdapter 的部分源码:

public class TextViewBindingAdapter {

    private static final String TAG = "TextViewBindingAdapters";
    @SuppressWarnings("unused")
    public static final int INTEGER = 0x01;
    public static final int SIGNED = 0x03;
    public static final int DECIMAL = 0x05;

    @BindingAdapter("android:text")
    public static void setText(TextView view, CharSequence text) {
        final CharSequence oldText = view.getText();
        if (text == oldText || (text == null && oldText.length() == 0)) {
            return;
        }
        if (text instanceof Spanned) {
            if (text.equals(oldText)) {
                return; // No change in the spans, so don't set anything.
            }
        } else if (!haveContentsChanged(text, oldText)) {
            return; // No content changes, so don't set anything.
        }
        view.setText(text);
    }
    //........
}

   Databinding 库以静态方法的形式为 ui 控件的各个属性绑定了响应的代码.若开发人员在 UI控件的属性中使用了表达式,那么当布局文件被渲染时,属性所绑定的方法会被自动调用.

   比如,当 TextView 被渲染时,android:text属性会自动调用 TextViewBindingAdapter.setText()方法.UI控件通过简单的属性设置,便可以在布局文件中调用所绑定的方法.

那么我们就来自定义一个处理图片的 BindAdapter 类:
1, 添加 Glide 库
    implementation 'com.github.bumptech.glide:glide:4.12.0'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
2, 添加网络权限
 
3, 编写处理图片的 BindAdapter 类
class ImageViewBindingAdapter {
    companion object{
    @JvmStatic
    @BindingAdapter("imageV")
    fun setImage(image: ImageView, imageUrl: String?) {
        if (!TextUtils.isEmpty(imageUrl)) {
            Glide.with(image.context).load(imageUrl).into(image)
        } else {
            image.setImageResource(R.mipmap.ic_launcher)
            }
        }
    }
}

   BindAdapter中的方法均为静态方法,第 1 个参数是调用者本身,即 ImageView,第 2 个参数是布局文件在调用该方法传递过来的参数.在静态方法前面需要加入@BindingAdapter()标签,并为该方法起一个别名,此处为 imageV.布局文件正式通过别名来调用方法的.

4, 修改布局文件

   在布局文件定义 String ,传递图片地址.

        

   ImageView调用



    
        
    

    
  
        
    

5, Activity 中设置布局文件变量
class DataBindingActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        var activityDataBindingBinding: ActivityDataBindingBinding =
            DataBindingUtil.setContentView(
                this,
                R.layout.activity_data_binding
            )
        //自定义 adapter
        activityDataBindingBinding.internetImageUrl =
            "https://dss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3436121203,3749922833&fm=26&gp=0.jpg"
    }
}

运行截图:


image.png

   在上面的示例中,我们做了接受网络图片地址的展示,如果还希望,在接受网络图片的时候,也能接受本地图片资源作为参数,这样,当网络图片地址为空的时候,则显示本地图片资源所制定的图片.根据这个需求,我们优化一下 BindAdapter

class ImageViewBindingAdapter {
    companion object {
        @JvmStatic
        @BindingAdapter(value = ["imageV", "defaultRes"], requireAll = false)
        fun setImage(image: ImageView, imageUrl: String?, imageResource: Int) {
            if (!TextUtils.isEmpty(imageUrl)) {
                Glide.with(image.context).load(imageUrl).into(image)
            } else {
                image.setImageResource(imageResource)
            }
        }
    }
}

   在@BindingAdapter标签中,方法参数以 value = ["", ""]的形式存在,变量 requireAll 作用是告诉 DataBinding 库这些参数是否都要赋值,默认是 true.

资源文件:




    

        

        
    
        
        
        


Activity:

class DataBindingActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        var activityDataBindingBinding: ActivityDataBindingBinding =
            DataBindingUtil.setContentView(
                this,
                R.layout.activity_data_binding
            )
        //自定义 adapter
        activityDataBindingBinding.internetImageUrl =
            "https://dss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3436121203,3749922833&fm=26&gp=0.jpg"
        activityDataBindingBinding.imageRes = R.mipmap.ic_launcher
   }
}
RecycleView 绑定机制
1,布局文件



    

    

    

        
    

   RecycleView 的布局

2,item 布局 item_recycle.xml



    

        

        

    

    

        

        

        
    

   RecycleView 的 item 布局文件,分别绑定不同的数据.

3,编写 Adapter
class RecycleViewAdapter(private val list: MutableList) :
    RecyclerView.Adapter() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        return MyViewHolder(DataBindingUtil.inflate(
                LayoutInflater.from(parent.context), R.layout.item_recycle,parent,  false ))
    }

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        val personViewModel = list[position]
        holder.itemRecycleBinding.personI = personViewModel
    }

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

    inner class MyViewHolder(var itemRecycleBinding: ItemRecycleBinding) 
        : RecyclerView.ViewHolder(itemRecycleBinding.root) //.root 返回的是布局的最外层 ui 视图
}

   编写 Adapter 需要注意三个地方:
   ⚠️ 在 onCreateViewHolder()方法中,通过 DataBindingUtil.inflate()实例化布局.
   ⚠️在 onBindViewHolder 方法中,设置布局变量
   ⚠️ ItemRecycleBinding 是 DataBinding 为布局文件 item_recycle.xml 生成的对象

4,Activity 添加数据
class DataBindingActivity : AppCompatActivity() {
    private val list = mutableListOf()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        var activityDataBindingBinding: ActivityDataBindingBinding =
            DataBindingUtil.setContentView(
                this,
                R.layout.activity_data_binding
            )
        //recycleview
        for (i in 1..100) {
            var personViewModel1 = Person("张三$i", i, "女")
            list.add(personViewModel1)
        }
        activityDataBindingBinding.recycle.layoutManager = LinearLayoutManager(this)
        activityDataBindingBinding.recycle.adapter = RecycleViewAdapter(list)
    }
}

运行截图:


image.png
双向绑定
1,编写PersonViewModel类
class PersonViewModel(name: String, age: Int, sex: String) {
    var name = ObservableField()
    var age = ObservableField()
    var sex = ObservableField()

    init {
        this.name.set(name)
        this.age.set(age)
        this.sex.set(sex)

    }
}

   ObservableField 关键字将普通对象包装成可观察对象,可以包装基本类型,集合数组类型,自定义类型的数据.当数据发生变化时,界面所用的与之相关的数据会随之刷新.

2,编写布局文件


    
      

        
        
    

    

        

        

   android:text="@={personVM.name}" 采用@={}表达式完成双向绑定
  

3,编写 Activty
class DataBindingActivity : AppCompatActivity() {
    var personViewModel = PersonViewModel("张三",20,"女")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        var activityDataBindingBinding: ActivityDataBindingBinding =
            DataBindingUtil.setContentView(
                this,
                R.layout.activity_data_binding
            )
      
        //双向
        activityDataBindingBinding.personVM = personViewModel
      
    }

    inner class HandleClickListener {
        fun showToast(view: View) {
            Toast.makeText(view.context, personViewModel.name.get(), Toast.LENGTH_LONG).show()
        }
    }

}

运行图片


嘿嘿嘿.gif

END

image.png

你可能感兴趣的:(Jetpack 之 DataBinding 小白入手)