ViewBinding用法

Posted ximen502_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ViewBinding用法相关的知识,希望对你有一定的参考价值。

视图绑定

通过视图绑定功能,您可以更轻松地编写可与视图交互的代码。在模块中启用视图绑定之后,系统会为该模块中的每个 XML 布局文件生成一个绑定类。绑定类的实例包含对在相应布局中具有 ID 的所有视图的直接引用。
在大多数情况下,视图绑定会替代 findViewById

设置说明

注意:视图绑定在 android Studio 3.6 Canary 11 及更高版本中可用。

视图绑定功能可按模块启用。要在某个模块中启用视图绑定,请将 viewBinding 元素添加到其 build.gradle 文件中,如下例所示:

android 
    ...
    viewBinding 
        enabled = true
    

然而,在后续的as版本中这个引用又变了,不然build可能报错,需要如下引用才行:

android 
	buildFeatures
		viewBinding true
	

如果您希望在生成绑定类时忽略某个布局文件,请将 tools:viewBindingIgnore=“true” 属性添加到相应布局文件的根视图中:

<LinearLayout
        ...
        tools:viewBindingIgnore="true" >
    ...
</LinearLayout>

用法

为某个模块启用视图绑定功能后,系统会为该模块中包含的每个 XML 布局文件生成一个绑定类。每个绑定类均包含对根视图以及具有 ID 的所有视图的引用。系统会通过以下方式生成绑定类的名称:将 XML 文件的名称转换为驼峰式大小写,并在末尾添加“Binding”一词。

例如,假设某个布局文件的名称为 result_profile.xml

<LinearLayout ... >
        <TextView android:id="@+id/name" />
        <ImageView android:cropToPadding="true" />
        <Button android:id="@+id/button"
            android:background="@drawable/rounded_button" />
    </LinearLayout>

所生成的绑定类的名称就为 ResultProfileBinding。此类具有两个字段:一个是名为 nameTextView,另一个是名为 buttonButton。该布局中的 ImageView 没有 ID,因此绑定类中不存在对它的引用。

每个绑定类还包含一个 getRoot() 方法,用于为相应布局文件的根视图提供直接引用。在此示例中,ResultProfileBinding 类中的 getRoot() 方法会返回 LinearLayout 根视图。

以下几个部分介绍了生成的绑定类在 Activity 和 Fragment 中的使用。

在 Activity 中使用视图绑定

如需设置绑定类的实例以供 Activity 使用,请在 Activity 的 onCreate() 方法中执行以下步骤:

  1. 调用生成的绑定类中包含的静态 inflate() 方法。此操作会创建该绑定类的实例以供 Activity 使用。
  2. 通过调用 getRoot() 方法或使用 Kotlin 属性语法获取对根视图的引用。
  3. 将根视图传递到 setContentView(),使其成为屏幕上的活动视图。
   private lateinit var binding: ResultProfileBinding

   override fun onCreate(savedInstanceState: Bundle) 
       super.onCreate(savedInstanceState)
       binding = ResultProfileBinding.inflate(layoutInflater)
       val view = binding.root
       setContentView(view)
   
   

您现在即可使用该绑定类的实例来引用任何视图:

    binding.name.text = viewModel.name
    binding.button.setOnClickListener  viewModel.userClicked() 

在 Fragment 中使用视图绑定

如需设置绑定类的实例以供 Fragment 使用,请在 Fragment 的 onCreateView() 方法中执行以下步骤:

  1. 调用生成的绑定类中包含的静态 inflate() 方法。此操作会创建该绑定类的实例以供 Fragment 使用。
  2. 通过调用 getRoot() 方法或使用 Kotlin 属性语法获取对根视图的引用。
  3. 从 onCreateView() 方法返回根视图,使其成为屏幕上的活动视图。

