Android?ViewBinding使用介紹
一、kotlin-android-extensions
在使用ViewBinding之前,我們一直使用的是kotlin-android-extensions,使用kotlin-android-extensions可以節(jié)約很多寫findViewById的時(shí)間。不過這個(gè)kotlin-android-extensions插件已經(jīng)廢棄了,簡(jiǎn)單說一下kotlin-android-extensions存在的問題:
1.通過反編譯kotlin-android-extensions的代碼,發(fā)現(xiàn)會(huì)創(chuàng)建一個(gè)HashMap,用來存放所有的id和對(duì)應(yīng)的View的緩存,如果緩存中沒有View,那么就通過findViewById去創(chuàng)建并存入緩存,否則就直接獲取。所以會(huì)存在內(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名直接引用的,如果多個(gè)布局間的同名id,就需要手動(dòng)對(duì)import進(jìn)行重命名處理,如果引用錯(cuò)誤的布局文件,就會(huì)出現(xiàn)crash。所以存在資源重名的問題。
3.只有Kotlin才可以使用。
所以ViewBinding優(yōu)勢(shì)有:java,kotlin都可以使用,可以有效避免NullPointerException。
二、ViewBinding使用
1.gradle配置
buildFeatures {
viewBinding true
}
開啟ViewBinding之后,在編譯時(shí),AGP會(huì)自動(dòng)幫我們給每個(gè)xml布局創(chuàng)建一個(gè)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)檫@些類編譯時(shí)就生成了,就不會(huì)占用運(yùn)行時(shí)內(nèi)存。
2.未使用的Binding文件會(huì)在混淆時(shí)被刪除,所以對(duì)包大小影響很小。
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)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android通過SharedPreferences實(shí)現(xiàn)自動(dòng)登錄記住用戶名和密碼功能
最近使用SharedPreferences實(shí)現(xiàn)了一個(gè)android自動(dòng)登錄功能,特此分享到腳本之家平臺(tái)供大家參考2017-07-07Android開發(fā)工程中集成mob短信驗(yàn)證碼功能的方法
這篇文章主要介紹了Android開發(fā)工程中集成mob短信驗(yàn)證碼功能的方法,感興趣的小伙伴們可以參考一下2016-05-05Android開發(fā)自學(xué)筆記(六):聲明權(quán)限和Activity
這篇文章主要介紹了Android開發(fā)自學(xué)筆記(六):聲明權(quán)限和Activity,本文是上一篇的補(bǔ)充,需要的朋友可以參考下2015-04-04android view轉(zhuǎn)Bitmap生成截圖的方法
這篇文章主要介紹了android view轉(zhuǎn)Bitmap生成截圖的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-09-09Android開發(fā)中使用外部應(yīng)用獲取SD卡狀態(tài)的方法
這篇文章主要介紹了Android開發(fā)中使用外部應(yīng)用獲取SD卡狀態(tài)的方法,簡(jiǎn)單分析了Android監(jiān)聽SD卡狀態(tài)的方法,并結(jié)合實(shí)例形式分析了Android外部應(yīng)用獲取SD卡狀態(tài)的相關(guān)操作技巧,需要的朋友可以參考下2017-11-11Android SeekBar 自定義thumb旋轉(zhuǎn)動(dòng)畫效果
某些音樂播放或者視頻播放的界面上,資源還在加載時(shí),進(jìn)度條的原點(diǎn)(thumb)會(huì)顯示一個(gè)轉(zhuǎn)圈的效果。這篇文章主要介紹了Android SeekBar 自定義thumb thumb旋轉(zhuǎn)動(dòng)畫效果,需要的朋友可以參考下2021-11-11Android布局之LinearLayout自定義高亮背景的方法
這篇文章主要介紹了Android布局之LinearLayout自定義高亮背景的方法,實(shí)例分析了Android中LinearLayout布局參數(shù)設(shè)置技巧,需要的朋友可以參考下2016-01-01