First, the official tutorial points the way: view binding
It will take about 5-20 minutes to read this article carefully.
You can also jump directly to 3.0 at the end of the article to see the final plan.
Directory
-
- 1 About ViewBinding
-
- The first point is that naming conforms to certain rules.
- Second point, inherited from ViewBinding
- 2 Inconveniences in normal use
- 3 Without further ado, let’s just look at the code 1.0
- 4 Without further ado, let’s just look at the code 2.0
- 5 Without further ado, let’s just look at the code 3.0
1 About ViewBinding
XXXBinding is automatically generated:
1. Naming conforms to certain rules
2. All XXXBindings inherit from the ViewBinding base class
First point, naming follows certain rules
This can be seen in the official documentation:
When view binding is enabled for a module, a binding class is generated for each XML layout file contained in the module. Each binding class contains a reference to the root view and all views with IDs. The name of the binding class is generated by converting the name of the XML file to camelCase and adding the word “Binding” to the end.
For example, if the Activity is named FirstTestActivity
, then the corresponding generated binding class is named ActivityTestFirstBinding
Second point, inherited from ViewBinding
This can be seen in the code:
2 Inconveniences in normal use
According to the official documentation, use it in Activity as follows:
private lateinit var binding: ResultProfileBinding override fun onCreate(savedInstanceState: Bundle) {<!-- --> super.onCreate(savedInstanceState) binding = ResultProfileBinding.inflate(layoutInflater) val view = binding.root setContentView(view) }
Note that a lateinit var
variable is defined here
This means we have to manually initialize before using
As in the official documentation, in the Activity#onCreate()
method, we need to manually call the inflate
method for initialization
Apparently some of this code is fixed template:
binding = ResultProfileBinding.inflate(layoutInflater) // Only the Binding class name of ResultProfileBinding will change val view = binding.root setContentView(view)
Change the corresponding template of YyyActivity
only to be different from YyyBinding
, but the others remain unchanged.
Similarly, let’s see what it looks like in Fragment
:
private var _binding: ResultProfileBinding? = null // This property is only valid between onCreateView and // onDestroyView. private val binding get() = _binding!! override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? {<!-- --> _binding = ResultProfileBinding.inflate(inflater, container, false) val view = binding.root return view } override fun onDestroyView() {<!-- --> super.onDestroyView() _binding = null }
It can be seen that the same is true for using viewBinding in Fragment
, and there is template code
By now you should know what I want to do: can I get rid of the template code?
The answer is yes, the practical plan I gave is: Reflection + Generics
3 Without further ado, let’s just look at the code 1.0
We can directly use the above Binding naming rules to do it
/** * Simplify the initialization of ViewBinding through generics + reflection, simplifying the complex and simplifying it. You don’t need to initialize it manually every time. You only need to pass in the type. * Inheritance rules: subclass suffix must end with Activity */ open class BaseActivity<BindingClass> : AppCompatActivity() {<!-- --> //Private Binding class, the type is the actual Binding type used private var innerBinding: BindingClass? = null //Binding class exposed to subclasses, returning the non-empty type of innerBinding is mainly to avoid use by subclasses //It needs to be added frequently!! (If you have a better solution, please tell me) val binding: BindingClass by lazy {<!-- --> innerBinding!! } override fun onCreate(savedInstanceState: Bundle?) {<!-- --> super.onCreate(savedInstanceState) //The following operations are basically the same val acName = javaClass.simpleName val name = acName.substring(0, acName.indexOf("Activity")) val bindingClass = classLoader.loadClass("com.example.test.databinding.Activity${<!-- -->name}Binding") //Finally forced conversion to the actual Binding type passed in as a generic innerBinding = bindingClass.getMethod("inflate", LayoutInflater::class.java) .invoke(null, layoutInflater) as BindingClass } } // Subclass class MainActivity : BaseActivity<ActivityMainBinding>() {<!-- --> override fun onCreate(savedInstanceState: Bundle?) {<!-- --> super.onCreate(savedInstanceState) //Associated layout (just use the binding of BaseActivity directly) setContentView(binding.root) } }
At this point, the subclass only needs to associate the layout setContentView(binding.root)
4 Without further ado, let’s just look at the code 2.0
In fact, careful friends will find that version 1.0 has limitations:
The defined XxxActivity must end with Activity, and the package it is in must be com.example.test.databinding
This is equivalent to dead code, which is not recommended and is not good.
So let’s continue to think of ways to solve this problem
We actually need to solve this problem now loadClass("com.example.test.databinding.Activity${name}Binding")
Note again that in the above 1.0 we used generics, and in the subclass we actually gave the Binding class name
class MainActivity : BaseActivity<ActivityMainBinding>()
Isn’t this the actual parameter name of the generic parameter?
Therefore, friends who are familiar with reflection should be able to imagine that our next step is to use reflection clazz.getGenericSuperclass().getActualTypeArguments()
to get the class name of the subclass’s generic actual parameter.
Without further ado, let’s look directly at the code:
//BaseActivity.kt open class BaseActivity<BindingClass> : AppCompatActivity() {<!-- --> //Private Binding class, the type is the actual Binding type used private var innerViewBinding: BindingClass? = null //Use lazy method to avoid initialization errors during creation. Because of the actual usage of the official template, binding needs to initialize LayoutInflater after onCreate. // Then here, through lazy method, subsequent subclasses no longer need to be initialized manually. protected val mViewBinding: BindingClass by lazy {<!-- --> innerViewBinding!! } override fun onCreate(savedInstanceState: Bundle?) {<!-- --> super.onCreate(savedInstanceState) // Get the binding class name and class of the corresponding view through reflection val actualGenericTypeName = GenericUtil.getActualGenericTypeName(this.javaClass) MyLogUtil.d("binding", "this.javaClass...: $actualGenericTypeName") //Class loader loads the class val bindingClass = classLoader.loadClass(actualGenericTypeName) //Normal initialization of viewBinding in an Activity: // binding = ResultProfileBinding.inflate(layoutInflater) //In Fragment: // _binding = ResultProfileBinding.inflate(inflater, container, false) innerViewBinding = bindingClass .getMethod("inflate", LayoutInflater::class.java) .invoke(null, layoutInflater) as BindingClass //The inflate method is a class method of ViewBinding, not an object method, so the obj parameter is null } }
// GenericUtil.java public class GenericUtil {<!-- --> public static String getActualGenericTypeName(@NonNull Class clazz) {<!-- --> String className; Type genericSuperclass = clazz.getGenericSuperclass(); ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass; try {<!-- --> Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); Class tClass = (Class) actualTypeArguments[0]; className = tClass.getName(); } catch (Exception e) {<!-- --> throw new IllegalArgumentException("Wrong ViewBinding Type"); } return className; } }
use:
//Subclass class MainActivity : BaseActivity<ActivityMainBinding>() {<!-- --> override fun onCreate(savedInstanceState: Bundle?) {<!-- --> super.onCreate(savedInstanceState) //Associated layout (just use the binding of BaseActivity directly) setContentView(binding.root) } }
5 Without further ado, let’s just look at the code 3.0
At this point, it’s basically solved
But some friends will also find that the line of code setContentView(binding.root)
in the subclass is also a fixed template code. Can this be simplified to the extreme?
The answer is of course yes
Direct operation:
The binding has been loaded in BaseActivity
. Can I just write setContentView(binding.root)
directly into BaseActivity
?
NO, in actual operation, I found that there is no need to compile, the compiler will prompt an error:
Why? Ask your friends to think about the dynamic loading process for themselves
Then you will find that this problem can continue to be completed through dynamic loading:
// BaseActivity.kt val invoke = bindingClass.getMethod("getRoot").invoke(mViewBinding) as View setContentView(invoke)
Complete code:
// BaseActivity.kt open class BaseActivity<BindingClass> : AppCompatActivity() {<!-- --> //Private Binding class, the type is the actual Binding type used private var innerViewBinding: BindingClass? = null protected val mViewBinding: BindingClass by lazy {<!-- --> innerViewBinding!! } override fun onCreate(savedInstanceState: Bundle?) {<!-- --> super.onCreate(savedInstanceState) // Get the binding class name and class of the corresponding view through reflection val actualGenericTypeName = GenericUtil.getActualGenericTypeName(this.javaClass) val bindingClass = classLoader.loadClass(actualGenericTypeName) innerViewBinding = bindingClass .getMethod("inflate", LayoutInflater::class.java) .invoke(null, layoutInflater) as BindingClass val invoke = bindingClass.getMethod("getRoot").invoke(mViewBinding) as View setContentView(invoke) } }
Use in subclasses:
class MainActivity : BaseActivity<ActivityMainBinding>() {<!-- --> override fun onCreate(savedInstanceState: Bundle?) {<!-- --> super.onCreate(savedInstanceState) // ...Complete other logic of the subclass... } }
The subclass does not need to do anything at all, it only needs to bind the generic parameters!
You’re done, it’s much easier to use in practice
The above is this practice. If it is helpful to you, please use your little hands to make a fortune, like and follow~
If you have other opinions, please feel free to communicate via private message or in the comment area~