Android Study: findViewById's evolution

Android Study: findViewById's evolution

Preface

Today we will talk about findViewById, which is commonly used in projects. This thing can be simply understood as: initializing controls, instantiating controls, and facilitating other operations. Generally speaking, we usually write like this:

  1. private void initView() {
  2. TextView tvTest = (TextView) findViewById(R.id.id_test);
  3. }

The example above is very simple, just initialize a TextView, but in actual projects, each Activity, Fragment or Adapter has n controls, each of which requires us to instantiate the control before we can operate it. FindViewById again and again is so annoying~!

Is there a good way? Of course there are many ways, but we have to find the one that suits our project. The following will give you examples one by one~

Simplify findViewById by annotation

In the past few years, Xutils has been very popular. There are many reasons for its popularity. I am more optimistic about the ease of use of Xutils. At least it encapsulates many commonly used tools for us, such as the commonly used disgusting image processing, which Xutils has good support for. Similarly, Xutils also supports annotations to simplify findViewById. A simple example is as follows:

  1. // The xUtils view annotation requires that an id must be provided to prevent code obfuscation. @ViewInject(R.id.id_test)
  2. TextView tvTest;

The more famous ButterKnife, I also studied it before, the relevant article address is as follows:

《Play with ButterKnife in one article to make the code more concise》

A simple example is as follows:

  1. @BindView(R.id.id_test)TextView tvTest;

The above are just two examples, at least the ones I have used. Of course, there are many other useful methods that support annotations. Welcome to communicate and learn together~

Personal encapsulation findViewById

I was searching online just now and suddenly saw that a friend had packaged one himself after being inspired by his teacher. I thought it was good and decided to try it out to see if it works.

After a simple modification, it passed the test and felt pretty good. Here is the source code for everyone:

  1. /**
  2. * Created by HLQ on 2017/6/25 0025.
  3. */
  4. public class FindView {
  5. private static Activity activity; // Use the hungry man style in the singleton pattern
  6. private static final FindView findView = new FindView();
  7. /**
  8. * Get the Activity instance
  9. *
  10. * @param activity
  11. */
  12. private static void setActivity(Activity activity) {
  13. FindView.activity = activity;
  14. } /**
  15. * Initialize FindView
  16. *
  17. * @param activities
  18. * @return  
  19. */
  20. public   static FindView with (Activity activities) {
  21. setActivity(activities);
  22. return findView;
  23. } /**
  24. * Get the View instance based on the Id
  25. *
  26. * @param id
  27. * @param <T>
  28. * @return  
  29. */
  30. public <T extends View > T getView( int id) {
  31. View   view = activity.findViewById(id);
  32. return (T) view ;
  33. } /**
  34. * Set the TextView content
  35. *
  36. * @param id
  37. * @param content
  38. * @return  
  39. */
  40. public FindView setTextContent( int id, String content) {
  41. TextView textView = getView(id);
  42. textView.setText(content);
  43. return this;
  44. } /**
  45. * Set ImageView resource
  46. *
  47. * @param id
  48. * @param imgResource
  49. * @return  
  50. */
  51. public FindView setImageResource( int id, int imgResource) {
  52. ImageView iv = getView(id);
  53. iv.setImageResource(imgResource);
  54. return this;
  55. }
  56.  
  57. }

So how do we use it? Simply put, there are two ways to call:

  1. Through chain calls, you can directly call the encapsulated setText or setImgResource to assign values ​​directly;
  2. Get the control instance by chaining getView calls, and then perform corresponding operations.

Another point is that you can expand the encapsulation class according to the project.

Here are two specific ways to call and run the results:

Method 1 calling method:

  1. FindView. with (this).setTextContent(R.id.id_test, "Hello" ).setImageResource(R.id.iv_test,R.mipmap.ic_launcher);

Running results:

Method 2 calling method:

  1. TextView textView= FindView. with (this).getView(R.id.id_test);
  2. textView.setText( "Hello" );

Running results:

Simplifying findViewById with generics

Generally speaking, we will define a generic method to obtain View instances in BaseActivity, as follows:

  1. public final <E extends View > E getView( int id) {
  2. try {
  3. return (E) findViewById(id);
  4. } catch (ClassCastException ex) {
  5. Log.e( "HLQ_Struggle" , "Could not cast View to concrete class." + ex);
  6. throw ex;
  7. }
  8. }

The call is very simple, as follows:

  1. TextView textView = getView(R.id.id_test);
  2. textView.setText( "What are you doing" );