注意:inflate() 方法会要求您传入布局膨胀器。如果布局已膨胀,您可以调用绑定类的静态 bind() 方法。如需了解详情,请查看视图绑定 GitHub 示例中的例子。

    private var _binding: ResultProfileBinding? = null
    // This property is only valid between onCreateView and
    // onDestroyView.
    private val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? 
        _binding = ResultProfileBinding.inflate(inflater, container, false)
        val view = binding.root
        return view
    

    override fun onDestroyView() 
        super.onDestroyView()
        _binding = null
    
    

您现在即可使用该绑定类的实例来引用任何视图:

    binding.name.text = viewModel.name
    binding.button.setOnClickListener  viewModel.userClicked() 

注意:Fragment 的存在时间比其视图长。请务必在 Fragment 的 onDestroyView() 方法中清除对绑定类实例的所有引用。

GitHub示例如下代码所示:

class BindFragment : Fragment(R.layout.fragment_blank) 

    // Scoped to the lifecycle of the fragment's view (between onCreateView and onDestroyView)
    private var fragmentBlankBinding: FragmentBlankBinding? = null

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) 
        super.onViewCreated(view, savedInstanceState)
        val binding = FragmentBlankBinding.bind(view)
        fragmentBlankBinding = binding
        binding.textViewFragment.text = getString(string.hello_from_vb_bindfragment)
    

    override fun onDestroyView() 
        // Consider not storing the binding instance in a field, if not needed.
        fragmentBlankBinding = null
        super.onDestroyView()
    

与 findViewById 的区别

与使用 findViewById 相比,视图绑定具有一些很显著的优点:

  • Null 安全:由于视图绑定会创建对视图的直接引用,因此不存在因视图 ID 无效而引发 Null 指针异常的风险。此外,如果视图仅出现在布局的某些配置中,则绑定类中包含其引用的字段会使用 @Nullable 标记。
  • 类型安全:每个绑定类中的字段均具有与它们在 XML 文件中引用的视图相匹配的类型。这意味着不存在发生类转换异常的风险。

这些差异意味着布局和代码之间的不兼容将会导致构建在编译时(而非运行时)失败。

与数据绑定的对比

视图绑定和数据绑定均会生成可用于直接引用视图的绑定类。但是,视图绑定旨在处理更简单的用例,与数据绑定相比,具有以下优势:

  • 更快的编译速度:视图绑定不需要处理注释,因此编译时间更短。
  • 易于使用:视图绑定不需要特别标记的 XML 布局文件,因此在应用中采用速度更快。在模块中启用视图绑定后,它会自动应用于该模块的所有布局。

反过来,与数据绑定相比,视图绑定也具有以下限制:

  • 视图绑定不支持布局变量或布局表达式,因此不能用于直接在 XML 布局文件中声明动态界面内容。
  • 视图绑定不支持双向数据绑定。

考虑到这些因素,在某些情况下,最好在项目中同时使用视图绑定和数据绑定。您可以在需要高级功能的布局中使用数据绑定,而在不需要高级功能的布局中使用视图绑定。

内容来源:
https://developer.android.google.cn/topic/libraries/view-binding?hl=zh-cn

Android 基于Jetpack的MVVM架构入门指南

Android MVVM架构入门指南

目录

Android MVVM架构入门指南

架构组件简介

View Binding

简介

View Binding 的优点

用法

ViewModel

简介

ViewModel 的生命周期

用法

LiveData

简介

LiveData 的优势

LiveData的子类

Transformations

用法

Data Binding

简介

用法

双向绑定

Lifecycles

简介

主要类

用法

生命周期感知型组件的最佳做法

实现了MVVM架构的Demo


架构组件简介

Android 架构组件是一系列的组件库,可以帮助我们设计稳健、可测试且易维护的应用,主要和常用的组件包括:View Binding 、Data Binding、Lifecycle、LiveData、ViewModel

