ViewBinding的使用及原理
背景
1.findViewById
作为 Android 开发人员,没有不知道 findViewById 的。
想要操作视图中的控件,都需要先用到 findViewById 来找到这个控件,然后就可以对齐进行一系列的操作了(如:改变属性、设置监听、添加动画)。
缺点:当 xml 视图中控件较多时,代码中的 findViewById 也相应的越来越多。
2.ButterKnife
通过注解的方式来完成布局文件的绑定,从而避免写 findViewById 来提高效率。
示例:
@BindView(R.id.tv_content)
TextView tvContent;
缺点:(其实也不能算缺点,只是时代在发展)在如今组件化随处可见的时期,使用 ButterKnife 就有点麻烦了,在 library 中需要将 R 修改为 R2 才可,这时候当 module 在 application 和 library 切换时就比较麻烦了。
3.kotlin-android-extensions
作为很多人初始接触 Kotlin 的一个入门操作,最初都是因为使用 kotlin-android-extensions 简便而用上了 Kotlin。
使用确实很简单,在工程 build.gradle 文件中添加
dependencies{
classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version"
}
然后在 module 的 build.gradle 中添加
apply plugin: 'kotlin-android-extensions'
这时候就直接可以使用 xml 视图中的控件 id 来操作控件了
tv_content.text = "Smaple"
缺点:
1.在模块之间不能通用,比如:业务模块中不能使用基础模块中的基础布局
2.由于不同的资源文件可以使用相同的控件 id 名称,会有一定的错误可能性。
由于上面的原因,所以有了 ViewBinding ,后面还有 DataBinding。
关于 DataBinding 可以参考笔者的这篇介绍:
ViewBinding 的使用
添加配置在 module 的 build.gradle
android{
buildFeatures{
viewBinding true
}
}
之后在编译的时候就会为我们的所有 xml 文件生成对应的 Binding 类了,如:activity_main.xml 对应的 ActivityMainBinding。
使用也非常简单,Activity 如下:
class MainActivity : AppCompatActivity() {
private lateinit var activityMainBinding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activityMainBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(activityMainBinding.root)
activityMainBinding.tv_content = "Smaple"
}
}
Fragment 如下:
class MainFragment: Fragment() {
private var fragmentMainBinding: FragmentMainBinding? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
fragmentMainBinding = FragmentMainBinding.inflate(inflater, container, false)
return super.onCreateView(inflater, container, savedInstanceState)
}
// 注意:在这里将 Binding 对象置为 null,防止内存泄漏(因为 Fragment 存在的时间大于视图)
override fun onDestroyView() {
super.onDestroyView()
fragmentMainBinding = null
}
}
如果某个布局不想使用 ViewBinding ,也就是不生成对应的 xxxBinding 类,那么只需要在 xml 的根 View 中添加 tools:viewBindingIgnore="true"
。
ViewBinding 原理
原理追溯到本质上还是使用了 findViewById 来实现了绑定。
只不过这个过程是通过 gradle 插件来实现的,在编译的时候 gradle 插件会为每一个 xml 布局生成对应的 xxxBinding 类,当然如果某个 xml 中配置了 tools:viewBindingIgnore="true"
的话就会被插件跳过了。
ActivityMainBinding.java 源码如下:
public final class ActivityMainBinding implements ViewBinding {
@NonNull
private final RelativeLayout rootView;
@NonNull
public final TextView tvContent;
private ActivityMainBinding(@NonNull RelativeLayout rootView, @NonNull TextView tvContent) {
this.rootView = rootView;
this.tvContent = tvContent;
}
@Override
@NonNull
public RelativeLayout getRoot() {
return rootView;
}
@NonNull
public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater) {
return inflate(inflater, null, false);
}
@NonNull
public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater,
@Nullable ViewGroup parent, boolean attachToParent) {
View root = inflater.inflate(R.layout.activity_main, parent, false);
if (attachToParent) {
parent.addView(root);
}
return bind(root);
}
@NonNull
public static ActivityMainBinding bind(@NonNull View rootView) {
// The body of this method is generated in a way you would not otherwise write.
// This is done to optimize the compiled bytecode for size and performance.
int id;
missingId: {
id = R.id.tv_content;
TextView tvContent = ViewBindings.findChildViewById(rootView, id);
if (tvContent == null) {
break missingId;
}
return new ActivityMainBinding((RelativeLayout) rootView, tvContent);
}
String missingId = rootView.getResources().getResourceName(id);
throw new NullPointerException("Missing required view with ID: ".concat(missingId));
}
}
可以看到,显示通过 inflate 方法得到了 View 对象,然后通过 bind 方法中通过 findViewById 完成了控件的绑定。
而 getRoot 方法其实就是对根 View 的返回。
ViewBinding 封装
既然使用 ViewBinding 的套路是固定的,那么我们就可以将其抽取出来。
代码如下:
abstract class BaseActivity<T: ViewBinding>: AppCompatActivity() {
lateinit var mViewBinding: T
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mViewBinding = getViewBinding()
setContentView(mViewBinding.root)
}
abstract fun getViewBinding(): T
}
使用如下:
class SecondActivity: BaseActivity<ActivitySecondBinding>() {
override fun getViewBinding(): ActivitySecondBinding {
return ActivitySecondBinding.inflate(layoutInflater)
}
fun test(){
mViewBinding.tv_content = "Sample"
}
}