通過視圖綁定功能,您可以更輕松地編寫可與視圖互動的代碼。在子產品中啟用視圖綁定之後,系統會為該子產品中的每個 XML 布局檔案生成一個綁定類。綁定類的執行個體包含對在相應布局中具有 ID 的所有視圖的直接引用。
在大多數情況下,視圖綁定會替代
。
findViewById
#1. 設定說明
視圖綁定功能可按子產品啟用。要在某個子產品中啟用視圖綁定,請将
viewBinding
元素添加到其
build.gradle
檔案中,如下例所示:
android {
...
viewBinding {
enabled = true
}
}
如果您希望在生成綁定類時忽略某個布局檔案,請将
tools:viewBindingIgnore="true"
屬性添加到相應布局檔案的根視圖中:
<LinearLayout
...
tools:viewBindingIgnore="true" >
...
</LinearLayout>
#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 <T : ViewBinding> AppCompatActivity.viewBinding(crossinline factory: (LayoutInflater) -> T) =
lazy(LazyThreadSafetyMode.NONE) {
factory(layoutInflater)
}
/**
* Fragment 綁定委托,可以從 onViewCreated 到 onDestroyView(含)使用
*/
fun <T : ViewBinding> Fragment.viewBinding(factory: (View) -> T): ReadOnlyProperty<Fragment, T> =
object : ReadOnlyProperty<Fragment, T>, 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 <T : ViewBinding> DialogFragment.viewBinding(crossinline factory: (LayoutInflater) -> T) =
lazy(LazyThreadSafetyMode.NONE) {
factory(layoutInflater)
}
/**
* 不是真正的委托,隻是 RecyclerView.ViewHolders 的一個小幫手
*/
inline fun <T : ViewBinding> ViewGroup.viewBinding(factory: (LayoutInflater, ViewGroup, Boolean) -> T) =
factory(LayoutInflater.from(context), this, false)
/**
* 不是真正的委托,隻是 CustomView 的一個小幫手
*/
inline fun <T : ViewBinding> 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<String, Adapter1.Holder>(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<String>() { ... }
}
// 或者,我們可以為所有擴充卡使用通用 BoundHolder
class Adapter2 : ListAdapter<String, BoundHolder<ListItemBinding>>(Differ()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BoundHolder<ListItemBinding> {
return BoundHolder(parent.viewBinding(ListItemBinding::inflate))
}
override fun onBindViewHolder(holder: BoundHolder<ListItemBinding>, position: Int) {
holder.binding.textView.text = getItem(position)
}
private class Differ : DiffUtil.ItemCallback<String>() { ... }
}
open class BoundHolder<T : ViewBinding>(val binding: T) : RecyclerView.ViewHolder(binding.root)
// 就我個人而言,我更喜歡将視圖建立和操作封裝在 ViewHolder 中。
// 在這種情況下,BoundHolder 可以用作超類。
class Adapter3 : ListAdapter<String, Adapter3.Holder>(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<ListItemBinding>(parent.viewBinding(ListItemBinding::inflate)) {
fun bind(item: String) {
binding.textView.text = item
}
}
private class Differ : DiffUtil.ItemCallback<String>() { ... }
}
abstract class BoundHolder<T : ViewBinding>(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
(歡迎讨論)