Android?ViewBinding使用介紹
一、kotlin-android-extensions
在使用ViewBinding之前,我們一直使用的是kotlin-android-extensions,使用kotlin-android-extensions可以節(jié)約很多寫findViewById的時間。不過這個kotlin-android-extensions插件已經(jīng)廢棄了,簡單說一下kotlin-android-extensions存在的問題:
1.通過反編譯kotlin-android-extensions的代碼,發(fā)現(xiàn)會創(chuàng)建一個HashMap,用來存放所有的id和對應(yīng)的View的緩存,如果緩存中沒有View,那么就通過findViewById去創(chuàng)建并存入緩存,否則就直接獲取。所以會存在內(nèi)存問題。
private HashMap _$_findViewCache;
public View _$_findCachedViewById(int var1) {
if (this._$_findViewCache == null) {
this._$_findViewCache = new HashMap();
}
View var2 = (View)this._$_findViewCache.get(var1);
if (var2 == null) {
View var10000 = this.getView();
if (var10000 == null) {
return null;
}
var2 = var10000.findViewById(var1);
this._$_findViewCache.put(var1, var2);
}
return var2;
}
public void _$_clearFindViewByIdCache() {
if (this._$_findViewCache != null) {
this._$_findViewCache.clear();
}
}
// $FF: synthetic method
public void onDestroyView() {
super.onDestroyView();
this._$_clearFindViewByIdCache();
}2.由于kotlin-android-extensions是通過view的id名直接引用的,如果多個布局間的同名id,就需要手動對import進(jìn)行重命名處理,如果引用錯誤的布局文件,就會出現(xiàn)crash。所以存在資源重名的問題。
3.只有Kotlin才可以使用。
所以ViewBinding優(yōu)勢有:java,kotlin都可以使用,可以有效避免NullPointerException。
二、ViewBinding使用
1.gradle配置
buildFeatures {
viewBinding true
}
開啟ViewBinding之后,在編譯時,AGP會自動幫我們給每個xml布局創(chuàng)建一個Binding類,位于build/generated/data_binding_base_class_source_out/目錄下。

