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
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
<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
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
<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
<?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