This result is needless to say, right?

Extract generic methods to public classes

This is the same as the previous method, but the previous method may have some flaws. What should I do if I call it in a non-Activtiy? Do I have to copy it once in each Base? I personally don't think it is very satisfactory.

  1. /**
  2. * Get the control instance
  3. * @param view  
  4. * @param viewId
  5. * @param <T>
  6. * @return   View  
  7. */
  8. public   static <T extends View > T viewById( View   view , int viewId) {
  9. return (T) view .findViewById(viewId);
  10. }

In this way, the call is a bit disgusting, as follows:

The calling method in Activitiy is as follows:

  1. TextView textView = UIHelper.viewById(selfActivity.getWindow().getDecorView(), R.id.id_test);

The call in Fragment is as follows:

  1. TextView textView= UIHelper.viewById(getActivity().getWindow().getDecorView(), R.id.id_test);

If you like to copy back and forth, this may be a solution.

Google's DataBinding

I just learned about this a few days ago and never paid attention to it before. But after a brief understanding, I found that it is really 666. I guess the reflection or annotation method has very 666 performance.

The following is a comparison of the current 6 statements:

DataBinding completely surpasses findViewById! findViewById is essentially a traversal search of the view tree. Each time a control is called, it will be searched once. Although it has O(n) performance, multiple calls will become O(n)xm. But DataBinding is different. It finds all controls through a traversal and assigns them to the corresponding variables. It does not rely on findViewById at all. In any case, the complexity is O(n). The same is true for code generation, but the data binding framework provides more functions, improves work efficiency, and writes safer and more reliable code.

So, what reason do we have to refuse?

Just understand it briefly, otherwise you will be confused.

Introduction to DataBinding

DataBinding is a framework introduced at the IO Conference in 2015. Its literal meaning is data binding. In general development, an Activity needs to implement both network requests and interface rendering/user interaction. If the functions of a page are more complex, it will be more difficult to maintain the project later. Therefore, the introduction of this framework is conducive to simplifying functional modules and trying to separate the interface rendering/user interaction functions into separate modules.

Feel the charm of DataBinding

Nothing is more practical, convenient and quick than coding directly. Let's start DataBinding Study through a simple example.

1.Configure DataBinding in build

  1. dataBinding {
  2. enabled = true  
  3. }

2. Write our layout file. Here we should remember the following points:

  • The layout file root is wrapped with < layout ></layout >;
  • There can only be one node under < layout >, which is the same as ScrollView

Based on the above two points, write and implement our layout file:

  1. <?xml version= "1.0" encoding= "utf-8" ?>
  2. <layout xmlns:android= "http://schemas.android.com/apk/res/android" >
  3. <LinearLayout
  4. android:layout_width= "match_parent"  
  5. android:layout_height= "match_parent"  
  6. android:orientation= "vertical" >
  7.  
  8. <TextView
  9. android:id= "@+id/id_test"  
  10. android:layout_width= "wrap_content"  
  11. android:layout_height= "wrap_content" />
  12.  
  13. <ImageView
  14. android:id= "@+id/iv_test"  
  15. android:layout_width= "wrap_content"  
  16. android:layout_height= "wrap_content" />
  17. </LinearLayout></layout>

After the layout file is ok, we will implement our Activity. How to call it? Look at you~

3. Call in Activity

Build the project and then call it as follows:

  1. ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
  2. binding.idTest.setText( "Hi DataBinding~" );
  3. binding.idTest.setOnClickListener(new View .OnClickListener() {
  4. @Override
  5. public void onClick( View   view ) {
  6. Toast.makeText(MainActivity.this, "Respond to click events~" , Toast.LENGTH_SHORT).show();
  7. }
  8. });

Regarding the above content, we can also write it like this. Suppose, we want to display the student's name, then we will proceed in three steps.

Step by step: define a simple entity class

  1. package cn.hlq.test;/**
  2. * Created by HLQ on 2017/6/26 0026.
  3. */ public class Student {
  4. public Student(String stuName) {
  5. this.stuName = stuName;
  6. } private String stuName;
  7. public String getStuName() {
  8. return stuName;
  9. } public void setStuName(String stuName) {
  10. this.stuName = stuName;
  11. }
  12. }

