「本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!」
ViewBinding 使用场景补充
上一片文章文章写了为什么我推荐你用ViewBinding 替换findViewById? 的文章, 文章下边评论大家讨论很积极,有部分朋友评论说没有写在Adapter和include中的使用,以及使用中有没有什么注意的点,还有少许人把 ViewBinding和DataBinding 有点傻傻分不清楚。
所以有了这篇文章,这片文章主要讲一下在Adapter中的应用, 以及如果有include标签的话viewbinding能不能使用,如何用的问题
ViewBinding在Adapter中的使用
写一个简单的页面,页面包含一个列表。列表有一些数据:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
xmlns:tools="http://schemas.android.com/tools"
tools:viewBindingIgnore="false"
android:background="@color/white"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp">
<TextView
android:id="@+id/tv_item_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:text="第几项"
android:textColor="@color/black"
android:textSize="20dp" />
<TextView
android:id="@+id/tv_item_index"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:text="1"
android:textColor="@color/black"
android:textSize="20dp" />
</RelativeLayout>
为了演示,创建了两个很简单的xml文件, 一个页面就一个RecyclerView ,列表适配器简单的item。就两个文本。 接下来重点看下adapter中如何使用 viewbinding 绑定视图。
class ViewBindingTestAdapter(val mList: ArrayList<ItemBean>):RecyclerView.Adapter<BindViewHolder>() {
//创建一个供我们Adapter使用的ViewHolder,注意传递的参数,是我们是我们生成的xml 生成的Viewbinding 的实例
class BindViewHolder(var itemBinding: ItemBindingLayoutBinding) :
RecyclerView.ViewHolder(itemBinding.root) {
fun bind(itemBean: ItemBean) {
itemBinding.tvItemName.text = itemBean.itemNamed
itemBinding.tvItemIndex.text = itemBean.itemIndex.toString()
}
}
//重点 : 替换我们传统的写法 直接用我们layout生成的Binding创建View ,然后传递给ViewHolder ,这样就可以愉快的使用我们的ViewBinding啦
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindViewHolder {
val itemBinding =
ItemBindingLayoutBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return BindViewHolder(itemBinding)
}
override fun getItemCount(): Int {
return mList.size
}
override fun onBindViewHolder(holder: BindViewHolder, position: Int) {
var itemBean = mList.get(position)
holder.bind(itemBean)
}
}
然后看一下我们主类如何使用Adapter的
class ViewBindingActivity : AppCompatActivity() {
lateinit var binding: ActivityViewBindingLayoutBinding
lateinit var adapter: ViewBindingTestAdapter
lateinit var mList: ArrayList<ItemBean>
override fun onCreate(savedInstanceState: Bundle?) { //使用ViewBinding绑定视图
super.onCreate(savedInstanceState)
binding = ActivityViewBindingLayoutBinding.inflate(layoutInflater)
val view = binding.root;
setContentView(view)
initView()
}
//初始化View 并且创建模拟数据
private fun initView() {
mList = ArrayList()
for (i in 1..100) {
var itemBean = ItemBean()
itemBean.itemNamed = "第$i 条数据"
itemBean.itemIndex = i
mList.add(itemBean)
}
adapter = ViewBindingTestAdapter(mList)
binding.rvList.setHasFixedSize(true)
binding.rvList.layoutManager = LinearLayoutManager(this)
binding.rvList.adapter = adapter
}
}
是不是使用也超级简单呢? 赶快把项目中的Adapter 替换成ViewBinding吧。可以大概率避免空指针的问题
ViewBinding 在include中的使用
include标签有两种情况,一种是不带merge标签,这种就相对来说简单很多,就是我们需要给我们的include定义一个id 这样 ViewBinding在生成绑定类时候会自动添加include。看代码
<inlude>
不带 <merge>
标签
首先写一个include 不带merge标签的layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="60dp"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_login_google"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:textColor="@android:color/holo_red_dark"
android:gravity="center"
android:text="Google"
android:textSize="20dp"
android:textStyle="bold" />
<View
android:layout_width="0.5dp"
android:layout_height="match_parent"
android:background="@color/black" />
<TextView
android:id="@+id/tv_login_facebook"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:textColor="@android:color/holo_red_dark"
android:gravity="center"
android:text="Facebook"
android:textSize="20dp"
android:textStyle="bold" />
<View
android:layout_width="0.5dp"
android:layout_height="match_parent"
android:background="@color/black" />
<TextView
android:id="@+id/tv_login_twitter"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:textColor="@android:color/holo_red_dark"
android:text="Twitter"
android:textSize="20dp"
android:textStyle="bold" />
</LinearLayout>
创建一个包含三个文本按钮的layout
然后我们在使用的界面 include进去:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:background="@color/white"
android:orientation="vertical"
tools:context=".MainActivity"
tools:ignore="MissingConstraints"
tools:viewBindingIgnore="false">
<TextView
android:id="@+id/tv_viewBinding"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#ac99FF"
android:gravity="center"
android:text="ViewBinding"
android:textColor="@color/design_default_color_primary_dark"
android:textSize="20dp" />
//划重点,需要给include 定义id
<include
android:id="@+id/include_login"
layout="@layout/include_login_layout" />
</LinearLayout>
然后我们就可以在代码中愉快的使用啦:
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.tvViewBinding.setOnClickListener {
val intent = Intent()
intent.setClass(this, ViewBindingActivity::class.java)
startActivity(intent)
}
//先调用我们给include 定义的Id 在调用具体的view视图
binding.includeLogin.tvLoginFacebook.setOnClickListener {
Log.e("pLog", "facebook登录")
}
binding.includeLogin.tvLoginGoogle.setOnClickListener {
Log.e("pLog", "google登录")
}
binding.includeLogin.tvLoginTwitter.setOnClickListener {
Log.e("pLog", "twitter登录")
}
}
}
看到这里,你是不是有很多问号? 我们接下来看下他生成的具体Binding类
我们include标签layout也给我我们生成了一个对应的Binding类,然后我们看下我们引用ActivityMainBinding类代码:
public final class ActivityMainBinding implements ViewBinding {
@NonNull
private final LinearLayout rootView;
//我们定义的include 标签id 生成的layout
@NonNull
public final IncludeLoginLayoutBinding includeLogin;
@NonNull
public final TextView tvViewBinding;
private ActivityMainBinding(@NonNull LinearLayout rootView,
@NonNull IncludeLoginLayoutBinding includeLogin, @NonNull TextView tvViewBinding) {
this.rootView = rootView;
this.includeLogin = includeLogin;
this.tvViewBinding = tvViewBinding;
}
@Override
@NonNull
public LinearLayout 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.include_login;
View includeLogin = ViewBindings.findChildViewById(rootView, id);
if (includeLogin == null) {
break missingId;
}
//重点看这里。画不同
IncludeLoginLayoutBinding binding_includeLogin = IncludeLoginLayoutBinding.bind(includeLogin);
id = R.id.tv_viewBinding;
TextView tvViewBinding = ViewBindings.findChildViewById(rootView, id);
if (tvViewBinding == null) {
break missingId;
}
return new ActivityMainBinding((LinearLayout) rootView, binding_includeLogin, tvViewBinding);
}
String missingId = rootView.getResources().getResourceName(id);
throw new NullPointerException("Missing required view with ID: ".concat(missingId));
}
}
是不是看懂他的逻辑流程啦。 那么我们接下来看下带merge标签的 该如何使用
<inlude>
带 <merge>
标签
当在一个布局中包含另一个布局时,我们通常使用一个带有 <merge>
标记的布局,主要用来减少View层级问题。
我们能不能直接通过给include 定义id 然后和不带merge标签一样使用呢? 先试一下。
先定义一个简单的带merge标签的 layout
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="我是带Merge标签的include layout"
android:textColor="@color/black"
android:textSize="20dp"
android:textStyle="bold" />
</merge>
然后直接include,定义一个id。预览实图也是没问题的 。 是不是可以了呢? 来运行下。
理想很丰满,现实很骨感,运行后,直接给我大嘴巴上
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3449)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3601)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
Caused by: java.lang.NullPointerException: Missing required view with ID: com.example.binding_module:id/include_merge
at com.example.binding_module.databinding.ActivityMainBinding.bind(ActivityMainBinding.java:91)
at com.example.binding_module.databinding.ActivityMainBinding.inflate(ActivityMainBinding.java:58)
at com.example.binding_module.databinding.ActivityMainBinding.inflate(ActivityMainBinding.java:48)
at com.example.binding_module.MainActivity.onCreate(MainActivity.kt:15)
at android.app.Activity.performCreate(Activity.java:8000)
at android.app.Activity.performCreate(Activity.java:7984)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1309)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3422)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3601)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
提示我找不到include_merge 。如果不能直接用 include定义id的方式,能不能通过别的方式呢? merge 的layout也生成了对应的 binding类。嘿嘿。我们能不能直接用呢。 试试....
即然生成了Binding类,我们直接拿来用,上代码:
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
//定义merge标签生成的 Binding类
lateinit var includeMergeLayoutBinding: IncludeMergeLayoutBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
//通过bind方法初始化
includeMergeLayoutBinding = IncludeMergeLayoutBinding.bind(binding.root)
setContentView(binding.root)
binding.tvViewBinding.setOnClickListener {
val intent = Intent()
intent.setClass(this, ViewBindingActivity::class.java)
startActivity(intent)
}
binding.includeLogin.tvLoginFacebook.setOnClickListener {
Log.e("pLog", "facebook登录")
}
binding.includeLogin.tvLoginGoogle.setOnClickListener {
Log.e("pLog", "google登录")
}
binding.includeLogin.tvLoginTwitter.setOnClickListener {
Log.e("pLog", "twitter登录")
}
//直接拿来用 >_>
includeMergeLayoutBinding.tvIncludeMerge.text = "这种方式可以成功"
includeMergeLayoutBinding.tvIncludeMerge.setOnClickListener{
Log.e("pLog", "点击 include merge标签的layout")
}
}
}
成功的输出了我们的打印。完美。
总结
ViewBinding 大大的减少了我们的工作量,也 避免了空指针和类型转化的错误。况且能支持include标签,不用在在类中定义各种View 然后在findViewByid了。直接定义好id直接拿来用。爽歪歪。赶快用在项目中吧。
你好,我是大忽悠,喜欢我的文章的话,记得帮我点个赞