During our development, we needed to get the ViewId in the XML layout file to display the assignment, and we used to use findViewById, which resulted in a lot of template code. Until Jake Wharton open-source the Butter Knife framework to Bind ViewId. With Google’s support for Kotlin in recent years, we started using the Android Kotlin Extensions. Import the layout file in the file and reference viewId directly. No additional operations required, most convenient. Google has now added ViewBinding to Android Studio 3.6 Canary 11 and later.

Note: to use the ViewBinding feature, AndroidStudio needs to be upgraded to at least 3.6.

ViewBinding introduction

What is a ViewBinding?

  • As the name implies, ViewBinding means how to bind a view to your code. So the main problem is how to safely and elegantly refer from code to view controls in XML layout files. Until now, the dominant way to build user interfaces on Android has been to use LAYOUT files in XML format. ViewBinding website

View Binding benefits:

Methods to access the view concise Compile safety Compilation speed
findViewById x x Square root
DataBinding Square root Square root x
ButterKnife Square root x x
Kotlin Synthetic Square root x Square root
View Binding Square root Square root Square root
  • Null security: Because view binding creates a direct reference to the view, there is no risk that Null pointer exceptions will be thrown because the view ID is invalid. In addition, if the view appears only in some configuration of the layout, the fields in the binding class that contain its references are used@NullableThe tag.
  • Type safety: Fields in each binding class have a type that matches the view they reference in the XML file. This means that there is no risk of class conversion exceptions.

The use of ViewBinding

ViewBinding can be enabled by module. To enable it in a module, add the following configuration to the build.gradle for that module:

android {
    ...
    buildFeatures {
        viewBinding = true
    }
    ...
}

Copy the code

The recompilation generates a Binding class for each layout file that contains direct references to all views with ids in the layout. The generated classes are stored in the /build/generated/data_binding_base_class_source_out directory.

If you want to ignore a layout file when generating binding classes, add tools:viewBindingIgnore=”true” to the root view of the corresponding layout file:


      
<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"  <!--Ignore generating layout file properties-->
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
Copy the code

ViewBinding Use instance

Use the Activity

Once the view binding function is used, a binding class is generated for each XML layout file contained in the module. The class name is named after the XML layout file name with the word uppercase and Binding removed after the drop line. For example, activity_main.xml generates the class ActivityMainBinding.

  • To set the binding in your Activity, perform the following steps in the Activity’s onCreate() method:

    1. Call the static contained in the generated binding classinflate()Methods. This action creates an instance of the binding class for the Activity to use
    2. By calling thegetRoot()Method or useKotlin attribute syntaxGets a reference to the root view.
    3. Pass the root view to setContentView() to make it the active view on the screen.
  • activity_main.xml:


      
<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=".MainActivity">

    <Button
        android:id="@+id/button"
        android:layout_width="100dp"
        android:layout_height="40dp"
        android:text="Here's the button."
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView" />


    <TextView
        android:id="@+id/textView"
        android:layout_width="100dp"
        android:layout_height="40dp"
        android:gravity="center"
        android:text="Hello World!"
        app:layout_constraintBottom_toTopOf="@+id/button"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>  
Copy the code

// MainActivity.java:

public class MainActivity extends AppCompatActivity {
    private ActivityMainBinding binding;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Key code
        binding = ActivityMainBinding.inflate(getLayoutInflater());
        View rootView = binding.getRoot();
        setContentView(rootView);
        // How to use it
        binding.textView.setText("This is modified.");
        binding.button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.d("Main"."Click the button."); }}); }}Copy the code

Fragments using

  • How to use view binding in your Fragment Do the following steps in your Fragment’s onCreateView() method (Note: Fragments exist longer than their views. Be sure to clear all references to the bound class instance in the Fragment onDestroyView() method. )
    1. Call the static contained in the generated binding classinflate()Methods. This creates an instance of the bound class for use by the Fragment.
    2. By calling thegetRoot()Method or useKotlin attribute syntaxGets a reference to the root view.
    3. fromonCreateView()Method returns the root view, making it the active view on the screen. You can use the bind(view) method in onViewCreated() if the view is already created.The official sample
  • fragment_my.xml

      
<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=".MainActivity">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="40dp"
        android:text="This is the Fragment button."
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView" />


    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="40dp"
        android:gravity="center"
        android:text="It's FragmentTextView"
        app:layout_constraintBottom_toTopOf="@+id/button"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
Copy the code
  • MyFragment.java
public class MyFragment extends Fragment {
 private FragmentMyBinding binding;

 public MyFragment(a) {}@Override
 public void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);

 }

 @Override
 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
     binding = FragmentMyBinding.inflate(inflater, container, false);
     return binding.getRoot();
 }

 @Override
 public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
     super.onViewCreated(view, savedInstanceState);
     binding.textView.setText("It's fragments");
     binding.button.setOnClickListener(new View.OnClickListener() {
         @Override
         public void onClick(View view) {
             Log.d("Fragment"."Click the button."); }}); }@Override
 public void onDestroy(a) {
     super.onDestroy();
     binding = null;
 }
Copy the code
  • Use a View Binding in onViewCreated
public class MyFragment extends Fragment {
  private FragmentMyBinding binding;

