/   今日科技快讯   /

近日,有网友在社交平台展示使用筋膜枪抢茅台的操作。对此天猫超市官方作出回应,表示此方法不可靠,并存在身体受伤的可能,希望广大网友理性购物。

/   作者简介   /

明天就是周六啦,祝大家都能有一个愉快的周末!

本篇文章来自DylanCai的投稿,分享了他对ViewBinding的封装,相信会对大家有所帮助!同时也感谢作者贡献的精彩文章!

DylanCai的博客地址:

https://juejin.cn/user/4195392100243000/posts

/   前言   /

之前看到官方公众号发文章说准备弃用 Kotlin Extensions Gradle 插件了。可能有些人不知道 Kotlin Extensions 插件是什么,就是用 Kotlin 写 Android 有个很爽的功能是,可以直接用布局里的 id 拿到控件对象。或许一些人经常这么写,但不知道是用一个插件实现的。要在 build.gradle 里配置了下面的代码才会生效,之前创建项目时会自动带上,现在最新版的模板已移除。

apply plugin: 'kotlin-android-extensions'

用 id 获取布局里的控件对象该插件的一个叫 Kotlin synthetic 的功能。貌似挺好的呀,不然写一个控件就要声明成 laterinit var 再调用 findViewById 才能拿到控件对象,写起来很繁琐。

这么方便的功能官方为什么要弃用呢?详细内容的可以看这篇文章《Kotlin Android Extensions 的未来计划》(https://mp.weixin.qq.com/s/pa1YOFA1snTMYhrjnWqIgg),官方提到了以下几点:

  • 污染全局命名空间。

  • 不能暴露可空性信息。

  • 仅支持 Kotlin 代码。

官方的建议是用 ViewBinding 来代替 Kotlin synthetic 。那么相对的,ViewBinding 会有以下优势:

  • 不污染命名空间。这个在我放弃 Kotlin synthetics 用 ViewBinding 时很有感触,终于不用在类文件里看到小写下划线命名的对象了,终于都统一成驼峰命名,强迫症患者表示这波很舒服。

  • 可以减少获取控件的空指针异常。这是 Kotlin synthetics、ButterKnife、findViewById 都存在的问题,大家应该多多少少都有遇到过。而用 ViewBinding 的话,在布局上有什么控件才能获取什么控件,这就不会出错。

  • 支持 Java 代码。还在用 Java 的朋友可以考虑放弃 ButterKnife 了。

  • 还有一点官方没有提到,就是用了 ViewBinding 能够很方便地使用 DataBinding。假如现在还在用 MVP,在未来想用 Jetpack MVVM 时就很容易了。

不过我之前了解过 ViewBinding,使用起来还是有点繁琐,所以那时没有改用 ViewBinding。现在官方表示一年后要弃用 Kotlin synthetic,不能用 id 获取控件了,所以现在还是慢慢用起来吧。我花了些时间对 ViewBinding 进行封装后,觉得可以用来代替 Kotlin synthetic 或者 ButterKnife,用 Kotlin 或者 Java 的朋友赶紧来试试吧。

下面来讲一下 ViewBinding 怎么使用和个人的封装建议。

/   基础用法   /

首先要在 module 的 build.gradle 文件配置开启 ViewBinding:

android {...viewBinding {enabled = true}
}

这样该模块下每个 XML 文件都生成一个对应的绑定类,每个绑定类会包含根视图以及具有 ID 的所有视图的引用。绑定类的命名是:将 XML 文件的名称转换为驼峰命名,并在末尾添加 “Binding” 。

比如现在有 activity_main.xml 文件:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><TextViewandroid:id="@+id/tv_hello_world"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Hello World!"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent" /></androidx.constraintlayout.widget.ConstraintLayout>

这会生成一个叫 ActivityMainBinding 的绑定类。该类的对象可以通过 getRoot() 方法获得根布局,并且可以获得一个叫 tvHelloWorld 的 TextView 对象。

如果不想生成某个布局的绑定类,可以在根视图添加 tools:viewBindingIgnore="true" 属性。

那这个绑定类的对象怎么实例化呢?该类会生成相关的 inflate 静态方法,调用该方法即可获得绑定对象。

class MainActivity : AppCompatActivity() {private lateinit var binding: ActivityMainBindingoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)binding = ActivityMainBinding.inflate(layoutInflater)setContentView(binding.root)binding.tvHelloWorld.text = "Hello Android!"}
}