如上Android架构图,有四部分主要组件,每个组件都有其责任

  • Activity 和Fragment代表的View层,不处理业务逻辑只显示视图、处理用户交互、观察数据并展示

  • ViewModel+LiveData层,ViewModel向数据仓库Repository中请求数据,然后通过LiveData发送到View层

  • Repository层,它不是一个Android组件,而是一个普通的类,它负责从所有可用的源来获取数据

  • 数据层,提供数据源的一层,主要是网络请求框架、Room数据库等

View Binding

简介

主要作用是为了替代 findViewById 在启动View Binding后,系统会为该模块下的每个xml生成一个绑定类,这个类的类名是以xml布局文件名去掉下划线后,单词首字母大写加上Binding命名的。

例如:activity_main.xml ---> ActivityMainBinding

View Binding 的优点

  • 空安全:View Binding会创建对视图的直接引用,因此不存在因视图 ID 无效而引发空指针异常的风险。(我们手动findeViewById()时容易传入无效的id)

  • 类型安全:每个绑定类中的字段均具有与它们在 XML 文件中引用的视图相匹配的类型。这意味着不存在发生类转换异常的风险。 val button:Button=findViewById<RecyclerView>(R.id.recyclerview)

详细介绍请参见Google官方文档: https://developer.android.google.cn/topic/libraries/view-binding

用法

View Binding 按模块启用,在app模块下的build.gradle添加如下配置

android 
        ...
        viewBinding 
            enabled = true
        
    
    
    或者使用buildFeatures方式但需要满足如下条件:
    1、Android Studio 版本 : 最低 4.1
    2、Gradle 版本 : 最低版本 6.6.1
    3、Gradle 插件版本配置 : 最低版本 4.1.0
    
    android 
      ...
       buildFeatures 
          viewBinding true
      

​
    

如果想忽略某个布局文件,可将 tools:viewBindingIgnore="true" 属性添加到相应布局文件的根视图

<?xml version="1.0" encoding="utf-8"?>
<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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:viewBindingIgnore="true"
    tools:context=".MainActivity">
    
       <TextView
        android:id="@+id/name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
        
        ...
</androidx.constraintlayout.widget.ConstraintLayout>
    

1. Activity中使用View Binding

  1. 调用生成的绑定类中包含的静态 inflate() 方法。此操作会创建该绑定类的实例以供 Activity 使用

  2. 通过调用 getRoot() 方法获取对根视图的引用

  3. 调用 setContentView()方法将根视图传入

    private lateinit var binding: ActivityMainBinding
​
    override fun onCreate(savedInstanceState: Bundle) 
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)
    
    binding.name.text ="Wilfried"
    binding.name.setOnClickListener   

2. Fragment中使用View Binding

Fragment中使用View Binding的步骤根Activity类似,主要区分在于是在哪个方法中进行绑定操作onCreateView、onViewCreated

onCreateView

    lateinit var binding: FragmentMainBinding
​
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? 
        binding = FragmentMainBinding.inflate(inflater, container, false)
        return binding.root
    

onViewCreated

    lateinit var binding: FragmentMainBinding
​
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) 
        super.onViewCreated(view, savedInstanceState)
        binding = FragmentMainBinding.bind(view)
    

3. ListAdapter中使用View Binding

​
class MyListAdapter : ListAdapter<Word, MyListAdapter.WordViewHolder>(WORDS_COMPARATOR) 
​
    class WordViewHolder(private val viewBinding: RecyclerviewItemBinding) :
        RecyclerView.ViewHolder(viewBinding.root) 
​
        fun bind(text: Word?) 
            viewBinding.textView.text = text?.word
        
​
        companion object 
            fun create(parent: ViewGroup): WordViewHolder 
                val bind = RecyclerviewItemBinding
                    .bind(LayoutInflater.from(parent.context)
                            .inflate(R.layout.recyclerview_item, parent, false)
                    )
                return WordViewHolder(bind)
            
        
    
​
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WordViewHolder 
        return WordViewHolder.create(parent)
    