  public MyFragment(a) {}@Override
  public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);

  }

  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
      return inflater.inflate(R.layout.fragment_my,container,false);
  }

  @Override
  public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
      super.onViewCreated(view, savedInstanceState);
      FragmentMyBinding binding = FragmentMyBinding.bind(view);
      this.binding = binding;

      binding.textView.setText("It's fragments");
      binding.button.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View view) {
              Log.d("Fragment"."Click the button."); }}); }@Override
  public void onDestroy(a) {
      super.onDestroy();
      binding = null;
  }
Copy the code

Encapsulate the ViewBinding base class

Encapsulate base classes through reflective methods.

Java packages

  • The Activity of the base class
public class BaseActivity<T extends ViewBinding> extends AppCompatActivity {
  protected T viewBinding;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      ParameterizedType type = (ParameterizedType) getClass().getGenericSuperclass();
      Class cls = (Class) type.getActualTypeArguments()[0];
      try {
          Method inflate = cls.getDeclaredMethod("inflate", LayoutInflater.class);
          viewBinding = (T) inflate.invoke(null, getLayoutInflater());
          setContentView(viewBinding.getRoot());
      } catch(NoSuchMethodException | IllegalAccessException| InvocationTargetException e) { e.printStackTrace(); }}}/ / use

public class MainActivity extends BaseActivity<ActivityMainBinding> {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        viewBinding.button.setText("This is MainActivity ViewBinding");
        viewBinding.button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.d("MainView"."Click the button"); }}); }}Copy the code
  • Fragments base class
public class BaseFragment<T extends ViewBinding> extends Fragment {
    protected T viewBinding;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        ParameterizedType type = (ParameterizedType) getClass().getGenericSuperclass();
        Class cls = (Class) type.getActualTypeArguments()[0];
        try {
            Method inflate = cls.getDeclaredMethod("inflate", LayoutInflater.class, ViewGroup.class, boolean.class);
            viewBinding = (T) inflate.invoke(null, inflater, container, false);
        }  catch (NoSuchMethodException | IllegalAccessException| InvocationTargetException e) {
            e.printStackTrace();
        }
        returnviewBinding.getRoot(); }}/ / use
public class MainFragment extends BaseFragment<FragmentMainBinding>{
    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        viewBinding.button.setText("This is the MainFragment ViewBinding");
        viewBinding.button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.d("MainView"."Click the button"); }}); }}Copy the code

Kotlin encapsulation

  • The Activity to encapsulate
open class BaseActivity<T : ViewBinding> : AppCompatActivity() {
    protected lateinit var binding: T
    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        val type = javaClass.genericSuperclass as ParameterizedType
        val aClass = type.actualTypeArguments[0] as Class<*>
        val method = aClass.getDeclaredMethod("inflate", LayoutInflater::class.java)
        binding = method.invoke(null, layoutInflater) as T
        setContentView(binding.root)
    }
}

/ / use
class MainActivity : BaseActivity<ActivityMainBinding>() {
    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        binding.textView.text = "It's MainActivity"}}Copy the code
  • Fragments encapsulation
open class BaseFragment<T:ViewBinding> :Fragment() {lateinit var binding: T
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup? , savedInstanceState:Bundle?).: View? {
        val type = javaClass.genericSuperclass as ParameterizedType
        val aClass = type.actualTypeArguments[0] as Class<*>
        val method = aClass.getDeclaredMethod("inflate", LayoutInflater::class.java,ViewGroup::class.java,Boolean: :class.java)
        binding = method.invoke(null,layoutInflater,container,false) as T
        return binding.root
    }
}

/ / use
class FirstFragment : BaseFragment<FragmentFirstBinding>() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?). {
        super.onViewCreated(view, savedInstanceState)
        binding.textView.text = "It's FirstFragment"}}Copy the code

Encapsulate base classes without using reflective methods.

  • Kotlin encapsulates the Activity
abstract class BaseActivity<T : ViewBinding> : AppCompatActivity() {
    private lateinit var _binding: T
    protected val binding get() = _binding;

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        _binding = getViewBinding()
        setContentView(_binding.root)
    }

    protected abstract fun getViewBinding(a): T
}

/ / use
class MainActivity : BaseActivity<ActivityMainBinding>() {
    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        binding.textView.text = "It's MainActivity"
    }

    override fun getViewBinding(a) = ActivityMainBinding.inflate(layoutInflater)
}
Copy the code
  • Kotlin encapsulation fragments
abstract class BaseFragment<T : ViewBinding> : Fragment() {
    private lateinit var _binding: T
    protected val binding get() = _binding;
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup? , savedInstanceState:Bundle?).: View? {
        _binding = getViewBinding(inflater, container)
        return _binding.root
    }
    
    protected abstract fun getViewBinding(inflater: LayoutInflater, container: ViewGroup?).: T
}

/ / use
class FirstFragment : BaseFragment<FragmentFirstBinding>() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?). {
        super.onViewCreated(view, savedInstanceState)
        binding.textView.text = "It's FirstFragment"
    }

    override fun getViewBinding(
        inflater: LayoutInflater,
        container: ViewGroup?). = FragmentFirstBinding.inflate(inflater, container, false)}Copy the code

ViewBinding confusion

-keepclassmembers class * implements androidx.viewbinding.ViewBinding { public static ** inflate(...) ; public static ** bind(***); }Copy the code