Step two, modify the layout file:

  1. <?xml version= "1.0" encoding= "utf-8" ?>
  2. <layout xmlns:android= "http://schemas.android.com/apk/res/android" >
  3. <data>
  4. <variable
  5. name = "stu"  
  6. type= "cn.hlq.test.Student" />
  7. </data>
  8. <LinearLayout
  9. android:layout_width= "match_parent"  
  10. android:layout_height= "match_parent"  
  11. android:orientation= "vertical" >
  12.  
  13. <TextView
  14. android:layout_width= "wrap_content"  
  15. android:layout_height= "wrap_content"  
  16. android:text= "@{stu.stuName}" />
  17.  
  18. <ImageView
  19. android:id= "@+id/iv_test"  
  20. android:layout_width= "wrap_content"  
  21. android:layout_height= "wrap_content" />
  22. </LinearLayout></layout>

Here is a brief explanation for everyone:

  • < data >< /data >: Bind the data source, which is your entity class
  • < variable >: basic parameter configuration
    • name: Aliases are convenient for assigning values ​​directly in controls
    • type: points to the entity class address

Three steps, how to play Activity?

  1. ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
  2. binding.setStu(new Student( "He Liquan" ));

Look at the running results. Chairman Mao once said: Practice is the only criterion for testing truth.

The above are two simple examples of implementation methods. You need to get other usage skills yourself. Here is the official API address:

https://developer.android.google.cn/reference/android/databinding/package-summary.html

Here, I will explain the above Activity calling content to you.

After building, a file with layout name + Binding will be generated for us. Our main operation is to be implemented through it. Let’s see how to operate in the related calling class.

First of all, you should know that DataBinding is a framework that implements data and UI binding based on the MVVM concept. So what is MVVM? Here is a brief understanding:

MVVM is the abbreviation of Model-View-ViewModel, which can also be simply understood as an upgrade of MVC, but I have never used it, so I don’t know what advantages it has. I guess it is also about decoupling and scalability.

Based on the previously mentioned DataBinding, all controls are found through a traversal and then assigned to the corresponding variables. Let's take a look at how DataBindingUtil operates.

1. Clicking in, we can clearly see that it calls a generic setContentView method, which is simply translated.

  1. /**
  2. * Sets the Activity's content view to the given layout and returns the associated bindings.
  3. * The given layout resource cannot contain a merged layout.
  4. *
  5. * @param activity Activity view should change the content of the activity.
  6. * @param layoutId The resource ID of the layout is imported, bound, and set as the content of the Activity.
  7.  
  8. * @return The bound view associated with the inflated content.
  9. */
  10. public   static <T extends ViewDataBinding> T setContentView(Activity activity, int layoutId) { return setContentView(activity, layoutId, sDefaultComponent);
  11. }

2. Continue reading, by setting the layout, get the View instance and find the content to be bound, and then return it through the bindToAddedViews method.

  1. /**
  2. * Set the Activity's content view   to the given layout and   return the associated binding.
  3. * The given layout resource must not be a merge layout.
  4. *
  5. * @param bindingComponent The DataBindingComponent to use in data binding.
  6. * @param activity The Activity whose content View should change.
  7. * @param layoutId The resource ID of the layout to be inflated, bound, and   set   as the
  8. * Activity's content.
  9. * @ return The binding associated with the inflated content view .
  10. */
  11. public   static <T extends ViewDataBinding> T setContentView(Activity activity, int layoutId,
  12. DataBindingComponent bindingComponent) { // Set ContentView
  13. activity.setContentView(layoutId); // Get the View instance
  14. View decorView = activity.getWindow().getDecorView(); // Find content
  15. ViewGroup contentView = (ViewGroup) decorView.findViewById(android.R.id.content); // Return the View added after binding  
  16. return bindToAddedViews(bindingComponent, contentView, 0, layoutId);
  17. }

3. Finally, we will find that he binds and adds View returns by checking the number of nodes

  1. // The method name is clear and binding is added to the View  
  2. private static <T extends ViewDataBinding> T bindToAddedViews(DataBindingComponent component,
  3. ViewGroup parent, int startChildren, int layoutId) { // Get the number of child nodes under parent
  4. final int endChildren = parent.getChildCount(); // Check the number of nodes under it
  5. final int childrenAdded = endChildren - startChildren; if (childrenAdded == 1) { // If there is only one node, get the instance and add the binding and return it
  6. final View childView = parent.getChildAt(endChildren - 1); return bind(component, childView, layoutId);
  7. } else { // If there is more than one node, you need to loop through and add
  8. final View [] children = new View [childrenAdded]; for ( int i = 0; i < childrenAdded; i++) {
  9. children[i] = parent.getChildAt(i + startChildren);
  10. } return bind(component, children, layoutId);
  11. }
  12. }

