android viewbinding_ViewBinding 实战,递进优雅的写波代码

稳住,今天是周六。

上周看到这篇文章,其实我还是蛮喜欢类似的文章的,喜欢这种循序渐进去重构代码的感觉。

另外一些新技术都缺少一些实战的文章,最近如果发现有不错的文章,也会上门求授权,争取推送。

不过看完后,总觉得 ViewBinding 和 DataBinding 能力非常类似,于是又找了一些和 DataBinding 的区别文章来看,恩...还是没有看到太本质的区别,后面可能会详细的对比一波,有比较了解的也欢迎留言,先看看这篇 ViewBinding 实战吧。

完全没了解过 View Binding,可以先看下面这篇介绍:

AS 3.6 Canary 中推出新技术 视图绑定 View Binding

另外 GSY 的新书出版了,明天会写篇文章感谢一下 GSY 一直以来的帮助与支持,顺便送 15 本新书给大家。

下面开始正文。


1 小试牛刀

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

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

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

66500ddd2ab5eff260c8c4a892e1e03e.png

小白:西哥,你这个问题也太简单了,虽然我才学Android不久,但是这个我还是知道的,我们都知道,Android 布局中,有个一个 标签,可以引用一个布局。

我们可以把这个复用的卡片写成一个单独的布局,然后在每个页面使用包含进来就好了呀!

于是二话没说,就是干,马上就开始写起了代码!

首先,抽出一个公共的布局叫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">
<RelativeLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"
    >
    <ImageViewandroid: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"
        />
    <TextViewandroid: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"
        />
    <TextViewandroid: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>

接着,在每一个使用该卡片设计的地方,使用 标签将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编程技术~"
    }
}

然后运行一下,效果如下:

bf2be6c22aefd59e83839b740e013349.png

然后,在其他需要的页面,如MyFragment2、MyFragment3,按照前面的步骤,引入布局,绑定数据,就好了。

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

我:嗯,小伙子不错不错,这样确实可以,布局文件确实复用了,但是你看看你的Fragment啊,比如我有4个Fragment,MyFragment1、MyFragment2,MyFragment3、MyFragment4,那其实我每个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,那么这些用到的页面都得改。

有没有办法能把这些样板代码也一起复用呢?

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

2 自定义View包装

不一会儿,小白大叫一声,我有办法了!

小白:我们可以借助自定义View来封装一下,我们把Fragment中的样板代码,抽到一个View 中去,然后提供一个API方法给外部来设置数据,每个使用的地方,将 引入的布局换成自定义的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来绑定数据。

然后使用的地方,先替换布局文件的 ,代码如下:

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



    <com.jay.jetpack.viewbinding.CardItemandroid:id="@+id/card_item"android:layout_width="match_parent"android:layout_height="wrap_content" />
LinearLayout>

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

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

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

47f75ff3649282b74792e2342827c439.png

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

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

但是不够完美,有一个小问题,你看这个自定义View类,里面同样是很多样板代码,如果我们又有另一个布局需要公用,那么我可能就需要再添加一个自定义View,把CardItem里面的代码拷贝过去,然后改吧改吧,改成对应的布局和View,当项目越来越大的时候,这种自定义View可能就越多。

但是他们的大部分代码其实是相同的。

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

小白又陷入了沉思!

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

我:你有听说过ViewBinding吗?

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

我:对,就是这个,我们可以用这个,加上Kotlin 的特性来做更完美的优化。

3 ViewBinding的救赎

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

viewBinding {
        enabled = true
 }

开启ViewBinding后,他会自动帮我的布局生成对应的类,比如我们上面的card_item.xml,会给我生成一个CardItemBinding.java类,my_fragment2.xml会生成

MyFragment2Binding.java,生成规则为:布局文件的名字去掉下划线 + Binding后缀,以驼峰的形式。

如下:

970b0ae2940171e54889b9e260bc5c7d.png

首先,把布局中的 换成 标签。代码如下:

<?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的扩展函数来优化!

4 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")

运行一下,效果如下:

a58d983e967f4418857b957f727f187e.png

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

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

并且,如果还有其他的复用布局,我们再添加一个扩展方法就好了,这就非常爽了!

小白:啥?等于说,利用Kotin + ViewBinding 可以替换自定义View了?妙啊!我也去写一个来试试!

推荐阅读:

“新技术” 又又又又来了?

“手把手”的性能优化文章来了!

面试官: 说一下你做过哪些性能优化?

b72ebfe048130e8916f9aae84b569de4.png

扫一扫 关注我的公众号

如果你想要跟大家分享你的文章,欢迎投稿~

┏(^0^)┛明天见!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值