在 Fragment 使用有点不同,由于 Fragment 的存在时间比其视图长,需要在 onDestroyView() 方法中清除对绑定类实例的所有引用,所以写起来会有点麻烦。

class HomeFragment : Fragment() {private var _binding: HomeFragmentBinding? = nullprivate val binding get() = _binding!!override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {_binding = ResultProfileBinding.inflate(inflater, container, false)return binding.root}override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)binding.tvHelloWorld.text = "Hello Android!"}override fun onDestroyView() {super.onDestroyView()_binding = null}
}

还有在 Adapter 的使用,因为布局不是只创建一次,而是每有一项数据就会创建,不能像上面那样在 Adapter 里写一个 binding 全局变量,这样 binding 只会得到最后一次创建的视图。所以 binding 对象应该是给 ViewHolder 持有。

class TextAdapter(private val list: List<String>
) : RecyclerView.Adapter<TextAdapter.TextViewHolder>() {override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TextViewHolder {val binding = ItemTextBinding.inflate(LayoutInflater.from(parent.context), parent, false)return TextViewHolder(binding)}override fun onBindViewHolder(holder: TextViewHolder, position: Int) {val content = list[position]holder.binding.tvContent.text = content}override fun getItemCount() = list.sizeclass TextViewHolder(val binding : ItemTextBinding) : RecyclerView.ViewHolder(binding.root)
}

常见的情况就讲完了,总结一下 ViewBinding 的用法是,获取绑定对象,然后用 getRoot() 方法拿到根视图来替代使用到布局的地方。后面就可以通过绑定对象获取布局上的控件对象。

一些使用 Java 的朋友可能会看不太懂上面的代码。这木有关系,因为不推荐直接用,模板代码用 Java 写起来更长。把上面文字看了,代码理解个大概,能比较清楚 ViewBinding 的用法就行了,接下来就是讲怎么封装来使用比较好。

/   封装建议   /

用惯了 Kotlin synthetic 用 id 获取控件,再看 ViewBinding 的用法多少会觉得有点繁琐,所以需要封装一下了,毕竟 ViewBinding 能减少 id 写错或类型写错导致的异常,而且前者快弃用了。个人想到了两种封装思路。

不依托于基类

类似在 Kotlin 使用 ViewModel 的用法,做到声明了对象即可使用,不用管是怎么创建的,不用考虑什么时候要清除实例,不用每次去写 inflate 的模板代码。这种用法的好处是想用就用,无需继承什么基类,泛用性更强,移植代码更加容易。会用到一些 Kotlin 的特性,不适用于 Java。Java 的推荐用法还在后面。

先来分析一下,首先肯定要调用 inflate() 方法,不然怎么实例化 binding 对象。但是我们可以做到使用前自动 inflate(),无需手动调用。这就用到延时委托来实现,在 Fragment 因为要清除实例后面另说。然后就是 inflate() 方法需要传 layoutInflater,而 Activity 、Dialog 都有提供对应 get 方法,所以就变成获取 Activity 、Dialog 对象,可以传参,但是更推荐写成拓展函数传进来。剩下一个问题,怎么调用 inflate() 方法,方法名和参数固定,可以用反射。但我们仍要一个 Class 对象,这可以通过内敛方法来获取泛型的 Class 对象。

上述的是封装思路,需要了解一些 Kotlin 的用法,有兴趣的自己去研究一下,涉及的知识点较多就不过多展开了。以下是封装好的代码:

inline fun <reified VB : ViewBinding> Activity.inflate() = lazy {inflateBinding<VB>(layoutInflater).apply { setContentView(root) }
}inline fun <reified VB : ViewBinding> Dialog.inflate() = lazy {inflateBinding<VB>(layoutInflater).apply { setContentView(root) }
}@Suppress("UNCHECKED_CAST")
inline fun <reified VB : ViewBinding> inflateBinding(layoutInflater: LayoutInflater) =VB::class.java.getMethod("inflate", LayoutInflater::class.java).invoke(null, layoutInflater) as VB

看不懂的没关系,知道怎么用就行。下面是 Activity 的使用示例,省去了 inflate() 和 setContentView() 的代码,在 Dialog 使用是类似的。

class MainActivity : AppCompatActivity() {private val binding: ActivityMainBinding by inflate()override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)binding.tvHelloWorld.text = "Hello Android!"}
}

而 Fragment 的封装就不一样了,首先 inflate() 方法还要传 parent 对象就不好处理,可以换个思路,我们用另一个生成的方法 bind(),只需传个 View,在 Fragment 很好拿。另外还需要释放 binding 对象,不能用延时委托改用属性委托。下面是封装的代码:

