当前位置:   article > 正文

【Jetpack】学穿:ViewBinding → 视图绑定_viewbinding { enabled = false (3)_android jetpack viewbinding

android jetpack viewbinding

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!


img
img

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

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

需要这份系统化的资料的朋友,可以戳这里获取

编译后,AGP会为Module中包含的XML布局文件生成一个绑定类,类名规则:

XML文件名转换为Pascal大小写,并加上Binding,比如:result_profile.xml → ResultProfileBinding。

② 三个类绑定API

// View已存在
fun bind(view : View) : T

// View未存在
fun inflate(inflater : LayoutInflater) : T
fun inflate(inflater : LayoutInflater, parent : ViewGroup?, attachToParent : Boolean) : T

接下来演示一波各种场景下的ViewBinding用法,其实都是围绕上述三个API进行的~

③ Activity

class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 1、实例化绑定实例
binding = ActivityMainBinding.inflate(layoutInflater)
// 2、获得对根视图的引用
val view = binding.root
// 3、让根视图称为屏幕上的活动视图
setContentView(view)
// 4、引用视图控件
binding.tvContent.text = “修改TextView文本”
}
}

④ Fragment

class ContentFragment: Fragment() {
private var _binding: FragmentContentBinding? = null
private val binding get() = _binding!!

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentContentBinding.inflate(inflater, container, false)
return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.ivLogo.visibility = View.GONE
}

override fun onDestroyView() {
super.onDestroyView()
// Fragment的存活时间比View长,务必在此方法中清除对绑定类实例的所有引用
// 否则会引发内存泄露
_binding = null
}
}

如果布局已inflated,还可以采用另一种写法(调bind):

class TestFragment: Fragment(R.layout.fragment_content) {
private var _binding: FragmentContentBinding? = null

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val binding = FragmentContentBinding.bind(view)
_binding = binding
binding.ivLogo.visibility = View.VISIBLE
}

override fun onDestroyView() {
super.onDestroyView()
// 同样需要置空
_binding = null
}
}

⑤ Dialog

如果是继承DialogFragment写法同Fragment,如果是继承Dialog写法示例如下(PopupWindow类似)~

class TestDialog(context: Context) : Dialog(context) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = DialogTestBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.tvTitle.text = “对话框标题”
}
}

⑥ RecyclerView

class TestAdapter(list: List) : RecyclerView.Adapter<TestAdapter.ViewHolder>() {
private var mList: List = list

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
// 需在此初始化以获得父类容器
val binding = ItemTestBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(binding)
}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.tvItem.text = “Adapter”
}

override fun getItemCount() = mList.size

// 传递Binding对象
class ViewHolder(binding: ItemTestBinding) : RecyclerView.ViewHolder(binding.root) {
var tvItem: TextView = binding.tvItem
}
}

⑦ 自定义ViewGroup

ViewGroup子类才能使用视图绑定,View子类不可使用,示例如下:

class TestLayout: LinearLayout {
constructor(context: Context): super(context)
constructor(context: Context, attrs: AttributeSet): super(context, attrs) {
val inflater = LayoutInflater.from(this.context)
val binding = ItemLayoutBinding.inflate(inflater, this, true)
binding.tvLayout.text = “自定义ViewGroup”
}

override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
}
}

⑧ include

根据include的布局xml是否带**标签**,分为两种,先是不带的情况:
include的xml文件名为sub_include_test.xml,id为include_layout:

然后是带的情况,布局文件改下:

使用部分的代码不变,运行奔溃报错信息如下:

原因是merge并不会加载到布局里,解法:把include标签的id去掉,然后bind传入父布局~

⑨ ViewStub

基础用法很简单,也很好上手,但存在下述问题:

需重复编写:创建和回收ViewBinding实例的样板代码,特别是Fragment,还要手动置空。

所以有必要封装优化一波~

0x4、封装优化思路

① 泛型 + 父类实现模板代码

最容易想到的常规写法,配合泛型,把模板代码都在父类中写好,非常简单:

abstract class BaseFragment(layoutId: Int) : Fragment(layoutId) {
private var _binding: T? = null
val binding get() = _binding!!

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = initBinding(view)
init()
}

abstract fun initBinding(view: View): T

abstract fun init()

override fun onDestroyView() {
_binding = null
super.onDestroyView()
}
}

// 子类实现
class TestFragment : BaseFragment(R.layout.fragment_content) {
override fun initBinding(view: View) = FragmentContentBinding.bind(view)

override fun init() {
binding.ivLogo.visibility = View.VISIBLE
}

}

② Kotlin委托 + lifecycle组件

有些朋友可能觉得写在父类中侵入性太强,接着试下用其他方式进行封装,先看原始Activity:

要把圈住的代码干掉,先是 泛型传递问题,泛型在进JVM前会被擦除,可在运行时通过反射获得,还可以通过实例化类类型代替类引用,如:

fun <T: Activity> FragmentActivity.startActivity(context: Context, clazz: Class) {
startActivity(Intent(context, clazz))
}

// 调用处
startActivity(context, MainActivity::class.java)

而在Kotlin中还可以用 inline 定义一个 内联函数 (编译时自动替换到调用位置),配合 reified 具体化(类型不擦除),得到泛型类型的Class,如:

inline fun Activity.startActivity(context: Context) {
startActivity(Intent(context, T::class.java))
}

// 调用
startActivity(context)

可以,配合反射invoke()调用,随手写上一个扩展方法:

调用下:

看似十拿九稳,结果一跑就崩:

不过也在意料之内,Activity还没onCreate()就初始化了,不空才怪,可以利用 标准委托-lazy 延迟初始化,修改后的代码:

调用下:

运行通过,你还可以把还可以把setContentView()也塞到扩展中:

配合lifecycle组件,顺手把Fragment的也写出来:

调用下:

对了,如果还不想使用反射,可以利用Kotlin高阶函数,示例如下:

调用下:

啧啧啧,你还可以不用lazy,自己重写ReadOnlyProperty,这里只是试试水封装,并没用到生产上,更完善的封装方案可自行参考下述开源库:

  • Binding
  • VBHelper

0x5、原理

AGP会为模块中每个XML生成一个绑定类,该类的实例会直接引用布局中声明了资源id的View

① 自动生成的绑定类

打开:module模块名/build/generated/intermediates/javac/渠道/包名/databinding
可以看到 (基于AGP 7.1.1,不同AGP版本可能不一样):

自动生成的class文件,随手打开一个:

所以本质上还是findViewById,只是自动生成了控件实例,并一一对应,接着简单了解下大概的生成流程。

② 生成Java类

执行gradlew assembleDebug,在Task构建列表没找到ViewBinding,却找到了DataBinding:

打开AGP源码,全局搜 dataBindingMergeGenClasses → DataBindingMergeBaseClassLogTask.kt

跟到:TaskManager.kt → createDataBindingTasksIfNecessary

2333,跟DataBinding混一起了,所以 ViewBinding其实只是DataBinding功能的一小部分~
看回:DataBindingMergeBaseClassLogTask,增量和全量执行动作:

跟下:DataBindingMergeBaseClassLogDelegate

跟下:DataBindingMergeBaseClassLogRunnable

判断文件是否新建、修改、或移除,跟下是哪个文件:

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!


img
img

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

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

需要这份系统化的资料的朋友,可以戳这里获取

索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**


[外链图片转存中…(img-FZmBtpdz-1715908445982)]
[外链图片转存中…(img-uJNtl0tY-1715908445982)]

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

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

需要这份系统化的资料的朋友,可以戳这里获取

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号