Hello, View Binding! See you again, findViewById!

As an Android developer, you must have heard of findViewById. This method can match the corresponding View according to the ID. Similar functions have been realized or enhanced:

  • Butter Knife(Kotter Knife)
  • Kotlin Android Extensions
  • Data Binding

  • Why isn't View Binding findViewById/Butter Knife(Kotter Knife)/Kotlin Android Extensions?
// findViewById
val fab = findViewById<FloatingActionButton>(R.id.fab)
val toolbar = findViewById<Toolbar>(R.id.toolbar)

setSupportActionBar(toolbar)
fab.setOnClickListener { view ->

}

// Kotter Knife
val fab: FloatingActionButton by bindView(R.id.fab)
val toolbar: Toolbar by bindView(R.id.toolbar)

setSupportActionBar(toolbar)
fab.setOnClickListener { view ->

}

// Kotlin Android Extensions
import kotlinx.android.synthetic.main.activity_main.*

setSupportActionBar(toolbar)
fab.setOnClickListener { view ->

}

// Data Binding & View Binding
val binding = ActivityMainBinding.inflate(layoutInflater)

setSupportActionBar(binding.toolbar)
binding.fab.setOnClickListener { view ->

}

Elegance

What is certain is that findViewById and Kotter Knife are the least elegant. Every time a view is initialized, the findViewById or bindView method needs to be called once, which results in a lot of template code in the activity or fragment. Kotlin Android Extensions (view cache map), Data Binding and View Binding (binding class) reduces the generation of template code by generating an intermediate variable.Imagine if you have 20 views that need to be initialized.

Type safety

// API 26 之前
public final View findViewById(int id) {
    if (id < 0) {
        return null;
    }
    return findViewTraversal(id);
}

protected View findViewTraversal(int id) {
    if (id == mID) {
        return this;
    }
    return null;
}

// API 26 及以后
@Nullable
public final <T extends View> T findViewById(@IdRes int id) {
    if (id == NO_ID) {
        return null;
    }
    return findViewTraversal(id);
}

protected <T extends View> T findViewTraversal(@IdRes int id) {
    if (id == mID) {
        return (T) this;
    }
    return null;
}

Prior to API 26, initializing the view requires a forced transfer, such as val fab = findViewById (R.id.fab) as FloatingActionButton. As we all know (and I believe you have encountered), forced transfer is likely to produce ClassCastException. Although Google In API 26, the findViewById method was updated to be a generic implementation, but there is still a problem of forced transfer. Imagine this code: val fab = findViewById (R.id.fab), there will be no problems at compile time, but it is obvious An error will occur during operation. Butter Knife and Kotter Knife also have this problem.

Empty security

First talk about Kotlin Android Extensionsthe problems.

import kotlinx.android.synthetic.main.fragment_main.*

class MainFragment : Fragment() {

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val rootView = inflater.inflate(R.layout.fragment_main, container, false)

        text.setOnClickListener {

        }

        return rootView
    }

}
<!-- fragment_main.xml -->
<androidx.constraintlayout.widget.ConstraintLayout ...>

    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/text"
        ... />

</androidx.constraintlayout.widget.ConstraintLayout>

It seems that there is no problem, right, after actually running:

Caused by: android.view.InflateException: Binary XML file line #24 in io.tonnyl.demo:layout/activity_main: Binary XML file line #8 in io.tonnyl.demo:layout/content_main: Error inflating class fragment
Caused by: android.view.InflateException: Binary XML file line #8 in io.tonnyl.demo:layout/content_main: Error inflating class fragment
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void androidx.appcompat.widget.AppCompatTextView.setOnClickListener(android.view.View$OnClickListener)' on a null object reference

Let's look at the generated code:

public final class MainFragment extends Fragment {
    private HashMap _$_findViewCache;

    @Nullable
    public View onCreateView(@NotNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
       Intrinsics.checkParameterIsNotNull(inflater, "inflater");
       View rootView = inflater.inflate(1300003, container, false);
       ((AppCompatTextView)this._$_findCachedViewById(id.text)).setOnClickListener((OnClickListener)null.INSTANCE);
       return rootView;
    }

    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;
    }
}

The problem is in the this_getView () line of the _ $ _ findCachedViewById method. When the onCreateView () method is called, there is no return value, so this.getView () returns null, and calling text.setOnClickListener {} in onCreateView will not have any Error message, because text is considered non-empty here.

Of course, the above problem can still be solved.

import kotlinx.android.synthetic.main.fragment_main.view.*

class MainFragment : Fragment() {

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val rootView = inflater.inflate(R.layout.fragment_main, container, false)

        rootView.text.setOnClickListener {}

        return rootView
    }

}

Note the changes in the imported classes. Look at the changes in the generated code:

public final class MainFragment extends Fragment {