inline fun <reified VB : ViewBinding> Fragment.bindView() =FragmentBindingDelegate(VB::class.java)class FragmentBindingDelegate<VB : ViewBinding>(private val clazz: Class<VB>
) : ReadOnlyProperty<Fragment, VB> {private var isInitialized = falseprivate var _binding: VB? = nullprivate val binding: VB get() = _binding!!override fun getValue(thisRef: Fragment, property: KProperty<*>): VB {if (!isInitialized) {thisRef.viewLifecycleOwner.lifecycle.addObserver(object : LifecycleObserver {@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)fun onDestroyView() {_binding = null}})_binding = clazz.getMethod("bind", View::class.java).invoke(null, thisRef.requireView()) as VBisInitialized = true}return binding}
}

使用起来就体现出封装的优势了,不用特地写个 _binding 来清除实例对象,不用重写 onDestoryView() 方法。

class HomeFragment : Fragment(R.layout.fragment_home) {private val binding: FragmentHomeBinding by bindView()override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)binding.tvHelloWorld.text = "Hello Android!"}
}

构造函数里的布局记得别漏了,因为需要用布局创建出 View ,我们才能调用 bind() 方法。

还有列表的封装,前面说了 binding 对象是给 ViewHolder 持有,所以我们写一个 BindingViewHolder 来接收 binding。

class BindingViewHolder<VB : ViewBinding>(val binding: VB) : RecyclerView.ViewHolder(binding.root)

当然这还不够,因为需要个 binding 对象,同样要用到反射进行实例化。我们得到 binding 对象后可以顺便把 BindingViewHolder 对象创建了,所以直接封装一个创建的方法。

inline fun <reified T : ViewBinding> newBindingViewHolder(parent: ViewGroup): BindingViewHolder<T> {val method = T::class.java.getMethod("inflate", LayoutInflater::class.java, ViewGroup::class.java, Boolean::class.java)val binding = method.invoke(null, LayoutInflater.from(parent.context), parent, false) as Treturn BindingViewHolder(binding)
}

怎么用呢?在 onCreateViewHolder 调用封装的方法就创建了 BindingViewHolder 对象,然后在 onBindViewHolder 方法通过 holder 持有的 binding 就能拿到得到布局里控件了。

class TextAdapter(private val list: List<String>
) : RecyclerView.Adapter<BindingViewHolder<ItemTextBinding>>() {override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =newBindingViewHolder<ItemTextBinding>(parent)override fun onBindViewHolder(holder: BindingViewHolder<ItemTextBinding>, position: Int) {val content = list[position]holder.binding.tvContent.text = content}override fun getItemCount() = list.size
}

以上的封装简化了绑定类固定的 inflate 模板代码和 Fragment 清除实例对象的代码,在普通的 Activity、Fragment、Dialog、Adapter 都能使用,非常灵活。接下来讲另外一种封装思路。

依托于基类

主要是把 binding 对象封装在基类里替换掉布局,这样可以进一步减少声明 binding 对象的代码。还有前面的用法在某些基类使用时可能会存在 setContentView() 的调用时机问题,因为用到 binding 才会实例化和设置根布局。也许还没设置根视图,基类就去找控件,遇到的话可以改用下面的方式封装。

因为这里想教大家怎么去改造自己的基类,会涉及到 Kotlin 和 Java 两种写法,还有几种类型的基类,讲完的话篇幅很长。所以写了一个库 ViewBindingKtx ,让大家用最少的代码使用上 ViewBinding,同时也方便自己平时在项目中使用。

下面只是介绍部分用法,完整的用法和例子请到 Github(https://github.com/DylanCaiCoding/ViewBindingKtx) 中查看。如果觉得对你有帮助,希望能点个 star 支持一下。

在 build.gradle 里配置 viewBinding 和添加依赖。包含了前面封装的拓展函数,不想把代码拷来拷去的话也可以添加依赖来使用。

dependencies {implementation 'com.dylanc:viewbinding-ktx:1.0.0'
}

介绍一下如何改造 Java 写的 Activity 基类。首先要给基类增加一个继承 ViewBinding 的泛型,然后类里增加一个 binding 全局变量。用工具类初始化 binding,删掉原来设置布局的代码,改为设置 binding.getRoot()。以下是核心的代码。

public abstract class BaseBindingActivity<VB extends ViewBinding> extends AppCompatActivity {private VB binding;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);binding = ViewBindingUtil.inflateWithGeneric(this, getLayoutInflater());setContentView(binding.getRoot());}public VB getBinding() {return binding;}
}

