Recently, I have fitfully changed the code for the interface part of the project from JAva to Kotlin, and if you use the Kotlin-Android-Extensions extension, one obvious benefit is that you no longer have to write findViewById() to instantiate your control objects. Just manipulate your ID in the layout file, which I find simpler and friendlier than Butterknife.

Activity

import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        textview.text="hello world"
    }
}

Copy the code

The kotlinx. Android. Synthetic. Main. Activity_main. * is kotlin – android – extensions plugin automatically generated. Now let’s try to explain how this works. Since Kotlin is also a JVM language and will be compiled to class bytecode just like Java these days, let’s go straight to decompile and look at the resulting Java files.

Select Decompile, and the code will parse as follows

public final class MainActivity extends AppCompatActivity {
   private HashMap _$_findViewCache;

   protected void onCreate(@Nullable Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      this.setContentView(2131296284);
      TextView var10000 = (TextView)this._$_findCachedViewById(id.textview);
      Intrinsics.checkExpressionValueIsNotNull(var10000, "textview");
      var10000.setText((CharSequence)"hello world");
   }

   public View _$_findCachedViewById(int var1) {
      if (this._$_findViewCache == null) {
         this._$_findViewCache = new HashMap();
      }

      View var2 = (View)this._$_findViewCache.get(var1);
      if (var2 == null) {
         var2 = this.findViewById(var1);
         this._$_findViewCache.put(var1, var2);
      }

      return var2;
   }

   public void _$_clearFindViewByIdCache() {
      if (this._$_findViewCache ! =null) {
         this._$_findViewCache.clear(); }}}Copy the code

You can clearly see that findViewById() is still called, but the call to retrieve the View object is findCachedViewById and create a HashMap to cache the View object. Avoid recalling findViewById() every time a View is called.

Fragment

Let’s look at the use of fragments:

import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import kotlinx.android.synthetic.main.fragment_blank.*


class BlankFragment : Fragment() {
    
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup? , savedInstanceState:Bundle?).: View? {
        
        return inflater.inflate(R.layout.fragment_blank, container, false)}override fun onViewCreated(view: View, savedInstanceState: Bundle?). {
        super.onViewCreated(view, savedInstanceState)
        textview_fra.text="hello world"}}Copy the code

The decompiled code is as follows

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

   @Nullable
   public View onCreateView(@NotNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
      Intrinsics.checkParameterIsNotNull(inflater, "inflater");
      return inflater.inflate(2131296285, container, false);
   }

   public void onViewCreated(@NotNull View view, @Nullable Bundle savedInstanceState) {
      Intrinsics.checkParameterIsNotNull(view, "view");
      super.onViewCreated(view, savedInstanceState);
      TextView var10000 = (TextView)this._$_findCachedViewById(id.textview_fra);
      Intrinsics.checkExpressionValueIsNotNull(var10000, "textview_fra");
      var10000.setText((CharSequence)"hello world");
   }

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

   public void _$_clearFindViewByIdCache() {
      if (this._$_findViewCache! = null) { this._$_findViewCache.clear(); / /}}$FF: synthetic method
   public void onDestroyView() {
      super.onDestroyView();
      this._$_clearFindViewByIdCache();
   }
}
Copy the code

You can see that the control is finally instantiated by calling getView().findViewById(). Take a look at the getView() source code

    @Nullable
    public View getView(a) {
        return this.mView;
    }
Copy the code

MView member variable assignment timing:

  void performCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        if (this.mChildFragmentManager ! =null) {
            this.mChildFragmentManager.noteStateNotSaved();
        }

        this.mPerformedCreateView = true;
        this.mViewLifecycleOwner = new LifecycleOwner() {
            public Lifecycle getLifecycle(a) {
                if (Fragment.this.mViewLifecycleRegistry == null) {
                    Fragment.this.mViewLifecycleRegistry = new LifecycleRegistry(Fragment.this.mViewLifecycleOwner);
                }

                return Fragment.this.mViewLifecycleRegistry; }};this.mViewLifecycleRegistry = null;
        this.mView = this.onCreateView(inflater, container, savedInstanceState);
        if (this.mView ! =null) {
            this.mViewLifecycleOwner.getLifecycle();
            this.mViewLifecycleOwnerLiveData.setValue(this.mViewLifecycleOwner);
        } else {
            if (this.mViewLifecycleRegistry ! =null) {
                throw new IllegalStateException("Called getViewLifecycleOwner() but onCreateView() returned null");
            }

            this.mViewLifecycleOwner = null; }}Copy the code

As you can see, mView is the return value of onCreateView(), so we can’t manipulate the View object in the same way as the control ID in onCreateView(), which would cause a null pointer exception. This is recommended in the onViewCreated() method.

Others (dynamic layout)

Other than activities and fragments, the most commonly used UI layouts are Adapters. The Kotlin-Android-Extensions also provide support for these types of dynamic layouts. Since this feature is implementable and is disabled by default, we need to manually enable it in build.gradle:

androidExtensions {
    experimental = true
}
Copy the code

Then recycler. Adapter is used as follows:

import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_recyclerview.*

class MyAdapter(val context: Context, val data: List<String>) :
    RecyclerView.Adapter<MyAdapter.ViewHolder>() {


    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(context).inflate(R.layout.item_recyclerview, parent, false)
        return ViewHolder(view)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.name_tv.text = data[position]
        holder.itemView.setOnClickListener {
           Toast.makeText(context,"Click number one$positionItem",Toast.LENGTH_SHORT).show()
        }
    }

    override fun getItemCount(a): Int {
        return data.size
    }

    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), LayoutContainer {

        override val containerView: View = itemView
    }
}
Copy the code

As you can see, our ViewHolder needs to implement one more interface, LayoutContainer, than the Activity and Fragment. Take a look at its source code:

/** * A base interface for all view holders supporting Android Extensions-style view access. */
public interface LayoutContainer {
    /** Returns the root holder view. */
    public val containerView: View?
}
Copy the code

There is only one object and we need to set this value to manually specify the root Holder View, which is the itemView of the ViewHolder. The getContainerView function is the same as the Fragment getView() function, but the Fragment contains a method that gets the root View of the layout. The Adapter needs to be implemented through the LayoutContainer interface.

   public final class ViewHolder extends android.support.v7.widget.RecyclerView.ViewHolder implements LayoutContainer {
      @NotNull
      private final View containerView;
      private HashMap _$_findViewCache;

      @NotNull
      public View getContainerView(a) {
         return this.containerView;
      }

      public ViewHolder(@NotNull View itemView) {
         Intrinsics.checkParameterIsNotNull(itemView, "itemView");
         super(itemView);
         this.containerView = itemView;
      }

      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.getContainerView();
            if (var10000 == null) {
               return null;
            }

            var2 = var10000.findViewById(var1);
            this._$_findViewCache.put(var1, var2);
         }

         return var2;
      }

      public void _$_clearFindViewByIdCache() {
         if (this._$_findViewCache ! =null) {
            this._$_findViewCache.clear(); }}}Copy the code