​
    override fun onBindViewHolder(holder: WordViewHolder, position: Int) 
        holder.bind(getItem(position))
    
​
    companion object 
        private val WORDS_COMPARATOR = object : DiffUtil.ItemCallback<Word>() 
            override fun areItemsTheSame(oldItem: Word, newItem: Word): Boolean 
                return oldItem === newItem
            
​
            override fun areContentsTheSame(oldItem: Word, newItem: Word): Boolean 
                return oldItem.word == newItem.word
            
        
    

ViewModel

简介

ViewModel 主要是用来存储和管理与UI相关的数据的,将一个Activity或Fragment组件相关的数据逻辑提取出来,并能适配UI组件的生命周期,如当屏幕旋转Activity重建后,ViewModel中的数据依然有效 ViewModel一般都是会配合LiveData使用

ViewModel 的生命周期

ViewModel 对象存在的时间范围是获取 ViewModel 时传递给 ViewModelProvider 的 Lifecycle(Activity、Fragment、所有实现LifecycleOwner的生命周期对象)

我们在首次调用 Activity 对象的 onCreate() 方法时请求 ViewModel。系统有可能会在 Activity 的整个生命周期内多次调用 onCreate(),但是ViewModel只要一个,比如在旋转设备屏幕时重走生命周期,此时依然可以拿到ViewModel中最新的数据。

由上图可以看出,ViewModel 存在的时间范围是从首次请求 ViewModel 直到 Activity 完成并销毁。

详细介绍请参见Google官方文档: https://developer.android.google.cn/topic/libraries/architecture/viewmodel

用法

定义ViewModel

class MyViewModel : ViewModel() 
    private val users: MutableLiveData<List<User>> by lazy 
        MutableLiveData<List<User>>().also 
            loadUsers()
        
    
​
    fun getUsers(): LiveData<List<User>> 
        return users
    
​
    private fun loadUsers() 
        // Do an asynchronous operation to fetch users.
        ...
        users.value = usersInfoList 
    

Activity 中使用ViewModel

1. 默认实现方式

class MyActivity : AppCompatActivity() 

    override fun onCreate(savedInstanceState: Bundle?) 

        val viewModel = ViewModelProviders
                .of(this).get(MainViewModel::class.java)
        
        model.getUsers().observe(this, Observer<List<User>> users ->
            // update UI
        )
    

2. 通过activity-ktx扩展库

class MyActivity : AppCompatActivity() 

    override fun onCreate(savedInstanceState: Bundle?) 
        // Use the 'by viewModels()' Kotlin property delegate
        // from the activity-ktx artifact
        val model: MyViewModel by viewModels()
        
        model.getUsers().observe(this, Observer<List<User>> users ->
            // update UI
        )
    

3. 过activity-ktx扩展库,自定义 ViewModelProvider.Factory(用于ViewModel需要传参数的情况)

  • MainActivity

class MyActivity : AppCompatActivity() 

    override fun onCreate(savedInstanceState: Bundle?) 

    private val wordViewModel: WordViewModel by viewModels 
        WordViewModelFactory((application as WordsApplication).repository)
    
        
        model.getUsers().observe(this, Observer<List<User>> users ->
            // update UI
        )
    

  • WordViewModel

class WordViewModel(private val repository: WordRepository) : ViewModel() 

    val allWords: LiveData<List<Word>> = repository.allWords.asLiveData()

    fun insert(word: Word) = viewModelScope.launch 
        repository.insert(word)
    
class WordViewModelFactory(private val repository: WordRepository) : ViewModelProvider.Factory 
    override fun <T : ViewModel> create(modelClass: Class<T>): T 
        if (modelClass.isAssignableFrom(WordViewModel::class.java)) 
            @Suppress("UNCHECKED_CAST")
            return WordViewModel(repository) as T
        
        throw IllegalArgumentException("Unknown ViewModel class")
    

LiveData

简介

