The company just came to a small partner, named Xiaobai, a young man who just graduated, this day after lunch, chatting about the problem of code reuse. Indeed, code reuse can be said to be the pursuit of every programmer with ideals. So I want to take this opportunity to test him.

Me: Speaking of code reuse, that! How do you reuse layouts in Android development?

For example, a card design like the one shown below can be used on many pages. It’s impossible to write every page, right? How to achieve good reuse?


Xiao Bai: West elder brother, you this problem is too simple, although I just learn Android soon, but this I still know, we all know, Android layout, there is a
tag, can drink a layout. We can write this reusable card as a separate layout and use
to include it on each page.

So without another word, is dry, immediately began to write up the code!

First, pull out a public layout called card_item.xml:


      
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="100dp"
 xmlns:app="http://schemas.android.com/apk/res-auto"  app:cardCornerRadius="5dp"  android:layout_margin="10dp"  app:cardElevation="2dp"> <RelativeLayout  android:layout_width="match_parent"  android:layout_height="match_parent"  >  <ImageView  android:id="@+id/avatar"  android:layout_width="80dp"  android:layout_height="90dp"  android:src="@mipmap/logo"  android:scaleType="centerCrop"  android:layout_centerVertical="true"  android:layout_marginLeft="15dp"  />  <TextView  android:id="@+id/name"  android:layout_width="wrap_content"  android:layout_height="wrap_content"  android:textColor="# 333"  android:textSize="18sp"  android:layout_toRightOf="@+id/avatar"  android:layout_marginLeft="5dp"  android:layout_marginTop="10dp"  />  <TextView  android:id="@+id/des"  android:layout_width="wrap_content"  android:layout_height="wrap_content"  android:textColor="# 999"  android:textSize="12sp"  android:layout_below="@+id/name"  android:layout_toRightOf="@+id/avatar"  android:layout_marginLeft="5dp"  android:layout_marginTop="10dp"  /> </RelativeLayout> </androidx.cardview.widget.CardView> Copy the code

Then, at each place where the card design is used, use the
tag to bring in the card_item.xml layout. Create a new layout file fragment.xml with the following code:


      
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
 android:orientation="vertical">
  <include layout="@layout/card_item" />  </LinearLayout> Copy the code

Create a new Fragment called MyFragment with the following code:

class MyFragment: Fragment() {
    private lateinit var avatar: ImageView
    private lateinit var name: TextView
    private lateinit var desc: TextView

 override fun onCreateView(  inflater: LayoutInflater. container: ViewGroup? . savedInstanceState: Bundle?  ): View? {  val view = inflater.inflate(R.layout.my_fragment,container,false)  avatar = view.findViewById(R.id.avatar)  name = view.findViewById(R.id.name)  desc = view.findViewById(R.id.des)  return view  }   override fun onViewCreated(view: View, savedInstanceState: Bundle?). {  super.onViewCreated(view, savedInstanceState)  avatar.setImageResource(R.mipmap.logo)  name.text = "TOP technology"  desc.text = "Dig the latest technology trends, talk about the TOP programming technology ~"  } } Copy the code

Then run it and it looks like this:


Then, on other required pages, such as MyFragment2, MyFragment3, follow the previous steps to introduce the layout, bind the data, and you’re done.

It was very simple. It was written in five minutes. Small white with a smile said.

I: Yes, it works. Layout files are reused, but if you look at your Fragment, say I have 4 fragments, MyFragment1, MyFragment2,MyFragment3, MyFragment4, that’s actually most of my code in each Fragment is the same.

As follows:

    / / declare the View
    private lateinit var avatar: ImageView
    private lateinit var name: TextView
    private lateinit var desc: TextView

 / / bind the View  val view = inflater.inflate(R.layout.my_fragment1,container,false)  avatar = view.findViewById(R.id.avatar)  name = view.findViewById(R.id.name)  desc = view.findViewById(R.id.des)   // Bind data  avatar.setImageResource(R.mipmap.logo)  name.text = "TOP technology"  desc.text = "Dig the latest technology trends, talk about the TOP programming technology ~" Copy the code

So all of this boilerplate code looks pretty bad, because I have to write every page like this, and it’s not easy to maintain later, so if I add a new View to my CardView, I’m going to have to change all the pages that I’m using. Is there a way to reuse this boilerplate code as well?

Xiaobai is a little confused, scratching his head with his hand, thoughtful.

Custom View wrapping

Not for a while, small white shout 1, I have a way!

Small white: We can encapsulate it with a custom View. We can take the boilerplate code in the Fragment, put it in a View, and then provide an API method to set the data externally, every place that we use it. Replace the layout introduced by
with a custom View, and then call the API in the Fragment to set the data.

White looked proud and proceeded to refactor the previous code.

First, we’ll extract a View named CardItem from our boilerplate code, and put the logic to declare the View, bind the View, and bind the data in there as follows:

class CardItem @JvmOverloads constructor(
    context: Context, attributes: AttributeSet? = null, defStyleAttr: Int = 0
) : FrameLayout(context, attributes, defStyleAttr) {

    private  var ivAvatar: ImageView
 private var tvName: TextView  private var tvDesc: TextView   init {  val view = LayoutInflater.from(context).inflate(R.layout.card_item,null.false)  ivAvatar = view.findViewById(R.id.avatar)  tvName = view.findViewById(R.id.name)  tvDesc = view.findViewById(R.id.des)   addView(view)  }   fun setData(imageAvatarRes: Int, name: String, desc: String) {  ivAvatar.setImageResource(imageAvatarRes)  tvName.text = name  tvDesc.text = desc  } } Copy the code

As shown in the code above, we provide a method called setData to bind the data.

Then replace the layout file
with the following code:


      
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
 android:orientation="vertical">
 <! -- <include layout="@layout/card_item" />-->   <com.jay.jetpack.viewbinding.CardItem  android:id="@+id/card_item"  android:layout_width="match_parent"  android:layout_height="wrap_content" /> </Line Copy the code

Then in the Fragment, call setData to bind the data:

 override fun onViewCreated(view: View, savedInstanceState: Bundle?). {
        super.onViewCreated(view, savedInstanceState)
        cardItem.setData(imageAvatarRes = R.mipmap.logo,name="TOP technology",desc = "Dig the latest technology trends, talk about the TOP programming technology ~")
    }
Copy the code

Run the code and it will look something like this:


After only 10 minutes, Xiaobai reconstituted the code.

Me: Not bad, young man. That’s a good plan. Almost all the code is public.

But it’s not perfect. There’s a little problem, if you look at this custom View class, it’s also full of boilerplate code, and if I have another layout that I want to share, I might add another custom View, copy the code that’s in the CardItem, and change it, Change to the corresponding layout and View, and as the project gets larger, there may be more of these custom views. But most of their code is the same.

Is there a way to fix this and get rid of boilerplate code?

Small white immersed in meditation again!

Small white: this I really don’t know, still have what method? Tell me about it, Seeger.

Me: Have you heard of ViewBinding?

Small white: listen to listen to! Google’s latest Jetpack component claims to kill findViewById and replace ButterKnife.

Me: Yeah, that’s it, and we can use that, plus Kotlin’s features, to do a better optimization.

ViewBingding redemption

ViewBinding is a new component added to Jetpack. First, enable it in build.gradle:

 viewBinding {
        enabled = true
 }
Copy the code

When ViewBinding is enabled, it automatically generates the corresponding class for my layout, such as card_item. XML, which generates a Carditembinding.java class for me, My_fragment2.xml generates myFragment2binding.java as follows: The layout file name is humped with an underscore + Binding suffix. As follows:


First, replace
in the layout with
tags. The code is as follows:

<?xml version="1.0" encoding="utf-8"? >
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <include android:id="@+id/topCard" layout="@layout/card_item"/>

</LinearLayout>
Copy the code

Instead of using findViewById () to bind the View, you can use xxBinding to access the View.

class MyFragment2: Fragment(R.layout.my_fragment2) {
    private lateinit var binding: MyFragment2Binding

    override fun onCreateView(  inflater: LayoutInflater. container: ViewGroup? . savedInstanceState: Bundle?  ): View? {  binding = MyFragment2Binding.inflate(inflater, container, false)  return binding.root  }   override fun onViewCreated(view: View, savedInstanceState: Bundle?). {  super.onViewCreated(view, savedInstanceState)  binding.topCard.apply {  avatar.setImageResource(R.mipmap.logo)  name.text="TOP technology"  des.text = "The latest technology trends, talk about the TOP programming technology."  }  } } Copy the code

So, we’ve turned a custom View class from a few dozen lines of code into four lines of code, which is pretty cool. Don’t be happy, there is still a problem, although we have removed the boilerplate code, but we still have our original problem, which is that if a reusable layout adds or subtracts views, it will have to change at every call. That’s not what we want. How do we solve this problem?

Good thing there’s Kotlin, we can use Kotlin’s extension function to optimize!

Kotlin extension function + ViewBinding

Let’s extract an extension function from the code that binds the data:

fun CardItemBinding.bind(imageResId: Int,nameStr: String, descStr: String){
        avatar.setImageResource(imageResId)
        name.text = nameStr
        des.text = descStr
}
Copy the code

We extended a bind method on CardItemBinding.

Now how do we call it? Here it is:

 binding.topCard.bind(imageResId = R.mipmap.logo,
            nameStr = "Technology TOP Super".            descStr = "Cutting edge, talking about the most advanced programming techniques. Super")
Copy the code

Run it and it looks like this:


Perfect implementation. We’ve replaced the custom View with a ViewBinding extension function, reducing the code from 33 lines to four.

Later maintenance is also very convenient, increase and decrease View, directly in the extension method changes.

And, if there are other reuse layouts, we could just add an extension method, and that would be great!

Small white: what? Kotin + ViewBinding can replace custom views. Ok? Wonderful!

I’ll try writing one too!

The article was first published on the public account: “Technology TOP”, there are dry articles updated every day, you can search wechat “technology TOP” first time to read, reply [mind map] [interview] [resume] yes, I prepare some Android advanced route, interview guidance and resume template for you