override fun onCreate(savedInstanceState: Bundle?) {
super.onCr 《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》无偿开源 徽信搜索公众号【编程进阶路】 eate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.tvHelloWorld.text = “Hello Android!”
}
}

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

class HomeFragment : Fragment() {
private var _binding: HomeFragmentBinding? = null
private 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
) : 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.size

class TextViewHolder(val binding : ItemTextBinding) : RecyclerView.ViewHolder(binding.root)
}

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

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

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 Activity.inflate() = lazy {
inflateBinding(layoutInflater).apply { setContentView(root) }
}

inline fun Dialog.inflate() = lazy {
inflateBinding(layoutInflater).apply { setContentView(root) }
}

@Suppress(“UNCHECKED_CAST”)
inline fun 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 对象,不能用延时委托改用属性委托。下面是封装的代码:(5 月 12 号更新增加 doOnDestroyView 方法)

inline fun Fragment.bindView() =
FragmentBindingDelegate(VB::class.java)

inline fun Fragment.doOnDestroyView(crossinline block: () -> Unit) =
viewLifecycleOwner.lifecycle.addObserver(object : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onDestroyView() {
block.invoke()
}
})

class FragmentBindingDelegate(
private val clazz: Class
) : ReadOnlyProperty<Fragment, VB> {

private var binding: VB? = null

@Suppress(“UNCHECKED_CAST”)
override fun getValue(thisRef: Fragment, property: KProperty<*>): VB {
if (binding == null) {
binding = clazz.getMethod(“bind”, View::class.java)
.invoke(null, thisRef.requireView()) as VB
thisRef.doOnDestroyView { binding = null }
}
return binding!!
}
}

使用起来就体现出封装的优势了,不用特地写个 _binding 对象和重写 onDestoryView() 方法来清除实例对象。另外,如果还有其它释放操作要在 binding 销毁前执行,需要写在 doOnDestroyView() 方法里。

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!”
doOnDestroyView {
// 在 binding 对象销毁前进行释放操作
}
}
}

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

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

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

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

inline fun newBindingViewHolder(parent: ViewGroup): BindingViewHolder {
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 T
return BindingViewHolder(binding)
}

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

class TextAdapter(
private val list: List
) : RecyclerView.Adapter<BindingViewHolder>() {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
newBindingViewHolder(parent)

override fun onBindViewHolder(holder: BindingViewHolder, 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](() 中查看。如果觉得对你有帮助,希望能点个 star 支持一下。

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

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

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

public abstract class BaseBindingActivity extends AppCompatActivity {

private VB binding;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ViewBindingUtil.inflateWithGeneric(this, getLayoutInflater());
setContentView(binding.getRoot());
}

public VB getBinding() {
return binding;
}
}

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

优雅地封装和使用 ViewBinding,该替代 Kotlin synthetic 和 ButterKnife 了相关推荐

  1. 优雅地封装和使用 ViewBinding

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

  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. 将视图转为image_使用视图绑定替代 findViewById

    从 Android Studio 3.6 开始,视图绑定能够通过生成绑定对象来替代 findViewById,从而可以帮您简化代码.移除 bug,并且从 findViewById 的模版代码中解脱出来 ...

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

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

  9. springboot统一封装返回结果

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

最新文章

  1. selenium+Edge浏览器实现web端自动化测试
  2. android 按钮换行_Android LinearLayout实现自动换行
  3. 优化函数式编程:向 PHP 移植 Clojure 函数
  4. 判断一棵二叉树是否为AVL树
  5. Python字典dict的增删查改及常用操作
  6. 将json数据写入html表单,将json数据提交到html表中
  7. centos-修改分辨率
  8. 【C语言】在线OJ题 BC72-BC87-牛客网编程初学者入门训练
  9. HTML 5 中的新元素
  10. 返回的图片 buffer 怎么接收_面试题:Kafka 会不会丢消息?怎么处理的?
  11. 人脸检测caffe下步骤
  12. 外贸客户类型及跟进策略、找客户渠道
  13. .Net Remoting 入门
  14. 继续教育自动听课软件_2017继续教育挂机软件下载
  15. u盘复制到计算机的文档打不开怎么办,U盘文件复制到别的电脑打不开怎么办
  16. 如何检测笔记本电脑的主板,cpu,硬盘的温度
  17. 为什么夏天家里空调滴水
  18. 爬虫学习案例3:数据可视化
  19. egg-views-ejs
  20. 配置windows 静态IP地址

热门文章

  1. 【组网工程】cisco packet tracer 路由器组网
  2. 文件缓存FileCache
  3. 安科瑞配电列头柜产品XXX数据中心案例分享
  4. OCR:财务报表识别
  5. 农夫山泉有点牛!港股打新把暗盘系统都整崩溃了
  6. 智能创新引导工具软件——项目总结报告
  7. spoolsv.exe占用cpu 100%的解决方法
  8. js毫秒转换年月日时分秒
  9. IOS学习六:Date Picker, Picker View选取器控件初步
  10. SQL Server 认证(Certification)