LiveData 是一种可观察的数据存储器类。与常规的可观察类不同,LiveData 具有生命周期感知能力,意指它遵循其他应用组件(如 Activity、Fragment 或 Service)的生命周期。这种感知能力可确保 LiveData 仅更新处于活跃生命周期状态的应用组件观察者

LiveData 的优势

  1. 观察者会绑定到 Lifecycle 对象(所有实现了所有实现LifecycleOwner的生命周期对象),并在其关联的生命周期遭到销毁后进行自我清理。

  2. 如果生命周期变为非活跃状态,它会在再次变为活跃状态时接收最新的数据。例如,曾经在后台的 Activity 会在返回前台后立即接收最新的数据。

  3. 如果由于配置更改(如设备旋转)而重新创建了 Activity 或 Fragment,它会立即接收最新的可用数据。

LiveData的子类

MutableLiveData

实现接口LiveData,并提供两个方法 setValue(T) 和 postValue(T) 用于将数据分发出去

setValue(T) 只能在主线程中调用,postValue(T) 可以在任何线程中调用。

MediatorLiveData

继承自MutableLiveData,它可以作为中间人的角色监听其他LiveData,通过addSource进行注册LiveData,当数据更新时通过MediatorLiveData倒一手再进行处理。提供如下两个方法,

addSource(LiveData< S> source, Observer<? super S> onChanged)方法

removeSource(LiveData< S> toRemote)方法

案例:如果我们只需要liveData1发出的10个值,并将其合并到liveDataMerger中。收到10个值之后,我们就停止监听liveData1并将其删除。

 liveDataMerger.addSource(liveData1, new Observer() 
      private int count = 1;

      @Override public void onChanged(@Nullable Integer s) 
          count++;
          liveDataMerger.setValue(s);
          if (count > 10) 
              liveDataMerger.removeSource(liveData1);
          
      
 );

Transformations

 //被监听数据
 private val count = MutableLiveData<Int>(1)

 private val mapCount: LiveData<Int> = Transformations.map(count) 
     it + 1
 
 //与map()的区别是必须返回一个 MutableLiveData 对象
 private val switchMapCount: LiveData<Int> = Transformations.switchMap(count) 
        MutableLiveData<Int>(it!! + 1)
 

详细介绍请参见Google官方文档: https://developer.android.google.cn/topic/libraries/architecture/livedata

用法

  1. 创建 LiveData 的实例以存储某种类型的数据。这通常在 ViewModel 类中完成。

class MyViewModel : ViewModel() 

    // Create a LiveData with a String
    val livedata: MutableLiveData<String> =  MutableLiveData<String>()
    
    

  1. 在界面控制器(如 Activity 或 Fragment)中创建 Observer 对象。

  2. 使用 observe() 方法将 Observer 对象附加到 LiveData 对象。

        myViewModel.livedata.observe(this, Observer 

        )
  1. 可以使用 observeForever(Observer) 方法在没有关联的 LifecycleOwner 对象的情况下注册一个观察者。在这种情况下,观察者会被视为始终处于活跃状态,可以通过调用 removeObserver(Observer) 方法来移除这些观察者。

        myViewModel.livedata.observeForever 

        

5.更新LiveData:LiveData 是一个抽象类,MutableLiveData 类将公开 setValue(T) 和 postValue(T) 方法,修改存储在 LiveData 对象中的值,则必须使用这些方法。

Data Binding

简介

Data Binding 简单的解释就是,之前我们需要通过id获取到控件,然后通过控件设置数据,现在有了Data Binding之后 我们可以直接在布局文件中直接绑定数据,它的侧重点是在绑定数据

注意:在许多情况下, Data Binding可简化实现,提高性能,提供与数据绑定相同的好处。如果使用数据绑定的主要目的是取代 findViewById() 调用,请考虑改用 View Binding。

详细介绍请参见Google官方文档: https://developer.android.google.cn/topic/libraries/data-binding

