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.gradleAdd 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.

  1. Create two sets of horizontal and vertical layout files with only one contentTextViewAnd the ids are respectivelyhello1 hello2
  2. usingkotlinjavacreateactivity, the use ofViewBindinghello1 textSet it to “Hello”
  3. Run the project and switch between horizontal and vertical screens

Conclusion:kotlinLanguage project failed to compile,javaLanguage 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