Jetpack ViewBinding
文章目錄
- Jetpack ViewBinding
- 概述
- ViewBinding優(yōu)點
- 配置ViewBinding
- 使用
- 在Activity中使用
- 在Fragment中使用
- 在RecyclerView adapter中使用
- 在include標(biāo)簽中使用
- 不使用merge標(biāo)簽
- 使用merge標(biāo)簽
- 封裝使用
- 基類封裝,不使用反射
- 基類封裝,使用反射
- 委托實現(xiàn)
- 源碼分析
- 代碼下載
Jetpack ViewBinding
概述
官網(wǎng)文檔
通過視圖綁定功能,您可以更輕松地編寫可與視圖交互的代碼。在模塊中啟用視圖綁定之后,系統(tǒng)會為該模塊中的每個 XML 布局文件生成一個綁定類。綁定類的實例包含對在相應(yīng)布局中具有 ID 的所有視圖的直接引用。
ViewBinding優(yōu)點
ViewBinding優(yōu)點
- Null 安全:由于視圖綁定會創(chuàng)建對視圖的直接引用,因此不存在因視圖 ID 無效而引發(fā) Null 指針異常的風(fēng)險。
- 類型安全:每個綁定類中的字段均具有與它們在 XML 文件中引用的視圖相匹配的類型。因此不存強(qiáng)制轉(zhuǎn)換導(dǎo)致的異常風(fēng)險。
與findViewById區(qū)別
- findViewById編寫過于冗余。
- 類型仍然不安全。
與ButterKnife區(qū)別
- 官宣不維護(hù),推薦使用ViewBinding。
- 類型仍然不安全。
- 對組件化項目不友好。
與Kotlin Android Extensions
- JetBrains廢棄該插件。
- 類型仍然不安全。
- 性能偏低。
配置ViewBinding
Android Studio3.6以上
android {viewBinding {enabled = true} }Android Studio4.0以上
android {buildFeatures {viewBinding = true} }如果需要忽略某個布局文件,需要添加tools:viewBindingIgnore="true"屬性到布局中
<LinearLayout...tools:viewBindingIgnore="true" >... </LinearLayout>使用
當(dāng)開啟ViewBinding后,系統(tǒng)會為該模塊中每個XML布局文件生成一個綁定類(轉(zhuǎn)換為駝峰命名并在末尾添加Binding),每個綁定類均包含根視圖已交具有id的所有視圖的引用。
例如:布局文件名為activity_main.xml,生成綁定類為ActivityMainBinding
在Activity中使用
使用流程
- 開啟視圖綁定功能后,系統(tǒng)會為該模塊中的XML布局生成一個綁定類,每個綁定類都包含根布局和具有ID布局的引用。
- 調(diào)用綁定類的inflate()方法獲取綁定類對象。
- 調(diào)用綁定類對象的getRoot()方法獲取根布局傳遞到setContentView()。
XML布局
activity_view_binding_simple.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout 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"android:gravity="center_horizontal"android:orientation="vertical"><TextViewandroid:id="@+id/tv_name"android:layout_width="wrap_content"android:layout_height="wrap_content" /><TextViewandroid:id="@+id/tv_age"android:layout_width="wrap_content"android:layout_height="wrap_content" /><ImageViewandroid:id="@+id/iv_avatar"android:layout_width="wrap_content"android:layout_height="wrap_content" /><Buttonandroid:id="@+id/btn"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="確定" /> </LinearLayout>Activity類
class ViewBindingSimpleActivity : BaseActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)val viewBinding = ActivityViewBindingSimpleBinding.inflate(layoutInflater)setContentView(viewBinding.root)viewBinding.tvName.text = "hello world"viewBinding.tvAge.text = 18.toString()viewBinding.ivAvatar.setImageResource(R.mipmap.ic_launcher_round)viewBinding.btn.setOnClickListener {Toast.makeText(mContext, "hello", Toast.LENGTH_SHORT).show()}} }在Fragment中使用
方式一
- 調(diào)用綁定類的inflate()方法,獲取綁定類對象。
- 再調(diào)用getRoot()方法獲取根布局。
- 在onCreateView()方法返回根布局,使其成為屏幕上的活動視圖。
- 由于Fragment的存在時間比視圖長。因此需要在Fragment的onDestroyView()方法中清除對綁定類對象的所有引用。
方式二
class MyFragment : BaseFragment() {private var _viewBinding: FragmentMyBinding? = nullprivate val viewBindingget() = _viewBinding!!override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {return inflater.inflate(R.layout.fragment_my, container, false)}override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)_viewBinding = FragmentMyBinding.bind(view)viewBinding.tvName.text = "hello world"viewBinding.tvAge.text = 18.toString()viewBinding.ivAvatar.setImageResource(R.mipmap.ic_launcher_round)viewBinding.btn.setOnClickListener {Toast.makeText(mContext, "hello", Toast.LENGTH_SHORT).show()}}override fun onDestroyView() {super.onDestroyView()_viewBinding = null} }在RecyclerView adapter中使用
class MyAdapter(context: Context, private val data: ArrayList<String>) :RecyclerView.Adapter<MyAdapter.ViewHolder>() {val layoutInflater: LayoutInflater = LayoutInflater.from(context)class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {val text: TextView = itemView.findViewById(R.id.text)}override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {val viewBinding = ItemTextBinding.inflate(layoutInflater, parent, false)return ViewHolder(viewBinding.root)}override fun onBindViewHolder(holder: ViewHolder, position: Int) {holder.text.text = data[position]}override fun getItemCount(): Int {return data.size} }在include標(biāo)簽中使用
ViewBinding可以與<include>標(biāo)簽一起使用
不使用merge標(biāo)簽
- 一定要給<include>標(biāo)簽定義id,使用該id訪問布局中的控件。
XML布局
title_bar.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="#2196F3"android:minHeight="50dp"android:padding="10dp"><TextViewandroid:id="@+id/back"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerVertical="true"android:text="返回" /><TextViewandroid:id="@+id/title"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:text="標(biāo)題位置" /><TextViewandroid:id="@+id/confirm"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentRight="true"android:layout_centerVertical="true"android:text="確定" /></RelativeLayout>Activity的XML布局
activity_vbinclude.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout 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:context=".IncludeActivity"><includeandroid:id="@+id/titleBar"layout="@layout/titlebar" /></LinearLayout>在include標(biāo)簽中使用
public class IncludeActivity extends AppCompatActivity {private Context context;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);context = this;ActivityIncludeBinding binding = ActivityIncludeBinding.inflate(getLayoutInflater());setContentView(binding.getRoot());binding.titleBar.title.setText("這是一個標(biāo)題");binding.titleBar.back.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Toast.makeText(context, "返回", Toast.LENGTH_SHORT).show();}});binding.titleBar.confirm.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Toast.makeText(context, "確定", Toast.LENGTH_SHORT).show();}});} }使用merge標(biāo)簽
- <merge>標(biāo)簽有利于減少布局層次。
- 需要使用bind()方法綁定根視圖。
- 不能給<include>標(biāo)簽設(shè)置id。
布局:detail_layout.xml
<?xml version="1.0" encoding="utf-8"?> <merge xmlns:android="http://schemas.android.com/apk/res/android"><ImageViewandroid:id="@+id/ivDetail"android:layout_width="100dp"android:layout_height="100dp"android:scaleType="fitXY" /> </merge>Activity的XML布局
<?xml version="1.0" encoding="utf-8"?> <LinearLayout 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"android:orientation="vertical"tools:context=".IncludeActivity"><include layout="@layout/detail_layout" /></LinearLayout>在include標(biāo)簽中使用
package com.example.viewbindingdemo;import android.content.Context; import android.os.Bundle; import android.view.View; import android.widget.Toast;import androidx.appcompat.app.AppCompatActivity;import com.example.viewbindingdemo.databinding.ActivityIncludeBinding; import com.example.viewbindingdemo.databinding.DetailLayoutBinding;public class IncludeActivity extends AppCompatActivity {private Context context;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);context = this;ActivityIncludeBinding binding = ActivityIncludeBinding.inflate(getLayoutInflater());setContentView(binding.getRoot());DetailLayoutBinding detailBinding = DetailLayoutBinding.bind(binding.getRoot());detailBinding.ivDetail.setImageResource(R.mipmap.ic_launcher);} }封裝使用
基類封裝,不使用反射
基類封裝
abstract class BindingActivity<VB : ViewBinding> : BaseActivity() {private lateinit var _viewBinding: VBprotected val mViewBinding get() = _viewBindingoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)_viewBinding = getViewBinding()setContentView(_viewBinding.root)}abstract fun getViewBinding(): VB }abstract class BindingFragment<VB : ViewBinding> : BaseFragment() {private lateinit var _viewBinding: VBprotected val mViewBinding get() = _viewBindingoverride fun onCreateView(inflater: LayoutInflater,container: ViewGroup?,savedInstanceState: Bundle?): View? {_viewBinding = getViewBinding(inflater, container)return _viewBinding.root}abstract fun getViewBinding(inflater: LayoutInflater, container: ViewGroup?): VB }使用
class OneActivity : BindingActivity<ActivityOneBinding>() { override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)mViewBinding.ivAvatar.setImageResource(R.mipmap.ic_launcher_round)mViewBinding.tvName.text = "小白"mViewBinding.tvAge.text = 18.toString()mViewBinding.btn.setOnClickListener {Toast.makeText(mContext, "hello", Toast.LENGTH_SHORT).show()}}override fun getViewBinding(): ActivityOneBinding {return ActivityOneBinding.inflate(layoutInflater)} }class OneFragment : BindingFragment<FragmentOneBinding>() {override fun getViewBinding(inflater: LayoutInflater,container: ViewGroup?): FragmentOneBinding {return FragmentOneBinding.inflate(inflater, container, false)}override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)mViewBinding.ivAvatar.setImageResource(R.mipmap.ic_launcher_round)mViewBinding.tvName.text = "小白"mViewBinding.tvAge.text = 18.toString()mViewBinding.btn.setOnClickListener {Toast.makeText(mContext, "hello", Toast.LENGTH_SHORT).show()}} }基類封裝,使用反射
基類封裝
abstract class BindingActivity<VB : ViewBinding> : BaseActivity() {private lateinit var _viewBinding: VBprotected val mViewBinding get() = _viewBindingoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)mContext = thisval type = javaClass.genericSuperclassif (type is ParameterizedType) {val clz = type.actualTypeArguments[0] as Class<*>val method = clz.getMethod("inflate", LayoutInflater::class.java)_viewBinding = method.invoke(null, layoutInflater) as VBsetContentView(_viewBinding.root)}} }abstract class BindingFragment<VB : ViewBinding> : BaseFragment() {private var _viewBinding: VB? = nullprotected val mViewBinding get() = _viewBinding!!override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {val type = javaClass.genericSuperclassif (type is ParameterizedType) {val clz = type.actualTypeArguments[0] as Class<*>val method = clz.getMethod("inflate", LayoutInflater::class.java, ViewGroup::class.java, Boolean::class.java)_viewBinding = method.invoke(null, inflater, container, false) as VB}return mViewBinding.root}override fun onDestroyView() {super.onDestroyView()_viewBinding = null} }使用
class TwoActivity : BindingActivity<ActivityTwoBinding>() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)mViewBinding.ivAvatar.setImageResource(R.mipmap.ic_launcher_round)mViewBinding.tvName.text = "小白"mViewBinding.tvAge.text = 28.toString()mViewBinding.btn.setOnClickListener {Toast.makeText(mContext, "hello2", Toast.LENGTH_SHORT).show()}} }class TwoFragment : BindingFragment<FragmentTwoBinding>() {override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)mViewBinding.ivAvatar.setImageResource(R.mipmap.ic_launcher_round)mViewBinding.tvName.text = "小白"mViewBinding.tvAge.text = 28.toString()mViewBinding.btn.setOnClickListener {Toast.makeText(mContext, "hello2", Toast.LENGTH_SHORT).show()}} }委托實現(xiàn)
封裝
//Activity ViewBindinginline fun <reified VB : ViewBinding> ComponentActivity.viewBindings(noinline factory: (LayoutInflater) -> VB,setContentView: Boolean = true ) = ActivityViewBindingDelegate1(factory, setContentView)inline fun <reified VB : ViewBinding> ComponentActivity.viewBindings(setContentView: Boolean = true) =ActivityViewBindingDelegate2(VB::class.java, setContentView)class ActivityViewBindingDelegate1<VB : ViewBinding>(private val factory: (LayoutInflater) -> VB,private val setContentView: Boolean, ) : ReadOnlyProperty<ComponentActivity, VB> {private var viewBinding: VB? = nulloverride fun getValue(thisRef: ComponentActivity, property: KProperty<*>): VB {viewBinding?.let { return it }viewBinding = factory(thisRef.layoutInflater).also { viewBinding ->if (setContentView) thisRef.setContentView(viewBinding.root)}return viewBinding!!} }class ActivityViewBindingDelegate2<VB : ViewBinding>(private val clazz: Class<VB>,private val setContentView: Boolean, ) : ReadOnlyProperty<ComponentActivity, VB> {private var viewBinding: VB? = nulloverride fun getValue(thisRef: ComponentActivity, property: KProperty<*>): VB {viewBinding?.let { return it }val inflateMethod = clazz.getMethod("inflate", LayoutInflater::class.java)viewBinding =(inflateMethod.invoke(null, thisRef.layoutInflater) as VB).also { viewBinding ->if (setContentView) thisRef.setContentView(viewBinding.root)}return viewBinding!!} } //Fragment ViewBindinginline fun <reified VB : ViewBinding> Fragment.viewBindings(noinline factory: (View) -> VB) =FragmentViewBindingDelegate1(factory)inline fun <reified VB : ViewBinding> Fragment.viewBindings() =FragmentViewBindingDelegate2(VB::class.java)class FragmentViewBindingDelegate1<VB : ViewBinding>(private val factory: (View) -> VB, ) : ReadOnlyProperty<Fragment, VB> {private var viewBinding: VB? = nulloverride fun getValue(thisRef: Fragment, property: KProperty<*>): VB {viewBinding?.let { return it }val lifecycle = thisRef.viewLifecycleOwner.lifecycleviewBinding = factory(thisRef.requireView())if (lifecycle.currentState == Lifecycle.State.DESTROYED) {Log.w("TAG","Access to viewBinding after Lifecycle is destroyed or hasn't created yet. The instance of viewBinding will be not cached.")} else {thisRef.viewLifecycleOwnerLiveData.observe(thisRef) { viewLifecycleOwner ->viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {override fun onDestroy(owner: LifecycleOwner) {viewBinding = null}})}}return viewBinding!!} }class FragmentViewBindingDelegate2<VB : ViewBinding>(clazz: Class<VB>, ) : ReadOnlyProperty<Fragment, VB> {private var viewBinding: VB? = nullprivate val bindMethod = clazz.getMethod("bind", View::class.java)override fun getValue(thisRef: Fragment, property: KProperty<*>): VB {viewBinding?.let { return it }val lifecycle = thisRef.viewLifecycleOwner.lifecycleviewBinding = bindMethod.invoke(null, thisRef.requireView()) as VBif (lifecycle.currentState == Lifecycle.State.DESTROYED) {Log.w("TAG","Access to viewBinding after Lifecycle is destroyed or hasn't created yet. The instance of viewBinding will be not cached.")} else {thisRef.viewLifecycleOwnerLiveData.observe(thisRef) { viewLifecycleOwner ->viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {override fun onDestroy(owner: LifecycleOwner) {viewBinding = null}})}}return viewBinding!!} }使用
class ThreeActivity : BaseActivity() {//方式一private val viewBinding: ActivityThreeBinding by viewBindings(ActivityThreeBinding::inflate)//方式二// private val viewBinding: ActivityThreeBinding by viewBindings()override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)viewBinding.ivAvatar.setImageResource(R.mipmap.ic_launcher_round)viewBinding.tvName.text = "小白"viewBinding.tvAge.text = 38.toString()viewBinding.btn.setOnClickListener {Toast.makeText(mContext, "hello3", Toast.LENGTH_SHORT).show()}} }class ThreeFragment : BaseFragment(R.layout.fragment_three) {//方式一 // private val viewBinding: FragmentThreeBinding by viewBindings(FragmentThreeBinding::bind)//方式二private val viewBinding: FragmentThreeBinding by viewBindings()override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)viewBinding.ivAvatar.setImageResource(R.mipmap.ic_launcher_round)viewBinding.tvName.text = "小白33"viewBinding.tvAge.text = 338.toString()viewBinding.btn.setOnClickListener {Toast.makeText(mContext, "hello33", Toast.LENGTH_SHORT).show()}} }源碼分析
代碼下載
總結(jié)
以上是生活随笔為你收集整理的Jetpack ViewBinding的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 微信小程序 + 腾讯位置服务获取全国城市
- 下一篇: 如何利用wordpress搭建一个免费博