用法

  1. 使用 Data Binding ,首先需要在 app moudle 下的 build.gradle 中添加:

android 
    ...
    dataBinding 
        enabled = true
    
    ...
  1. 创建一个实体类,例:Info 类

    class Info(var name: String = "Wilfried",
               var job: String = "Android Engineer",
               var company: String = "noboAuto")

3.在这个Activity的xml(activity_my_info.xml)中的根布局下,通过Alt+Enter快捷键创建databinding的布局,同时导入 Info 类

<?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:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="myInfo"
            type="com.noboauto.example.Info" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".ui.slideshow.SlideshowFragment">

        <TextView
            android:id="@+id/tv_name"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@myInfo.name"
            android:textSize="20sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
  1. 此时编译器会自动根据这个布局生成相应的绑定类,这里会生成一个 ActivityMyInfoBinding 的类,然后在对应的Activity获取Data Binding并绑定数据

class MyInfoActivity : AppCompatActivity() 

    override fun onCreate(savedInstanceState: Bundle?) 
        super.onCreate(savedInstanceState)

        val bindingBinding : ActivityMyInfoBinding = DataBindingUtil.setContentView(this, R.layout.activity_my_info)
        bindingBinding.myInfo = Info()//已实现默认参数
    

双向绑定

单向绑定:字段变化-->View自动更新

双向绑定:字段变化-->view自动更新、用户操作view变化-->字段同步变化(EditText)

为什么要使用双向绑定?

因为我们使用DataBinding一般数据源是ViewModel, 我们可以根据ViewModel的LiveData来赋值给EditText,但是我们从界面操作了EditText无法更新对应的LiveData,也就是EditText的数据已经由用户操作更新了,可是此时ViewModel里的LiveData数据还是原先的数据,如果此时我们要获取数据,那到LiveDate的值就是旧值,所以我们需要双向绑定,当界面更新ViewModel的数据同步更新

如何使用双向绑定?

1、自定义view必须要定义了对关键值的监听,比如CheckBox的checked属性,Android有定义接口来回调这个值的状态,这个时候就可以将navigationViewModel中定义的实现了OnCheckedChangeListener的接口作为参数传入到onCheckedChanged,所以当View有状态的变化就会通过ViewModel注册的监听将状态同步到LiveData,完成View和LiveData状态同步了

android:onCheckedChanged="@navigationViewModel.rememberMeChanged"

2、当然因为是系统控件我们直接使用 android:checked="@=navigationViewModel.booleanLiveData"也就搞定了 双向绑定的形式也就是上面的“@= ”,他可以接收属性的数据更改并同时监听用户更新

3、也可以使用可观察的数据对象,将布局变量设置为 Observable(通常为 BaseObservable)的实现,并使用 @Bindable 注释

4、注册监听,当addTextChangedListener发生变化,会通知属性调用InverseBindingAdapter注解方法

        <EditText
            android:id="@+id/ed_info2"
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:layout_marginTop="20dp"
            app:bindingName='@=bannerViewModel.userDefined'
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="@+id/ed_info"
            tools:ignore="Autofill,LabelFor,TextFields" />
        @BindingAdapter("bindingNameAttrChanged")
        @JvmStatic
        fun setBindingListener(edit: EditText, listener: InverseBindingListener?) 
            Log.i(TAG, "setBindingListener..")
            var txt = ""
            edit.addTextChangedListener(object : TextWatcher 
                override fun afterTextChanged(p0: Editable?) 
                    if (txt != p0.toString()) 
                        // 会通知属性调用InverseBindingAdapter注解方法
                        listener?.onChange()
                        txt = p0.toString()
                    
                    edit.setSelection(txt.length)
                

                override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) 
                override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) 
            )
        

InverseBindingAdapter注解,提供view的最新值,赋值给数据层

        @InverseBindingAdapter(attribute = "bindingName")
        @JvmStatic
        fun getBindingName(edt: EditText): String 
            Log.i(TAG, "getBindingName..$edt.text")
            return edt.text.toString()
        