下面是基类改造后的使用示例。

class MainActivity extends BaseBindingActivity<ActivityMainBinding> {@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);getBinding().tvHelloWorld.setText("Hello Android!");}
}

无需声明控件变量,代码简洁很多,而且不会有 id 写错或者类型转换的问题,所以赶紧把 ButterKnife 换了吧。

另外再讲一下列表的基类封装,这里以个人一直在使用的列表库 Drakeet/MultiType 为例子。先看下原本的用法,ViewDelegate 可以当成 Adapter 来看。

class FooViewDelegate : ItemViewDelegate<Foo, FooViewDelegate.ViewHolder>() {override fun onCreateViewHolder(context: Context, parent: ViewGroup): ViewHolder {return ViewHolder(LayoutInflater.from(context).inflate(R.layout.item_foo, parent, false))}override fun onBindViewHolder(holder: ViewHolder, item: Foo) {holder.binding.tvFoo.text = item.value}class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {val fooView: TextView = itemView.findViewById(R.id.foo)}
}

来封装基类,同样要在基类增加一个继承 ViewBinding 的泛型,然后将原来的 ViewHolder 换成 BindingViewHolder,最后在 onCreateViewHolder 方法里调用一个用泛型创建 BindingViewHolder 的方法。下面是封装好的代码。

abstract class BindingViewDelegate<T, VB : ViewBinding> : ItemViewDelegate<T, BindingViewHolder<VB>>() {override fun onCreateViewHolder(context: Context, parent: ViewGroup) =newBindingViewHolderWithGeneric<VB>(parent)
}

使用起来就简单很多,可以对比一下前面的基础用法。

class FooViewDelegate : BindingViewDelegate<Foo, ItemFooBinding>() {override fun onBindViewHolder(holder: BindingViewHolder<ItemFooBinding>, item: Foo) {holder.binding.tvFoo.text = item.value}
}

本文所封装的代码用到了反射,开启混淆时要增加以下配置:

-keepclassmembers class * implements androidx.viewbinding.ViewBinding {public static ** inflate(...);public static ** bind(***);
}

更多基类改造封装 ViewBinding 的 Java 、Kotlin 示例请到 GitHub 查看。

关于用反射进行封装

可能有些人比较介意用反射,其实我也不太想用,能有其它更好的方式实现谁会特意用反射呢。如果反射的使用带来了足够的便利性,个人觉得还是可以接受的。比如 ViewModel 的源码也用了反射进行实例化,相较于自己手动创建 ViewModel 对象,使用官方的 ViewModelProviders 获取 ViewModel 对象能在 Activity 和 Fragment 销毁重建时恢复数据。

其实本文的封装从本质上来说是和 ButterKnife 一样的。同样生成了绑定控件的类,ButterKnife 用注解生成,ViewBinding 解析 XML 生成。都用到了反射,调用 ButterKnife.bind(this) 时反射了一次,我们调用工具类方法时反射了一次。最终的目的都是减少模板代码的编写,让代码更简洁。所以用反射来封装 ViewBinding 个人觉得是合适的。

/   总结   /

本文讲了官方弃用 Kotlin Extensions 插件的原因和使用 ViewBinding 的好处,可以避免 id 写错或类型写错导致的异常。然后讲述了 ViewBinding 的基础用法,并给出了两种 ViewBinding 的封装建议。

后面介绍了个人封装的库 ViewBindingKtx,让大家用最少的代码使用上 ViewBinding,所以该弃用 Kotlin synthetic 和 ButterKnife 了。

项目地址:

https://github.com/DylanCaiCoding/ViewBindingKtx

推荐阅读:

Android 卡顿调研

我又开发了一个非常好用的开源库

用烂的LruCache,你真的完全懂了么?

欢迎关注我的公众号

学习技术或投稿

长按上图,识别图中二维码即可关注

