类似在 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;
}
}

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

class MainActivity extends BaseBindingActivity {

@Override
protected 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)
}

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

文末

架构师不是天生的,是在项目中磨练起来的,所以,我们学了技术就需要结合项目进行实战训练,那么在Android里面最常用的架构无外乎 MVC,MVP,MVVM,但是这些思想如果和模块化,层次化,组件化混和在一起,那就不是一件那么简单的事了,我们需要一个真正身经百战的架构师才能讲解透彻其中蕴含的深理。

移动架构师

系统学习技术大纲

一线互联网Android面试题总结含详解(初级到高级专题)

image

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
img
一线互联网Android面试题总结含详解(初级到高级专题)

[外链图片转存中…(img-yLx4Fplo-1712807886976)]

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-I5uxVxME-1712807886976)]

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