数据层发生变化,触发单向绑定,改变view的值

        @BindingAdapter("bindingName")
        @JvmStatic
        fun setBindingName(edt: EditText, txt: String) 
            Log.i(TAG, "setBindingName..$txt")
            edt.setText(txt)
        

注意:在afterTextChanged的时候判断了txt != p0.toString(),如果不判断会无限循环,所以只有值发生改变的时候才去触发

Lifecycles

简介

生命周期感知型组件可执行操作来响应另一个组件(如 Activity 和 Fragment)的生命周期状态的变化

生命周期拥有者LifecycleOwner生命周期的观察者LifecycleObserver之间快速方便的建立一种联系。在生命周期拥有者的生命周期变化时,观察者会收到对应的通知。

简单的说就是用来监听Activity与Fragment的生命周期变化

主要类

lifecycleRegister

lifecycle的唯一子类,在生命周期拥有者的生命周期发生变化时触发自身状态和相关观察者的订阅回调逻辑。

lifecycleOwner

该接口的实现类可以提供lifecycleRegister的实例,主要实现类就是AppCompatActivity和Fragment。 (android.app.Activity.Activity默认不实现lifecycleOwner接口,所以推荐使用AppCompatActivity)

lifecycleObserver

该接口的实现类表示为关注生命周期事件的观察者。

详细介绍请参见Google官方文档: https://developer.android.google.cn/topic/libraries/architecture/lifecycle

用法

生命周期拥有者 LifecycleOwner

  • AppCompatActivity的实现原理是在的父类ComponentActivity中实现了LifecycleOwner,并创建和提供了LifecycleRegistry

    private final LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);

    @Override
    public Lifecycle getLifecycle() 
        return mLifecycleRegistry;
    

在ComponentActivity的子类ComponentActivity中实现了事件的分发

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) 
    
        ...
        mFragmentLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);

    

枚举中的方法跟Activity生命周期一致,注意ON_ANY只要生命周期变化就会调用此方法

        public enum Event 
        /**
         * Constant for onCreate event of the @link LifecycleOwner.
         */
        ON_CREATE,
        /**
         * Constant for onStart event of the @link LifecycleOwner.
         */
        ON_START,
        /**
         * Constant for onResume event of the @link LifecycleOwner.
         */
        ON_RESUME,
        /**
         * Constant for onPause event of the @link LifecycleOwner.
         */
        ON_PAUSE,
        /**
         * Constant for onStop event of the @link LifecycleOwner.
         */
        ON_STOP,
        /**
         * Constant for onDestroy event of the @link LifecycleOwner.
         */
        ON_DESTROY,
        /**
         * An @link Event Event constant that can be used to match all events.
         */
        ON_ANY
    

  • 自定义LifecycleOwner

实现LifecycleOwner接口,重写getLifecycle方法返回Lifecycle的唯一子类LifecycleRegistry

markState和handleLifecycleEvent都可以将生命周期状态分发出去,但是markState已经是过时的方法,建议使用handleLifecycleEvent

class MyActivity : Activity(), LifecycleOwner 

    private lateinit var lifecycleRegistry: LifecycleRegistry

    override fun onCreate(savedInstanceState: Bundle?) 
        super.onCreate(savedInstanceState)

        lifecycleRegistry = LifecycleRegistry(this)
        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)

        lifecycle.addObserver(MyInfoObserver())
        lifecycle.addObserver(MyInfoObserver2())
    

    override fun onStart() 
        super.onStart()
        lifecycleRegistry.markState(Lifecycle.State.STARTED)
    

    override fun onResume() 
        super.onResume()
        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)

    

    override fun getLifecycle(): Lifecycle 
        return lifecycleRegistry
    

生命周期的观察者 LifecycleObserver