优雅地封装和使用 ViewBinding相关推荐

  1. 优雅地封装和使用 ViewBinding,该替代 Kotlin synthetic 和 ButterKnife 了

    override fun onCreate(savedInstanceState: Bundle?) { super.onCr <Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+ ...

  2. 优雅的封装ajax,含跨域

    之前写过一篇 先定一个小目标,自己封装个ajax,是基于原生js的,也就是jquery中ajax的简化版本实现的思路.众所周知,jquery的ajax是项目中最常用的请求后台的方式,也算是封装的很完美 ...

  3. Kolin 更优雅的封装ProgressDialog

    最近已经开始全面转投kotlin,不得不说使用kotlin开发项目真的是非常舒服,编写代码过程中真的有一种所想即所得的感觉,不会再让一些无聊的东西打断你的思路,回归正题,这边介绍一下kotlin中的一 ...

  4. 小米推送php文档,利用php重载和curl的并行方式优雅的封装小米推送sdk

    前段时间迁移小米推送部分代码,这部分之前是其他人负责的.读了代码,发现了两点: 所有接口的实现除了url和传参基本都是一致的 android和ios的设备需要分别推送一次 刚好这段时间了解了一下php ...

  5. Spring JDBC的优雅设计 - 异常封装(下)

    在上一篇中,蘑菇君记录了自己封装JDBC异常的骚操作.这一次咱们来看看Spring是如何优雅的封装的. 从哪看起呢?这里不得不提一下蘑菇君看源码的思路: 第一步,打开IDE,打开Spring源码 第二 ...

  6. html时钟翻牌效果,干货满满!如何优雅简洁地实现时钟翻牌器(支持JS/Vue/React)

    双十一剁手节过去了,大家应该在很多网页中看到了数字翻牌的效果吧,比如倒计时. 数字增长等.相信很多人都已经自己独立实现过了,我也在网上看了一些demo,发现HTML结构大多比较复杂,用了4个并列的标签 ...

  7. lua面向对象封装及元表(metatable)性能测试

    Lua本身是没有面向对象支持的,但面向对象编程在逻辑复杂的大型工程却很有用.于是很多人用Lua本身的数据结构table来模拟面向对象.最简单的一种方法是把对象的方法.成员都放到table中.如: -- ...

  8. springboot统一封装返回结果

    前言 在项目框架整合阶段,为了更优雅的封装后端返回结果,便于前后端联调,通常需要对后端的返回值进行一定的封装处理,下面介绍2种比较实用的方式 方式1:常规处理 定义一个枚举类,主要包括返回的code和 ...

  9. retrofit框架学习(二)----retrofit封装

    retrofit 的封装 前言 上一篇文章的链接 http://blog.csdn.net/qq_26296197/article/details/78011188 1 上一篇文章讲到Retrofit ...

最新文章

  1. easy ui example
  2. 【转】asp.net中@page指令的属性Inherits、Src、CodeBehind区别
  3. spring logback mysql_logback 日志输出格式
  4. Spring整合Hibernate 二 - 声明式的事务管理
  5. 与自定义词典 分词_【201110】ElasticSearch实现中文分词查询
  6. 将数据压缩到数据结构中
  7. linux cnc_CNC的完整形式是什么?
  8. java 自定义报表_设计好的报表是如何在 web 上显示的
  9. 探讨如何在有着1000万条数据的MS SQL SERVER数据库中实现快速的数据提取和数据分页...
  10. Java 就业培训教程 第二章读书笔记啊
  11. foxmail收取服务器邮件次数,Foxmail:如何设置收取历史邮件?
  12. c++中x的y次方怎么求
  13. 树莓派3b+和 intel movidius 神经元计算棒2代 跑yolo v3 tiny
  14. 万字长文,分享腾讯面试攻略
  15. 中级育婴师证怎么考,需要些什么条件
  16. PTA寒假基础题训练(含解题思路)(中)
  17. 嵌入式linux rootfs,【转】制作 嵌入式 linux 根文件系统 rootfs
  18. python3检查证书到期时间以及域名ip地址
  19. 需要证件照怎么办?教你如何自己在线做照片
  20. python DES加解密实例(pyDes)

热门文章

  1. Qt快速转换路径(斜杠与反斜杠转换)
  2. 2019百度地图离线地图制作
  3. 搭建最新版本的Android开发环境
  4. lol最克制诺手的英雄_LOL:对线很“无解”的5个英雄,其实他们都有克星,诺手只怕它!...
  5. 处理excel,该选择VBA,还是python?
  6. Java实现 蓝桥杯VIP 算法提高 促销购物
  7. 游戏贴图打包工具——TexturePacker详解
  8. POPTEST老李推荐:互联网时代100本必读书,来自100位业界大咖推荐 3
  9. Matlab系列教程_数值计算_求协方差和相关系数
  10. 内存检测工具:sanitizer