一篇获取布局中的元素的通神文

作者AS版本:Android Studio 2021.2.1稳定版:代号“花栗鼠”

image-20220805113558620

实验时间:2022/08/05

findViewById(传统方式)

大家第一次接触 Android 想获取布局中的一个控件,估计大家第一个接触的就是 findViewById() 这个传统方式绑定视图吧

配置布局的xml文件时,有时会给View配置id,而在Activity中寻找到该View的方法就是通过该id号来找到该View的。具体调用的方法就是findViewById。

这个findViewById方法顾名思义,就是根据id来找到View

1
2
3
4
5
 //代码长这样
val button = findViewById<Button>(R.id.button01)
button.setOnClickListener {
Toast.makeText(this,"祝你身体健康",Toast.LENGTH_SHORT).show()
}

注意哦:《第一行代码》中这里是:显示声明变量类型

image-20220805113052613

有些网上教学是:强制类型转换

1
val button = findViewById(R.id.button01) as Button

但是最新版是直接用泛型写啦!!!

Android中的View结构是一个树形结构,findViewById就是自树的根节点,依次遍历其子节点,知道找到目标的id。

原理:

image-20220805140301988

缺点:

  • 写的太累啦
  • 性能略差,树形结构查找;
  • Fragment中容易使用失误;
  • 增加代码耦合度,滥用了bug难找;
  • NPE:一个大型项目中,控件的id经常会重复,xml中删除了一个控件,但是对应的Activity中没有删除这个控件的相关引用,编译时并不会报错,但是运营室时会报出空指针;
  • 可读性不好,简直看不懂哈。

优点:

  • 兼容性好,使用所有场景
  • 非常灵活,适合动态加载layout文件。

ButterKnife (框架注解)

这个Java用的多,不过我们 Kotlin 用的真的不多,而且快遗弃了,这里不展开,知道有这个就行。

1
2
3
4
5
6
7
8
9
10
11
12
13
@BindView(R.id.btn_login)
var mTestBtn:Button

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

ButterKnife.bind(this)

mTestBtn.setOnClickListener {
Toast.makeText(this@MainActivity,"success",Toast.LENGTH_SHORT).show()
}
}

Kotlin-android-extensions(插件)

《第一行代码》中

image-20220805130857651

新版本 AS 是不会默认引入的,AS4.1之后就要自己手动加了

大声BB:但是现在你再创建Android项目,就不会自动帮你依赖了,其原因就是kotlin-android-extensions这个插件已经被废弃了。

QQ图片20220521182515

为啥?Google这新技术迭代跟玩一样啊,有kotlin-android-extensions插件我不用,我就手写,哎,就是玩儿~

其实底层原因是这个是牺牲掉一部分内存来换取的方便,在对应用性能日益严格的今天,这种做法势必会被淘汰掉。

image-20220805131041184

1
2
3
4
//然后就可以直接用xml中对应的id来访问视图了
button01.setOnClickListener {
Toast.makeText(this,"第二次实验",Toast.LENGTH_SHORT).show()
}

原理:通过反编译kotlin-android-extensions的代码,你就会发现,通过kotlin-android-extensions,它会在代码中创建一个HashMap,用来存放所有的id和对应的View的缓存,如果缓存中没有需要的View,那么就通过findViewById去创建,否则就直接获取,这就是它的原理。

三宗罪:

  • 耗费内存

    见原理

  • 资源 ID 重名

    由于kotlin-android-extensions是通过view的id名直接引用的,所以多个布局间的同名id,就需要手动对import进行重命名处理,而且经常会引用错误的布局文件,导致运行崩溃。

  • 仅支持 Kotlin 代码

viewBinding

最低环境要求:

Android Studio:3.6
Android Gradle Plugin 3.6.0

系统会为该模块中的每个 XML 布局文件生成一个绑定类。绑定类的实例包含对在相应布局中具有 ID 的所有视图的直接引用。
在大多数情况下,视图绑定会替代 findViewById

也就是说我们通过使用ViewBinding也可以轻松的解决findViewById的繁琐使用问题,同样可以实现kotlin-android-extensions插件的类似功能。

其实同时还能在java和kotlin中同时使用,解决了kotlin-android-extensions插件使用所带来的问题。另外针对使用MVVM的朋友来说ViewBinding还可以和databinding配合使用,非常的方便。

