ViewBinding - Jetpack 视图绑定委托封装及使用示例

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

在大多数情况下,视图绑定会替代 findViewById

⚠️ Kotlin Android Extensions 已弃用,这意味着不再支持使用 Kotlin 合成进行视图绑定。 如果您的应用使用 Kotlin 合成进行视图绑定,请迁移到 Jetpack 视图绑定。

#1. 设置说明

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

android {
        ...
        viewBinding {
            enabled = true
        }
    }
    

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



        ...

#2. 用法

简单使用:可参考 ViewBinding-视图绑定用法

我有在网上找到封装好的写法,这里借鉴分享一下:委托 Android 视图绑定以及使用示例

这里我也贴一份出来:

#3. 封装

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.viewbinding.ViewBinding
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty

/**
 * Activity 绑定委托,可以从 onCreate 到 onDestroy(含)使用
 */
inline fun  AppCompatActivity.viewBinding(crossinline factory: (LayoutInflater) -> T) =
    lazy(LazyThreadSafetyMode.NONE) {
        factory(layoutInflater)
    }

/**
 * Fragment 绑定委托,可以从 onViewCreated 到 onDestroyView(含)使用
 */
fun  Fragment.viewBinding(factory: (View) -> T): ReadOnlyProperty =
    object : ReadOnlyProperty, DefaultLifecycleObserver {
        private var binding: T? = null

        override fun getValue(thisRef: Fragment, property: KProperty<*>): T =
            binding ?: factory(requireView()).also {
                // 如果在 Lifecycle 被销毁后访问绑定,则创建新实例,但不要缓存它
                if (viewLifecycleOwner.lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) {
                    viewLifecycleOwner.lifecycle.addObserver(this)
                    binding = it
                }
            }

        override fun onDestroy(owner: LifecycleOwner) {
            binding = null
        }
    }

/**
 * 实现 onCreateDialog 的 DialogFragment 的绑定委托(像活动一样,它们没有单独的视图生命周期),
 * 可以从 onCreateDialog 到 onDestroy(包括)使用
 */
inline fun  DialogFragment.viewBinding(crossinline factory: (LayoutInflater) -> T) =
    lazy(LazyThreadSafetyMode.NONE) {
        factory(layoutInflater)
    }

/**
 * 不是真正的委托,只是 RecyclerView.ViewHolders 的一个小帮手
 */
inline fun  ViewGroup.viewBinding(factory: (LayoutInflater, ViewGroup, Boolean) -> T) =
    factory(LayoutInflater.from(context), this, false)

/**
 * 不是真正的委托,只是 CustomView 的一个小帮手
 */
inline fun  ViewGroup.viewBinding(
    factory: (LayoutInflater, ViewGroup, Boolean) -> T,
    attachToRoot: Boolean = false
) = factory(LayoutInflater.from(context), this, attachToRoot)

#4. 使用场景示例

1. Activity中使用:

class MainActivity : AppCompatActivity() {
    private val binding by viewBinding(ActivityMainBinding::inflate)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)
        binding.button.text = "Bound!"
    }
}

2. Fragment中使用:

// 不要忘记在 Fragment 构造函数中传递 layoutId

class RegularFragment : Fragment(R.layout.fragment) {
    private val binding by viewBinding(FragmentBinding::bind)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding.button.text = "Bound!"
    }
}

3.DialogFragment中使用:(两种写法)

// 带有 onCreateDialog 的 DialogFragment 没有视图生命周期,所以我们需要一个不同的委托

class DialogFragment1 : DialogFragment() {
    private val binding by viewBinding(FragmentBinding::inflate)

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        binding.button.text = "Bound!"
        return AlertDialog.Builder(requireContext()).setView(binding.root).create()
    }
}
// 对于具有完整视图的 DialogFragment,我们可以使用常规的 Fragment 委托(实际上是这里的整个代码与在 RegularFragment 中的完全一样)
// 注意:最近才添加了具有 layoutId 的构造函数(在 Fragment 1.3.0 中)

class DialogFragment2 : DialogFragment(R.layout.fragment) {
    private val binding by viewBinding(FragmentBinding::bind)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding.button.text = "Bound!"
    }
}

4.DialogFragment中使用:(三种写法)

// 对于 RecyclerView,我们不需要任何委托,只需要一个属性。
// 不幸的是,这里我们有一个名称重载:View Binding vs “binding” holder to data (onBindViewHolder)。
// ViewGroup.viewBinding() 辅助函数可以稍微减少样板。

class Adapter1 : ListAdapter(Differ()) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
        return Holder(parent.viewBinding(ListItemBinding::inflate))
    }

    override fun onBindViewHolder(holder: Holder, position: Int) {
        holder.binding.textView.text = getItem(position)
    }

    class Holder(val binding: ListItemBinding) : RecyclerView.ViewHolder(binding.root)

    private class Differ : DiffUtil.ItemCallback() { ... }
}
// 或者,我们可以为所有适配器使用通用 BoundHolder

class Adapter2 : ListAdapter>(Differ()) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BoundHolder {
        return BoundHolder(parent.viewBinding(ListItemBinding::inflate))
    }

    override fun onBindViewHolder(holder: BoundHolder, position: Int) {
        holder.binding.textView.text = getItem(position)
    }

    private class Differ : DiffUtil.ItemCallback() { ... }
}

open class BoundHolder(val binding: T) : RecyclerView.ViewHolder(binding.root)
// 就我个人而言,我更喜欢将视图创建和操作封装在 ViewHolder 中。
// 在这种情况下,BoundHolder 可以用作超类。

class Adapter3 : ListAdapter(Differ()) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = Holder(parent)

    override fun onBindViewHolder(holder: Holder, position: Int) = holder.bind(getItem(position))

    class Holder(parent: ViewGroup) : BoundHolder(parent.viewBinding(ListItemBinding::inflate)) {

        fun bind(item: String) {
            binding.textView.text = item
        }
    }

    private class Differ : DiffUtil.ItemCallback() { ... }
}

abstract class BoundHolder(protected val binding: T) : RecyclerView.ViewHolder(binding.root)

5.自定义View中使用:

class CustomLayout @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {

    private val binding: ViewCustomLayoutBinding by lazy {
        viewBinding(
            ViewCustomLayoutBinding::inflate,
            true
        )
    }

    init{
        binding.textView.text = "Test"
    }
}

具体的项目架构地址:MVVM-Project-Hilt

(欢迎讨论) 

你可能感兴趣的:(Android,Jetpack,ViewBinding,视图绑定,绑定委托)