Android 视图绑定 ViewBinding

2022-12-07 17:24:36 浏览数 (1)

介绍

我们以前获取xml中的View,通常通过findViewById但是样板代码太多,而且容易引用错误发生崩溃。

后续有很多自动注解方式来帮我们节省时间,其中butterknife可以算是典型代表了。

但后续Android 升级之后再依赖库中的R资源并不是find了。同时当Gradle 5.0之后Resource ID将不会是final类型了。ButterKnife官方也标注了ButterKnife被废弃使用。(http://jakewharton.github.io/butterknife/ )

针对该需求,Google 推出了ViewBinding来帮我们解决视图绑定。

而如果我们先接触的DataBinding 那么就很容易将ViewBinding误解成两个是一样的。但其实他们并不一样。

一个叫做视图绑定(ViewBinding),而另外一个叫做数据绑定(DataBinding)。

1. ViewBinding

要想使用ViewBinding 必须Android Studio 3.6 Canary 11 以上版本才行。

开启viewBinding功能:在app/build.gradle文件中添加:

代码语言:javascript复制
//老版本 方法一:
android {
        ...
        viewBinding {
            enabled = true //关键点
        }
    }

//新版本 方法二:
android {
        ...
    buildFeatures {
        viewBinding  true
}
 }

以上两个方法都可以,建议用新版本,如果Studio比较老或者gradle版本老,可以用旧版本。

配置完毕后,Gradle一下整个项目,AndroidStudio会自动为每个XML布局文件生成一个绑定类,并不需要我们给xml文件中添加任何代码。(Databinding就需要我们调整xml布局 才会生成。)

实例:假设创建了一个activity_main.xml 的布局文件:

代码语言:javascript复制
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

   <Button
        android:id="@ id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="3dp"
        android:text="Detect"
        app:layout_constraintStart_toStartOf="parent" />
    ...

</androidx.constraintlayout.widget.ConstraintLayout>

那么就会自动生成一个ActivityMainBinding类。

如果我们布局中给View 添加了ID,那么就会在Binding类中存在,如果没有ID则不存在。

1.1 过滤xml转binding类

我们如果有些类不想自动生成对应的binding类,那么只需要在该布局的layout中添加忽略字段即可:tools:viewBindingIgnore="true"

实例如下:

代码语言:javascript复制
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    tools:viewBindingIgnore="true" >
        ...
    </androidx.constraintlayout.widget.ConstraintLayout>

1.2 Activity 使用ViewBinding

我们如果开启ViewBinding之后,但是activity中仍然是通过:setContentView(R.layout.activity_main)加载布局的话。那么ViewBinding功能就没有起作用了。我们需要通过ViewBinding类来注入。

实例 java版本:

代码语言:javascript复制
    private ActivityMainBinding binding;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot()); //添加绑定
    }

实例Kotlin版本:

代码语言:javascript复制
   private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
    }

而我们如果想使用布局中的View ,可以直接通过binding对象操作即可:

实例:

代码语言:javascript复制
    binding.button.setText("zinyan");
    binding.button.setOnClickListener(new View.OnClickListener() {
        viewModel.userClicked()
    });

我们从此就不用害怕id写错了,或者引用其他布局中的view了。

1.3 Fragment中使用ViewBinding

如果是在Fragment中使用,大体上和Activity差不多。只是需要在onCreateView方法中进行初始化即可。

实例:Java版

代码语言:javascript复制
 private ActivityMainBinding binding;

    @Override
    public View onCreateView (LayoutInflater inflater,
                              ViewGroup container,
                              Bundle savedInstanceState) {
        binding = ActivityMainBinding.inflate(inflater, container, false);
        return binding.getRoot();
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        binding = null;
    }

实例:Kotlin 版

代码语言:javascript复制
    private var _binding: ActivityMainBinding? = null

    private val binding get() = _binding!!

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

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

有别于Activity,我们需要在FragmentonDestoryView中进行清除绑定引用。也就是上面实例中设置为null

这是因为Fragment的存活时间比它的视图时间长。否则会出现OOM异常。

1.4 Adapter中使用ViewBinding

代码语言:javascript复制
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
    val viewBinding = DemoItemViewBinding.inflate(LayoutInflater.from(parent.context))
    val layoutParams = ViewGroup.LayoutParams(
        ViewGroup.LayoutParams.MATCH_PARENT,
        ViewGroup.LayoutParams.MATCH_PARENT
    )
    viewBinding.root.layoutParams = layoutParams
    return MyViewHolder(viewBinding.root)
}

java版本:

代码语言:javascript复制
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    DemoItemViewBinding binding = DemoItemViewBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
    return new MyViewHolder(binding);
}

2. 总结

与使用 findViewById 相比,视图绑定具有的优点:

  • Null 安全:由于视图绑定会创建对视图的直接引用,因此不存在因视图 ID 无效而引发 Null 指针异常的风险。此外,如果视图仅出现在布局的某些配置中,则绑定类中包含其引用的字段会使用 @Nullable 标记。
  • 类型安全:每个绑定类中的字段均具有与它们在 XML 文件中引用的视图相匹配的类型。这意味着不存在发生类转换异常的风险。

而相较于DataBinding 的优势在于:

  • 更快的编译速度:视图绑定不需要处理注释,因此编译时间更短。
  • 易于使用:视图绑定不需要特别标记的 XML 布局文件,因此在应用中采用速度更快。在模块中启用视图绑定后,它会自动应用于该模块的所有布局。

而DataBinding 的优势就在于:布局和数据的双向绑定了。

所以其实我们可以在项目之中同时使用视图绑定和数据绑定。根据具体的业务需求,使用不同的绑定方式获取布局对象

关于DataBinding的相关介绍可以通过这篇文章了解:https://zinyan.com/?p=105

0 人点赞