public final class FragmentLoginBinding implements ViewBinding {
@NonNull
private final ConstraintLayout rootView;
@NonNull
public final ConstraintLayout container;
@NonNull
public final ProgressBar loading;
@NonNull
public final Button login;
@NonNull
public final EditText password;
@NonNull
public final EditText username;
private FragmentLoginBinding(@NonNull ConstraintLayout rootView,
@NonNull ConstraintLayout container, @NonNull ProgressBar loading, @NonNull Button login,
@NonNull EditText password, @NonNull EditText username) {
this.rootView = rootView;
this.container = container;
this.loading = loading;
this.login = login;
this.password = password;
this.username = username;
}
@Override
@NonNull
public ConstraintLayout getRoot() {
return rootView;
}
@NonNull
public static FragmentLoginBinding inflate(@NonNull LayoutInflater inflater) {
return inflate(inflater, null, false);
}
@NonNull
public static FragmentLoginBinding inflate(@NonNull LayoutInflater inflater,
@Nullable ViewGroup parent, boolean attachToParent) {
View root = inflater.inflate(R.layout.fragment_login, parent, false);
if (attachToParent) {
parent.addView(root);
}
return bind(root);
}
@NonNull
public static FragmentLoginBinding bind(@NonNull View rootView) {
// The body of this method is generated in a way you would not otherwise write.
// This is done to optimize the compiled bytecode for size and performance.
int id;
missingId: {
ConstraintLayout container = (ConstraintLayout) rootView;
id = R.id.loading;
ProgressBar loading = rootView.findViewById(id);
if (loading == null) {
break missingId;
}
id = R.id.login;
Button login = rootView.findViewById(id);
if (login == null) {
break missingId;
}
id = R.id.password;
EditText password = rootView.findViewById(id);
if (password == null) {
break missingId;
}
id = R.id.username;
EditText username = rootView.findViewById(id);
if (username == null) {
break missingId;
}
return new FragmentLoginBinding((ConstraintLayout) rootView, container, loading, login,
password, username);
}
String missingId = rootView.getResources().getResourceName(id);
throw new NullPointerException("Missing required view with ID: ".concat(missingId));
}
}注意:
1.因?yàn)檫@些類編譯時就生成了,就不會占用運(yùn)行時內(nèi)存。
2.未使用的Binding文件會在混淆時被刪除,所以對包大小影響很小。
3.編譯器生成Binding文件是增量更新的。
那么如何不生成Binding類呢?tools:viewBindingIgnore="true"
<androidx.constraintlayout.widget.ConstraintLayout 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:viewBindingIgnore="true"
tools:context=".MainActivity">2.在Activity 使用
class TestViewBindingActivity : AppCompatActivity() {
private lateinit var bindding: ActivityTestViewBindingBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
bindding = ActivityTestViewBindingBinding.inflate(layoutInflater)
setContentView(bindding.root)
changeText()
}
private fun changeText() {
bindding.titleTv.text = "哈哈,在Activity中使用ViewBinding了"
}
}<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".testviewbinding.TestViewBindingActivity">
<TextView
android:id="@+id/titleTv"
android:layout_width="match_parent"
android:layout_height="100dp"
android:gravity="center"
android:text="在Activity中使用ViewBinding"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
3.在Fragment使用
class TextViewBindingFragment : Fragment() {
private var param1: String? = null
private var param2: String? = null
private var _binding: FragmentTextViewBindingBinding? = null
private val binding get() = _binding!!
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
param1 = it.getString(ARG_PARAM1)
param2 = it.getString(ARG_PARAM2)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentTextViewBindingBinding.inflate(layoutInflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
changeText()
}
private fun changeText() {
binding.tvTitle.text = "哈哈,在Fragment中使用ViewBinding"
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
companion object {
@JvmStatic
fun newInstance(param1: String, param2: String) =
TextViewBindingFragment().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
}
}
@JvmStatic
fun newInstance() = TextViewBindingFragment()
}
}class TestViewBindingActivity : AppCompatActivity() {
private lateinit var bindding: ActivityTestViewBindingBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
bindding = ActivityTestViewBindingBinding.inflate(layoutInflater)
setContentView(bindding.root)
val newInstance = TextViewBindingFragment.newInstance()
addFragment(
supportFragmentManager,
newInstance,
isAllowStateLoss = true,
frameId = R.id.fragmentFrame
)
}
}<?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=".testviewbinding.TextViewBindingFragment">
<TextView
android:id="@+id/tvTitle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="在Fragment中" />
</FrameLayout>
4.在Adapter中使用
class TestAdapterActivity : AppCompatActivity() {
private lateinit var binding: ActivityTestAdapterBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityTestAdapterBinding.inflate(layoutInflater)
setContentView(binding.root)
initView()
}
companion object {
val ITEMS = mutableListOf<String>("1", "2", "3", "4", "5", "6")
}
private fun initView() {
with(binding.contentRcycler) {
layoutManager = GridLayoutManager(context, 4)
adapter = TestRecyclerViewAdapter(ITEMS)
}
}
}<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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=".testviewbinding.TestAdapterActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/contentRcycler"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>class TestRecyclerViewAdapter(private val values: List<String>) :
RecyclerView.Adapter<TestRecyclerViewAdapter.ViewHolder>() {
inner class ViewHolder(binding: RecyclerItemLayoutBinding) :
RecyclerView.ViewHolder(binding.root) {
val textTv = binding.contentTv
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
RecyclerItemLayoutBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = values[position]
holder.textTv.text = item
}
override fun getItemCount(): Int = values.size
}<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="200dp">
<TextView
android:id="@+id/contentTv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textSize="16dp"
tools:text="99" />
</androidx.constraintlayout.widget.ConstraintLayout>
5.在Dialog中使用
class CommonDialog(context: Context) : Dialog(context) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(DialogLayoutBinding.inflate(layoutInflater).root)
}
}<?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/dialogContent"
android:layout_width="200dp"
android:layout_height="200dp"
android:text="This is Dialog"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
6.Include中使用
class TestIncludeActivity : AppCompatActivity() {
private lateinit var binding: ActivityTestIncludeBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityTestIncludeBinding.inflate(layoutInflater)
setContentView(binding.root)
initView()
}
private fun initView() {
binding.itemInclude.itemContentTv.text = "哈哈, this is include"
}
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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=".testviewbinding.TestIncludeActivity">
<include
android:id="@+id/itemInclude"
layout="@layout/item_layout" />
</androidx.constraintlayout.widget.ConstraintLayout><?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/itemContentTv"
android:layout_width="match_parent"
android:layout_height="200dp"
android:text="Test include"
android:textSize="30sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
三、ViewBinding封裝
1.在BaseActivity中封裝
abstract class BaseViewBindingActivity<T : ViewBinding> : AppCompatActivity() {
protected val binding by lazy {
getViewBinding()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
}
protected abstract fun getViewBinding(): T
}class ChildViewBindingMainActivity :
BaseViewBindingActivity<ActivityChildViewBindingMainBinding>() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding.titleTv.text = "哈哈,this is child binding activity"
}
override fun getViewBinding(): ActivityChildViewBindingMainBinding {
return ActivityChildViewBindingMainBinding.inflate(layoutInflater)
}
}<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".encapsulatviewbinding.ChildViewBindingMainActivity">
<TextView
android:id="@+id/titleTv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="36sp" />
</androidx.constraintlayout.widget.ConstraintLayout>
2.通過反射的方式封裝
class TestViewBindingMainActivity : AppCompatActivity() {
private val binding by inflate<ActivityTestViewBindingMainBinding>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding.titleTv.text = "哈哈,通過反射封裝ViewBinding"
}
}
inline fun <reified T : ViewBinding> inflateByViewBinding(layoutInflater: LayoutInflater) =
T::class.java.getMethod("inflate", LayoutInflater::class.java).invoke(null, layoutInflater) as T
inline fun <reified T : ViewBinding> Activity.inflate() = lazy {
inflateByViewBinding<T>(layoutInflater).apply {
setContentView(root)
}
}<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".encapsulatviewbinding.TestViewBindingMainActivity">
<TextView
android:id="@+id/titleTv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textSize="36sp" />
</androidx.constraintlayout.widget.ConstraintLayout>
3.反射+基類
1.在Activity 中使用
abstract class BaseBindingMainActivity2<T : ViewBinding> : AppCompatActivity() {
protected lateinit var binding: T
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val type = javaClass.genericSuperclass
if (type is ParameterizedType) {
val clazz = type.actualTypeArguments[0] as Class<T>
val method = clazz.getMethod("inflate", LayoutInflater::class.java)
binding = method.invoke(null, layoutInflater) as T
}
setContentView(binding.root)
}
}class ChildViewBindingMainActivity2 :
BaseBindingMainActivity2<ActivityChildViewBindingMain2Binding>() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding.titleTv.text = "哈哈,這是反射+基類的方式"
}
}<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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=".encapsulatviewbinding.ChildViewBindingMainActivity2">
<TextView
android:id="@+id/titleTv"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
2.在Fragment中使用
abstract class BaseBindingViewFragment<T : ViewBinding> : Fragment() {
private var _binding: T? = null
protected val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val type = javaClass.genericSuperclass
val clazz = (type as ParameterizedType).actualTypeArguments[0] as Class<T>
val method = clazz.getMethod(
"inflate",
LayoutInflater::class.java,
ViewGroup::class.java,
Boolean::class.java
)
_binding = method.invoke(null, layoutInflater, container, false) as T
this.viewLifecycleOwner.lifecycle.addObserver(object : LifecycleEventObserver {
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (event == Lifecycle.Event.ON_DESTROY) {
Log.v(TAG, "onDestroy binding be null")
_binding = null
}
}
})
return binding.root
}
companion object {
const val TAG = "BaseBindingViewFragment"
}
}class ChildBindingFragment : BaseBindingViewFragment<FragmentChildBindingBinding>() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return super.onCreateView(inflater, container, savedInstanceState)
}
companion object {
@JvmStatic
fun newInstance() = ChildBindingFragment()
}
}class TestBindingMainActivity3 : BaseBindingMainActivity2<ActivityTestBindingMain3Binding>() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val newInstance = ChildBindingFragment.newInstance()
addFragment(
supportFragmentManager,
newInstance,
isAllowStateLoss = true,
frameId = R.id.frame
)
}
}<?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=".encapsulatviewbinding.ChildBindingFragment">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/hello_blank_fragment" />
</FrameLayout>
4.委托的方式
class TestViewBindingFragment2 : Fragment(R.layout.fragment_test_view_binding2) {
private val binding by inflate<FragmentTestViewBinding2Binding>()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.root
}
companion object {
@JvmStatic
fun newInstance() = TestViewBindingFragment2()
}
}
inline fun <reified T : ViewBinding> Fragment.inflate() =
FragmentViewBindingDelegate(T::class.java)
class FragmentViewBindingDelegate<T : ViewBinding>(private val clazz: Class<T>) :
ReadOnlyProperty<Fragment, T> {
private var binding: T? = null
override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
if (binding == null) {
binding =
clazz.getMethod("bind", View::class.java).invoke(null, thisRef.requireView()) as T
thisRef.viewLifecycleOwner.lifecycle.addObserver(object : LifecycleEventObserver {
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (event == Lifecycle.Event.ON_DESTROY) {
binding = null
}
}
})
}
return binding!!
}
}<?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=".encapsulatviewbinding.TestViewBindingFragment2">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="8888888" />
</FrameLayout>
到此這篇關(guān)于Android ViewBinding使用介紹的文章就介紹到這了,更多相關(guān)Android ViewBinding內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android kotlin RecyclerView遍歷json實(shí)現(xiàn)列表數(shù)據(jù)的案例
這篇文章主要介紹了Android kotlin RecyclerView遍歷json實(shí)現(xiàn)列表數(shù)據(jù)的案例,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧2024-08-08
Android判斷用戶是否允許了攝像頭權(quán)限實(shí)例代碼
本篇文章主要介紹了Android判斷用戶是否允許了攝像頭權(quán)限實(shí)例代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-04-04
Android實(shí)現(xiàn)打開本地pdf文件的兩種方式
在現(xiàn)代應(yīng)用中,PDF格式因其跨平臺、穩(wěn)定性好、展示內(nèi)容一致等特點(diǎn),在Android平臺上,如何高效地打開本地PDF文件,不僅關(guān)系到用戶體驗(yàn),也直接影響到應(yīng)用的功能豐富度,本文將給大家詳細(xì)介紹了Android打開本地pdf文件的兩種方式,需要的朋友可以參考下2025-04-04
Android編程實(shí)現(xiàn)自定義ProgressBar樣式示例(背景色及一級、二級進(jìn)度條顏色)
這篇文章主要介紹了Android編程實(shí)現(xiàn)自定義ProgressBar樣式功能,涉及針對背景色及一級、二級進(jìn)度條顏色的操作技巧,需要的朋友可以參考下2017-01-01
Android使用Notification實(shí)現(xiàn)普通通知欄(一)
這篇文章主要為大家詳細(xì)介紹了Android使用Notification實(shí)現(xiàn)普通通知欄,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-12-12
Android本地數(shù)據(jù)存儲Room實(shí)踐和優(yōu)化技巧
本文詳細(xì)介紹了Android本地數(shù)據(jù)存儲框架Room的使用,包括基本概念、核心組件、最佳實(shí)踐、優(yōu)化技巧等,幫助開發(fā)者學(xué)習(xí)和掌握Room的使用方法,提升數(shù)據(jù)存儲效率和應(yīng)用性能2023-04-04
flutter?Bloc?實(shí)現(xiàn)原理示例解析
這篇文章主要為大家介紹了flutter?Bloc實(shí)現(xiàn)原理示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11

