The original: Exploring ViewBinding in Depth — Using ViewBinding with < include>, < merge>, Adapters, fragments, and activities Translator: Fly_with24
Google introduced View Binding at What’s New in Architecture Components 2019 I/O
In What’s New in Architecture Components, there is a short presentation on View Binding that compares view Binding with existing solutions, And further discusses why View Binding is better than existing solutions like Data Binding or Kotlin Synwide-footing.
To me, Kotlin Synwide-footing works well, but doesn’t have compile-time safety, which means that all ids are in a global namespace. So if you use an ID with the same name and import an ID from the wrong layout, because the ID is not part of the current layout, it crashes, and you can’t know that in advance unless you run your application into that layout.
This article gives a good overview of the problems in Kotlin Synwide-footing
The Argument Over Kotlin Synthetics
View Binding will be available in Android Studio 3.6 stable. The current Android Studio stable is 3.5.3), if you want to use it, you can download Android Studio 3.6 (update 2020.02.25) or Android Studio 4.0 Canary
The main advantage of View Binding is that all the binding classes are generated by the Gradle plug-in, so it has no impact on build time and has compile-time security (as we’ll see in the example).
First, to enable view Binding, we need to add the following to the build.gradle file of the Module:
// Android Studio 3.6 Android {viewBinding {enabled =true} // Android Studio 4.0 Android {buildFeatures {viewBinding =true}}Copy the code
Note: View binding is module-by-module enabled, so if you have a multi-module project setup, you need to be in eachbuild.gradle
Add the above code to the file.
If you want to disable View Binding for a particular layout, add tools to the root view of the layout file: viewBindingIgnore = “true”.
When enabled, we can start using it immediately, and when you finish synchronizing the build.gradle file, all bound classes are generated by default.
It generates the Binding class by converting the XML layout file name to camel case and adding a Binding to the end. For example, if your layout file is named activity_splash, it will generate the binding class as ActivitySplashBinding.
How to use it?
The activity is used in
override fun onCreate(savedInstanceState: Bundle?). {
super.onCreate(savedInstanceState)
val binding: ActivitySplashBinding = ActivitySplashBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.tvVersionName.text = getString(R.string.version)
}
Copy the code
We have a layout file called Activity_splash with a TextView ID tvVersionName in it, so when using a View Binding, all we need to do is get a reference to the binding class, for example:
val binding: ActivitySplashBinding = ActivitySplashBinding.inflate(layoutInflater)
Copy the code
Use getRoot() in the setContentView() method, which returns the root layout of the layout. The view can be accessed from the binding class object we created, and can be used immediately after the object is created, as follows:
binding.tvVersionName.text = getString(R.string.version)
Copy the code
In this case, the binding class knows that tvVersionName is a TextView, so we don’t have to worry about type conversions.
Fragments used in
class HomeFragment : Fragment() {
private var _binding: FragmentHomeBinding? = null
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup? , savedInstanceState:Bundle?).: View? {
_binding = FragmentHomeBinding.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView(a) {
_binding = null}}Copy the code
Using a View Binding is a little different in the Fragment. We need to pass in LayoutInflator, ViewGroup, and a attachToRoot Boolean variable, which we obtained by overwriting onCreateView.
We can return the view by calling binding.root. You’ll also notice that we used two different variables binding and _binding, and that the _binding variable is set to null in onDestroyView().
This is because the fragment’s life cycle is different from that of an activity, and the fragment can extend beyond the life of its view, so memory leaks can occur if it is not set to NULL.
Another variable passes!! Making one variable nullable and another non-nullable avoids null-checking. .
Use in RecyclerView Adapter
class PaymentAdapter(private val paymentList: List<PaymentBean>) : RecyclerView.Adapter<PaymentAdapter.PaymentHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PaymentHolder {
val itemBinding = RowPaymentBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return PaymentHolder(itemBinding)
}
override fun onBindViewHolder(holder: PaymentHolder, position: Int) {
val paymentBean: PaymentBean = paymentList[position]
holder.bind(paymentBean)
}
override fun getItemCount(a): Int = paymentList.size
class PaymentHolder(private val itemBinding: RowPaymentBinding) : RecyclerView.ViewHolder(itemBinding.root) {
fun bind(paymentBean: PaymentBean) {
itemBinding.tvPaymentInvoiceNumber.text = paymentBean.invoiceNumber
itemBinding.tvPaymentAmount.text = paymentBean.totalAmount
}
}
}
Copy the code
Row_payment. XML is our layout file for RecyclerView Item, corresponding to the generated binding class RowPaymentBinding.
Now, all we need to do is call the inflate() method in onCreateViewHolder() to generate the RowPaymentBinding object and pass it to the PaymentHolder main constructor, And pass itembinding. root to the recyclerView.viewholder () constructor.
To deal with<include>
The label
A View Binding can be used with
tags. Layouts typically contain two types of
tags, with or without the
tag.
<inlude>
Don’t take<merge>
The label
We need to assign an ID to
and then use that ID to access the view in the include layout. Let’s look at an example.
app_bar.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="wrap_content">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="0dp"
android:layout_height="? actionBarSize"
android:background="? colorPrimary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Copy the code
main_layout.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">
<include
android:id="@+id/appbar"
layout="@layout/app_bar"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Copy the code
In the code above, we have included a generic toolbar in the layout file,
has an Android :id= “@+id/appbar” ID, which we will use to access the toolbar from app_bar.xml and set it as our Action bar.
override fun onCreate(savedInstanceState: Bundle?). {
super.onCreate(savedInstanceState)
val binding: MainLayoutBinding = MainLayoutBinding.inflate(layoutInflater)
setContentView(binding.root)
setSupportActionBar(binding.appbar.toolbar)
}
Copy the code
<inlude>
带<merge>
The label
When we include another layout in a layout, we usually use a layout with a
tag, which helps eliminate layout nesting.
placeholder.xml
<?xml version="1.0" encoding="utf-8"? >
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<TextView
android:id="@+id/tvPlaceholder"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</merge>
Copy the code
fragment_order.xml
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/placeholder" />
</androidx.constraintlayout.widget.ConstraintLayout>
Copy the code
If we try to provide an ID for the
, the View Binding does not generate an ID in the binding class, so we cannot access the view as we would with a normal include.
In this case we have a PlaceholderBinding, which is an automatically generated class from placeholder. XML (
layout file). We can call its bind() method and pass in the root view of the layout that contains it.
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup? , savedInstanceState:Bundle?).: View {
binding = FragmentOrderBinding.inflate(layoutInflater, container, false)
placeholderBinding = PlaceholderBinding.bind(binding.root)
placeholderBinding.tvPlaceholder.text = getString(R.string.please_wait)
return binding.root
}
Copy the code
Then, we can start our class (such as placeholderBinding. TvPlaceholder. Text) access placeholder. The inside of the XML view.
Thanks for reading. We look forward to receiving your comments.
- Android Developer docs — view binding
Translators supplement
Using a View binding in a Fragment is a bit cumbersome. We provide a BaseFragment wrapper for your reference
abstract class BaseFragment<T : ViewBinding>(layoutId: Int) : Fragment(layoutId) {
private var _binding: T? = null
val binding get() = _binding!!
override fun onViewCreated(view: View, savedInstanceState: Bundle?). {
super.onViewCreated(view, savedInstanceState)
_binding = initBinding(view)
init()
}
/** * initializes [_binding] */
abstract fun initBinding(view: View): T
abstract fun init(a)
override fun onDestroyView(a) {
_binding = null
super.onDestroyView()
}
}
Copy the code
class HomeFragment : BaseFragment<FragmentHomeBinding>(R.layout.fragment_home) {
override fun initBinding(view: View): FragmentHomeBinding = FragmentHomeBinding.bind(view)
override fun init(a) {
binding.viewPager.adapter = SectionsPagerAdapter(this)
TabLayoutMediator(binding.tabs, binding.viewPager) { tab, position ->
tab.text = TAB_TITLES[position]
}.attach()
}
}
Copy the code
Add 2 (2020.02.27 update) on ViewBinding null security
The ViewBinding rotates the screen and so on. The ViewBinding rotates the screen and so on.
- Create two sets of horizontal and vertical layout files with only one content
TextView
And the ids are respectivelyhello1
hello2
- using
kotlin
和java
createactivity
, the use ofViewBinding
将hello1
text
Set it to “Hello” - Run the project and switch between horizontal and vertical screens
Conclusion:kotlin
Language project failed to compile,java
Language item running properly, null pointer exception after rotation
Analysis: Open the Binding class generated by ViewBinding
/** * This binding is not available in all configurations. * * Present: *
*
- layout/
*
* * Absent: *
*
- layout-land/
*
*/
@Nullable
public final TextView hello1;
/** * This binding is not available in all configurations. * * Present: *
*
- layout-land/
*
* * Absent: *
*
- layout/
*
*/
@Nullable
public final TextView hello2;
Copy the code
The @nullable annotation explains why Kotlin failed to compile, and for Java it will only alert developers to the possibility of a null pointer at that location
About me
I am a fly_with24
- The Denver nuggets
- Jane’s book
- Github