为某个模块启用视图绑定功能后,系统会为该模块中包含的每个 XML 布局文件生成一个绑定类。每个绑定类均包含对根视图以及具有 ID 的所有视图的引用。系统会通过以下方式生成绑定类的名称:将 XML 文件的名称转换为驼峰式大小写,并在末尾添加“Binding”一词。

比如我们的是first_layout.xml到时候的绑定类名就是FirstLayoutBinding

1
2
3
4
5
6
<LinearLayout ... >
<TextView android:id="@+id/name" />
<ImageView android:cropToPadding="true" />
<Button android:id="@+id/button"
android:background="@drawable/rounded_button" />
</LinearLayout>

此类具有两个字段:一个是名为 name 的 TextView,另一个是名为 button 的 Button。该布局中的 ImageView 没有 ID,因此绑定类中不存在对它的引用。

每个绑定类还包含一个 getRoot() 方法,用于为相应布局文件的根视图提供直接引用。在此示例中,ResultProfileBinding 类中的 getRoot() 方法会返回 LinearLayout 根视图。

1.项目引入

app 下面的 build.gradle 的如图位置加上:

1
2
3
4
5
6
7
viewBinding {
enabled = true
}
//或者
buildFeatures {
viewBinding true
}

注意在 android {}里面哈

image-20220805132017799

2. Activity 中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class MainActivity : AppCompatActivity() {

private lateinit var binding: FirstLayoutBinding

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.first_layout)
//使用步骤
binding = FirstLayoutBinding.inflate(layoutInflater)//1.创建绑定类的实例
val view = binding.root//2.获取根视图的引用
setContentView(view)//3.让根视图成为屏幕上的活动视图
//然后这个 xml 文件中的不管啥子都可以用啦
binding.button01.setOnClickListener {
Toast.makeText(this,"第三种实现办法",Toast.LENGTH_SHORT)
}
}
}

3.Fragment 中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private var _binding: ResultProfileBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!

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

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

4. Adapter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class DemoAdapter(val dataList: List<String>) : RecyclerView.Adapter<DemoAdapter.ViewHolder>() {

inner class ViewHolder(binding: OutcircleMissionFansGroupBinding) : RecyclerView.ViewHolder(binding.root) {
val title: TextView = binding.titleTextView
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding = OutcircleMissionFansGroupBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(binding)
}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val data = dataList[position]
holder.title.text = data.title
}

override fun getItemCount() = dataList.size
}

5.希望在生成绑定类时忽略某个布局文件

官方这样说:

如果您希望在生成绑定类时忽略某个布局文件,请将tools:viewBindingIgnore="true"属性添加到相应布局文件的根视图中:

1
2
3
4
5
<LinearLayout
...
tools:viewBindingIgnore="true" >
...
</LinearLayout>

当你这样操作了发现:image-20220805132427925

解决办法:

  1. 变成<tools:LinearLayout>

    image-20220805104131845

  2. 前面加上一行xmlns:tools="http://schemas.android.com/tools"image-20220805104247376

6.总结

缺陷:

  1. 使用比kotlin-android-extensions复杂
  2. 不灵活,依然有需要手动处理的场景
  3. 默认给所有 xml 生成 blinding 文件,冗余

优点:

  1. 规避了 NPE
  2. 可读性好了点
  3. 内存好了点
  4. 轻量级了一点

亲戚:datablinding (有兴趣了解,xml外面套了一层layout)

其实 ViewBinding 还有封装,这部分内容比较有意思,俺能力有限就请更有能力的观众朋友们下方评论区补充。

实话实说,这个 viewBinding 的使用确实不如kotlin-android-extensions方便,但是天下大势所趋,也没有办法,随着Kotlin的升级,早晚还是要切换到ViewBinding。

欢迎大家加群技术交流/群内也有很多免费资源

欢迎大家加群技术交流/群内也有很多免费资源

参考文献:

视图绑定 | Android 开发者 | Android Developers (google.cn)

废弃的kotlin-android-extensions,是时候接受ViewBinding了 - 知乎 (zhihu.com)

Warning: The ‘kotlin-android-extensions‘ Gradle plugin is deprecated. Please use this migration【已解决】_安果移不动的博客-CSDN博客

Google挖坑后人埋-ViewBinding(上) - 腾讯云开发者社区-腾讯云 (tencent.com)

(13条消息) Android findViewById及替代方案大全_柚子君下的博客-CSDN博客_android findviewbyid