天天看點

ViewBinding - 委托 Android 視圖綁定以及使用示例

通過視圖綁定功能,您可以更輕松地編寫可與視圖互動的代碼。在子產品中啟用視圖綁定之後,系統會為該子產品中的每個 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

(歡迎讨論) 

繼續閱讀