4. I wonder if you have noticed that DataBindingUtil has a static constant:

  1. private static DataBinderMapper sMapper = new DataBinderMapper();

Judging from the name, it should be a Map type with key value. There is no doubt about it. Click to continue taking a look.

  1. package android.databinding;import cn.hlq.test.BR;class DataBinderMapper { final static   int TARGET_MIN_SDK = 24; public DataBinderMapper() {
  2. } public android.databinding.ViewDataBinding getDataBinder(android.databinding.DataBindingComponent bindingComponent, android. view . View   view , int layoutId) { switch(layoutId) { case cn.hlq.test.R.layout.activity_main: return cn.hlq.test.databinding.ActivityMainBinding.bind( view , bindingComponent);
  3. } return   null ;
  4. }
  5. android.databinding.ViewDataBinding getDataBinder(android.databinding.DataBindingComponent bindingComponent, android. view . View [] views, int layoutId) { switch(layoutId) {
  6. } return   null ;
  7. } int getLayoutId(String tag) { if (tag == null ) { return 0;
  8. } final int code = tag.hashCode(); switch(code) { case 423753077: { if(tag.equals( "layout/activity_main_0" )) { return cn.hlq.test.R.layout.activity_main;
  9. } break;
  10. }
  11. } return 0;
  12. } String convertBrIdToString( int id) { if (id < 0 || id >= InnerBrLookup.sKeys.length) { return   null ;
  13. } return InnerBrLookup.sKeys[id];
  14. } private static class InnerBrLookup { static String[] sKeys = new String[]{ "_all" };
  15. }
  16. }

5. It’s a mess, but have you noticed anything here?

  1. switch(layoutId) { case cn.hlq.test.R.layout.activity_main: return cn.hlq.test.databinding.ActivityMainBinding.bind( view , bindingComponent);
  2.  
  3. }

What a familiar content! Isn't this our layout? By determining which layout it is, we can bind the corresponding source.

6. Continue to click in, we will find that it will first go to the view to verify whether the tag of the current layout has been added. If it has been added, it will directly return to the ActivityMainBinding class generated for us.

  1. public   static ActivityMainBinding bind(android. view . View   view , android.databinding.DataBindingComponent bindingComponent) { if (! "layout/activity_main_0" .equals( view .getTag())) { throw new RuntimeException( "view tag isn't correct on view:" + view .getTag());
  2. } return new ActivityMainBinding(bindingComponent, view );
  3. }

7. Check the ActivityMainBinding construction method generated for us

  1. public ActivityMainBinding(android.databinding.DataBindingComponent bindingComponent, View root) {
  2. super(bindingComponent, root, 0); // Get the bound View instance of return
  3. final Object[] bindings = mapBindings(bindingComponent, root, 3, sIncludes, sViewsWithIds);
  4. // No need to say this
  5. this.idTest = (android.widget.TextView) bindings[1];
  6. this.ivTest = (android.widget.ImageView) bindings[2];
  7. this.mboundView0 = (android.widget.LinearLayout) bindings[0];
  8. this.mboundView0.setTag( null );
  9. setRootTag(root); //Set related monitoring You can also understand request related monitoring
  10. invalidateAll();
  11. }

This is a simple analysis. If there are any errors, please point them out. Let's make progress together.

Conclusion

get skills apply skills find problems solve problems record problems

The road to becoming a god is full of holes, but it is not enough to fill them up

Come on everyone

<<:  Building a simple logistic regression model from scratch using TensorFlow

>>:  The second issue of the Aiti Tribe live broadcast class: How to make good use of HTML5 in mobile Internet products?

Recommend

7 essential elements for virtual reality to succeed

According to foreign media reports, virtual reali...

HTML 5 has won out – but for how long?

[51CTO.com Quick Translation] We have compiled va...

This article is suitable for forwarding to the "loving family group"

This era does not only belong to young people. In...

Nature has a set of "lazy" rules? You heard it right!

1. Introduction: Nature’s mysterious “lazy” law H...

Baidu bidding promotion, how to optimize keyword quality?

In order to optimize the quality of keywords, you...

How much does it cost to develop the Datong Jewelry Mini Program?

What is the price for developing Datong Jewelry M...

What fields are good for short videos? How to do it?

If you want to shoot short videos, choosing a goo...