    @Nullable
    public View onCreateView(@NotNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
       Intrinsics.checkParameterIsNotNull(inflater, "inflater");
       View rootView = inflater.inflate(1300003, container, false);
       Intrinsics.checkExpressionValueIsNotNull(rootView, "rootView");
       ((AppCompatTextView)rootView.findViewById(id.text)).setOnClickListener((OnClickListener)null.INSTANCE);
       return rootView;
    }
}

Here it is no longer through _ $ _ findViewCache but directly through findViewById.

Kotlin Android Extensions still have some other problems, and Google is no longer used internally. For specific reasons, please refer to Why kotlinx synthetic is no longer a recommended practice.

Let me talk about the problem of findViewById and Butter Knife (Kotter Knife). Consider the following code:

val fab = findViewById<FloatingActionButton>(View.NO_ID)
val fab2: FloatingActionButton by bindView(0)

We all hope that problems can be discovered at compile time, not at runtime. Unfortunately, the above code happens to have no problems at compile time and will run wrong.

The view binding will directly create a reference to the view, so do n’t worry about the null pointer error caused by the invalid view ID. And, if a view only appears in the partially configured layout, then the binding class will contain the referenced field. Is marked as @Nullable.

Why not Data Binding?

Only when the root tag of the layout file is, Data Binding will generate the corresponding binding class, View Binding does not have such requirements;
Data Binding will affect the speed of construction. The bottom layer of Data Binding is actually achieved by the annotation processor, and there is a speed for the construction. Negatively affected. View Binding is not implemented by annotation processor, so it solves the performance problem of Data Binding.
What is View Binding?

View Binding is a function that makes it easier for you to write code that interacts with views. When View Binding is enabled in a module, it generates a corresponding binding class for each XML file that exists in the module ). An instance of the binding class contains direct references to all views with IDs in the corresponding layout. In
most cases, View Binding can replace findViewById.
From Android Developers.

View Binding first appeared at the 2019 Google I / O conference What's New in Android (Google I / O'19) speech.

How to use View Binding?

Requirements

You need at least Android Studio 3.6 Canary 11 and above to enable View Binding.

Setup guide

View Binding can be opened module by module. For example, our project consists of 2 modules (A and B), we can choose to enable View Binding only in module A without affecting module B. Enable View Binding in module , First you need to add the following code in the module's build.gradle file:

android {
  ...
  viewBinding {
      enabled = true
  }
}

If we want to ignore a layout file when generating a binding class, we need to add tools: viewBindingIgnore = “true” attribute to the root element of the layout file.

<LinearLayout
      ...
      tools:viewBindingIgnore="true" >
  ...
</LinearLayout>

Instructions

Like Data Binding, View Binding will convert the underlined name of the XML file into a binding class with a hump style and ending with Binding.

For example, we have a result_profile.xml layout file:

<LinearLayout ... >
  <TextView android:id="@+id/name" />
  <ImageView android:cropToPadding="true" />
  <Button android:id="@+id/button"
      android:background="@drawable/rounded_button" />
</LinearLayout>

The name of the generated binding class is ResultProfileBinding. This class has two fields: a TextView called name and a Button called button. The ImageView in the layout file has no ID, so there is no reference to it in the binding class.

Each binding class contains a getRoot () method that provides a direct reference to the root view of the corresponding layout file. In the above example, the getRoot () method in the ResultProfileBinding class returns the root view LinearLayout.

We can call the inflate () static method to get the instance of the generated binding class. Generally speaking, you need to call the setContentView () method, passing the root view of the generated class as a parameter, as the content on the screen. In the above example , We can call ResultProfileBinding.inflate () in activity.

private lateinit var binding: ResultProfileBinding

@Override
fun onCreate(savedInstanceState: Bundle) {
  super.onCreate(savedInstanceState)
  binding = ResultProfileBinding.inflate(layoutInflater)
  setContentView(binding.root)
}

Then you can reference any view through the binding class:

binding.name.text = viewModel.name
binding.button.setOnClickListener { viewModel.userClicked() }

Conclusion

That's right, we have to say goodbye to findViewById again. View Binding is indeed simple enough and powerful enough. Jake Wharton also marked Kotter Knife as `` deprecated '' and recommended using View Binding.

If you have a chance, I suggest you try it.

At the end of the article, I recommend to you the simplest and most effective way to improve learning: brain map + video + information

In this also own a share included finishing Ali P6P7 Andrews [+] Advanced information sharing raises questions quit the necessary surface , as well as advanced technical architecture Advanced Brain Mapping, Android interview with thematic development, advanced materials advanced architecture of these These are all fine materials that I will read repeatedly in my spare time. In the brain map, each topic of knowledge points is equipped with a corresponding actual combat project, which can effectively help you master the knowledge points.

In short, it is here to help you learn and improve your progress, and save you time to search for information on the Internet.

If you need it, you can like + comment , follow me , add Vx: q1607947758 (remarks brief book, need advanced information)

Published 488 original articles · praised 85 · 230,000 views +

Guess you like

Origin blog.csdn.net/Coo123_/article/details/104420712