两种方式可以实现生命周期的观察

  1. 实现LifecycleObserver接口,通过OnLifecycleEvent注解来回调各个生命周期状态

class MyInfoObserver : LifecycleObserver 

    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
    fun onMyInfoCreate() 
        Log.i("wilfried", "onCreate")
    

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    fun onMyInfoStart() 
        Log.i("wilfried", "onStart")
    

    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    fun onMyInfoResume() 
        Log.i("wilfried", "onResume")
    
  1. 实现LifecycleEventObserver接口,通过回调方法onStateChanged来实现状态监听(本身也是实现了LifecycleObserver)

public interface LifecycleEventObserver extends LifecycleObserver 

    void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event);

class MyInfoObserver2 : LifecycleEventObserver 
    override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) 
        Log.i("wilfried", "LifecycleOwner==$source Event==$event.name ")

    

最终打印结果如下:

2021-05-27 20:37:48.820 28878-28878/com.example.android.roomwordssample I/wilfried: onCreate
2021-05-27 20:37:48.821 28878-28878/com.example.android.roomwordssample I/wilfried: LifecycleOwner==com.example.android.roomwordssample.MyActivity@c1f76a Event==ON_CREATE 
2021-05-27 20:37:48.839 28878-28878/com.example.android.roomwordssample I/wilfried: onStart
2021-05-27 20:37:48.839 28878-28878/com.example.android.roomwordssample I/wilfried: LifecycleOwner==com.example.android.roomwordssample.MyActivity@c1f76a Event==ON_START 
2021-05-27 20:37:48.841 28878-28878/com.example.android.roomwordssample I/wilfried: onResume
2021-05-27 20:37:48.841 28878-28878/com.example.android.roomwordssample I/wilfried: LifecycleOwner==com.example.android.roomwordssample.MyActivity@c1f76a Event==ON_RESUME 

生命周期感知型组件的最佳做法

  • 使界面控制器(Activity 和 Fragment)尽可能保持精简。它们不应试图获取自己的数据,而应使用 ViewModel 执行此操作,并观察 LiveData 对象以将更改体现到视图中。

  • 设法编写数据驱动型界面,对于此类界面,界面控制器的责任是随着数据更改而更新视图,或者将用户操作通知给 ViewModel

  • 将数据逻辑放在 ViewModel 类中。ViewModel 应充当界面控制器与应用其余部分之间的连接器。不过要注意,ViewModel 不负责获取数据(例如,从网络获取)。但是,ViewModel 应调用相应的组件来获取数据,然后将结果提供给界面控制器。

  • 使用数据绑定在视图与界面控制器之间维持干净的接口。这样一来,您可以使视图更具声明性,并尽量减少需要在 Activity 和 Fragment 中编写的更新代码。如果您更愿意使用 Java 编程语言执行此操作,请使用诸如 Butter Knife 之类的库,以避免样板代码并实现更好的抽象化。

  • 如果界面很复杂,不妨考虑创建 presenter 类来处理界面的修改。这可能是一项艰巨的任务,但这样做可使界面组件更易于测试。

  • 避免在 ViewModel 中引用 ViewActivity 上下文。如果 ViewModel 存在的时间比 Activity 更长(在配置更改的情况下),Activity 将泄漏并且不会获得垃圾回收器的妥善处置。

  • 使用 Kotlin 协程管理长时间运行的任务和其他可以异步运行的操作。

实现了MVVM架构的Demo

Demo下载地址

以上是关于ViewBinding用法的主要内容,如果未能解决你的问题,请参考以下文章

Android 基于Jetpack的MVVM架构入门指南

Android开发:关于Databinding与Viewbinding以及kotlin-android-extensions

Android开发:关于Databinding与Viewbinding以及kotlin-android-extensions

Android开发:关于Databinding与Viewbinding以及kotlin-android-extensions

Android开发:关于Databinding与Viewbinding以及kotlin-android-extensions

ViewBinding用法