jetpack-ViewBinding

1,121 阅读2分钟

Android Studio 3.6中有一项名为“视图绑定”的新功能。就像数据绑定一样,只不过它要做的就是采用您现有的布局,并为其生成绑定-无需更改,无需<layout标签,尤其是无需<data标签。只是纯布局!

启用ViewBinding

首先,我们在build.gradle中启用它:

viewBinding {
        enabled = true
    }

Activity绑定视图

普通xml文件 activity_first.xml

<?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"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

Activity

class FirstActivity: AppCompatActivity() {

    /**
     * ActivityFirstBinding由Android Studio自动生成(需要升级到3.6.0)所以基本不需要考虑编译性能(这也是和dataBinding差别最大的地方)
     */
    private lateinit var binding: ActivityFirstBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityFirstBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.textView.text = "已经绑定视图了"
    }
}

Fragment绑定视图

fragment_bind.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".InflateFragment">

    <TextView
        android:id="@+id/textViewFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="@string/hello_blank_fragment" />

</FrameLayout>

Fragment

/**
 * View Binding example with a fragment that uses the alternate constructor for inflation and
 * [onViewCreated] for binding.
 */
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()
    }
}

Activity写法还算比较简单,但是Fragment稍微比较麻烦,但我们仍然有方法改进它。通过改进,我们能够实现:一行代码完成绑定

原理: kotlin自定义委托

参考:AutoClearedValue

我们需要做的就是将MyBinding.bind函数引用传递给我们的委托,并使用该函数初始化View的绑定。然后,当视图被销毁时,我们清除此绑定值。

委托类FragmentViewBindingDelegate

class FragmentViewBindingDelegate<T : ViewBinding>(
    val fragment: Fragment,
    val viewBindingFactory: (View) -> T
) : ReadOnlyProperty<Fragment, T> {
    private var binding: T? = null

    init {
        fragment.lifecycle.addObserver(object: DefaultLifecycleObserver {
            override fun onCreate(owner: LifecycleOwner) {
                fragment.viewLifecycleOwnerLiveData.observe(fragment) { viewLifecycleOwner ->
                    viewLifecycleOwner?.lifecycle?.addObserver(object: DefaultLifecycleObserver {
                        override fun onDestroy(owner: LifecycleOwner) {
                            binding = null
                        }
                    })
                }
            }
        })
    }

    override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
        val binding = binding
        if (binding != null) {
            return binding
        }

        val lifecycle = fragment.viewLifecycleOwner.lifecycle
        if (!lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) {
            throw IllegalStateException("Should not attempt to get bindings when Fragment views are destroyed.")
        }

        return viewBindingFactory(thisRef.requireView()).also { this@FragmentViewBindingDelegate.binding = it }
    }
}

fun <T : ViewBinding> Fragment.viewBinding(viewBindingFactory: (View) -> T) =
    FragmentViewBindingDelegate(this, viewBindingFactory)

委托后的Fragment绑定

class BindFragment : Fragment(R.layout.fragment_blank) {

    /**
     * 通过委托调用绑定方法
     */
    private val fragmentBlankBinding: FragmentBlankBinding by viewBinding(FragmentBlankBinding::bind)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        fragmentBlankBinding.textViewFragment.text = "通过委托绑定成功"
    }
}

定义Activity绑定委托

inline fun <T : ViewBinding> AppCompatActivity.viewBinding(crossinline bindingInflater: (LayoutInflater) -> T)
        = lazy(LazyThreadSafetyMode.NONE) {
    bindingInflater.invoke(layoutInflater)
}

委托后的Activity绑定

class FirstActivity: AppCompatActivity() {

    /**
     * ActivityFirstBinding由Android Studio自动生成(需要升级到3.6.0)所以基本不需要考虑编译性能(这也是和dataBinding差别最大的地方)
     */
    private val binding by viewBinding(ActivityFirstBinding::inflate)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        binding.textView.text = "已经通过委托绑定视图了"
    }
}

借助Kotlin代表和Jetpack Lifecycle组件的强大功能,我们已将ViewBinding变成了单行调用