啥?ViewBinding还能替换自定义View!

公司刚来了一个小伙伴,名叫小白,刚毕业的小伙子,这天茶余饭后,聊天聊起了代码复用的问题。确实,代码复用,能够说是咱们每个有理想的程序员的追求。因而想借机考考他。php

:说到代码复用,那!Android开发中,布局该如何复用呢?java

好比,像下面所示的这样一个卡片设计,不少页面都有用到,不可能每一个页面都去写一遍吧?如何能很好的实现复用呢?android

小白:西哥,你这个问题也太简单了,虽然我才学Android不久,可是这个我仍是知道的,咱们都知道,Android 布局中,有个一个<include /> 标签,能够饮用一个布局。咱们能够把这个复用的卡片写成一个单独的布局,而后在每一个页面使用<include />包含进来就行了呀!程序员

因而二话没说,就是干,立刻就开始写起了代码!web

首先,抽出一个公共的布局叫card_item.xml代码以下:面试

<?xml version="1.0" encoding="utf-8"?>
<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> 复制代码

接着,在每个使用该卡片设计的地方,使用<include /> 标签将card_item.xml布局引入进来。新建布局文件fragment.xml,代码以下:编程

<?xml version="1.0" encoding="utf-8"?>
<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> 复制代码

而后新建一个Fragment,名叫MyFragment,代码以下:微信

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"  desc.text = "扒最前沿科技动态,聊最TOP编程技术~"  } } 复制代码

而后运行一下,效果以下:app

而后,在其余须要的页面,如MyFragment2MyFragment3,按照前面的步骤,引入布局绑定数据,就行了。编辑器

很是简单,5分钟就写好了。小白略带微笑的说到。

:嗯,小伙子不错不错,这样确实能够,布局文件确实复用了,可是你看看你的Fragment啊,好比我有4个Fragment,MyFragment1MyFragment2,MyFragment3MyFragment4,那其实我每一个Fragment中的大部分代码都是相同的。

以下:

// 声明View
 private lateinit var avatar: ImageView  private lateinit var name: TextView  private lateinit var desc: TextView   // 绑定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)   // 绑定数据  avatar.setImageResource(R.mipmap.logo)  name.text = "技术最TOP"  desc.text = "扒最前沿科技动态,聊最TOP编程技术~" 复制代码

上面这些样板代码看起来很难受啊,每一个页面都要这样写,而且后期很差维护,好比,我CardView 里面新增长一个View,那么这些用到的页面都得改。有没有办法能把这些样板代码也一块儿复用呢?

小白有点迷惑,用手挠挠头,如有所思。

自定义View包装

不一下子,小白大叫一声,我有办法了!

小白:咱们能够借助自定义View来封装一下,咱们把Fragment中的样板代码,抽到一个View 中去,而后提供一个API方法给外部来设置数据,每一个使用的地方,将<include /> 引入的布局换成自定义的View, 而后在Fragment中调用API设置数据就能够了。

小白一脸自豪,说干就干,又开始重构前面的代码。

首先,将样板代码抽取一个View名叫CardItem,将声明View、绑定View、绑定数据的逻辑都放在这里,代码以下:

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  } } 复制代码

如上面代码所示,咱们提供了一个方法setData来绑定数据。

而后使用的地方,先替换布局文件的<include /> ,代码以下:

<?xml version="1.0" encoding="utf-8"?>
<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 复制代码

而后在Fragment中,调用setData绑定数据

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
 super.onViewCreated(view, savedInstanceState)  cardItem.setData(imageAvatarRes = R.mipmap.logo,name="技术最TOP",desc = "扒最前沿科技动态,聊最TOP编程技术~")  } 复制代码

运行一下代码,效果以下图所示:

才过10分钟,小白就把代码重构好了。

我: 不错不错,小伙子,这种方案很好,几乎大部分代码都公用了。

可是不够完美,有一个小问题,你看这个自定义View类,里面一样是不少样板代码,若是咱们又有另外一个布局须要公用,那么我可能就须要再添加一个自定义View,把CardItem里面的代码拷贝过去,而后改吧改吧,改为对应的布局和View,当项目愈来愈大的时候,这种自定义View可能就越多。可是他们的大部分代码实际上是相同的。

有没有办法可以解决这个问题,把这里面的样板代码也消除呢?

小白又陷入了沉思!

小白:这我真不知道了,还有什么办法?西哥给我讲讲呗。

:你有据说过ViewBinding吗?

小白:听过听过!就是Google 最新出的Jetpack组件嘛,江湖上声称干掉findViewById,取代黄油刀ButterKnife的大杀器。

:对,就是这个,咱们能够用这个,加上Kotlin 的特性来作更完美的优化。

ViewBingding的救赎

ViewBinding是Jetpack中新添加的组件,首先,在build.gradle中开启:

viewBinding {
        enabled = true
 }
复制代码

开启ViewBinding后,他会自动帮个人布局生成对应的类,好比咱们上面的card_item.xml,会给我生成一个CardItemBinding.java类,my_fragment2.xml会生成MyFragment2Binding.java,生成规则为:布局文件的名字去掉下划线 + Binding后缀,以驼峰的形式。以下:

首先,把布局中的<CardItem /> 换成 <include /> 标签。代码以下:

<?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>
复制代码

而后,咱们就能够不用findViewById()来绑定View了,能够直接使用xxBinding类访问View,Fragment代码以下:

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"  des.text = "扒最前沿科技动态,聊最TOP编程技术。"  }  } } 复制代码

这样,咱们就把一个几十行代码的自定义View类,变成了4代码,是否是就很是爽了。先别高兴,还有点问题,虽然咱们去掉了样板代码,可是仍是存在咱们最初的那个问题,那就是,若是复用的布局增长或者减小View的话,那么在每一个调用的地方都要更改。 这可不是咱们想要的,怎么解决这个问题呢?

还好有Kotlin,咱们能够用Kotlin的扩展函数来优化!

Kotlin扩展函数 + ViewBinding

咱们把绑定数据的那一段代码,抽一个扩展函数:

fun CardItemBinding.bind(imageResId: Int,nameStr: String, descStr: String){
 avatar.setImageResource(imageResId)  name.text = nameStr  des.text = descStr } 复制代码

咱们在CardItemBinding上扩展了一个bind方法。

如今咱们如何调用了?下面这样:

binding.topCard.bind(imageResId = R.mipmap.logo,
 nameStr = "技术最TOP Super",  descStr = "扒最前沿科技动态,聊最TOP编程技术。Super") 复制代码

运行一下,效果以下:

完美实现,咱们把自定义View,替换成了一个ViewBinding的扩展函数,代码从原来的33行,减小到了如今的4行。

后期维护也很方便,增长减小View,直接在扩展方法里面更改就好。

而且,若是还有其余的复用布局,咱们再添加一个扩展方法就行了,这就很是爽了!

小白:啥?等于说,利用Kotin + ViewBinding 能够替换自定义View了?妙啊!

我也去写一个来试试!

文章首发于公众号:「 技术最TOP 」,天天都有干货文章持续更新,能够微信搜索「 技术最TOP 」第一时间阅读,回复【思惟导图】【面试】【简历】有我准备一些Android进阶路线、面试指导和简历模板送给你

相关文章
相关标签/搜索