ViewBinding 在Adapter和include中的应用 和注意事项。

7,198 阅读2分钟

「本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!

ViewBinding 使用场景补充

上一片文章文章写了为什么我推荐你用ViewBinding 替换findViewById? 的文章, 文章下边评论大家讨论很积极,有部分朋友评论说没有写在Adapter和include中的使用,以及使用中有没有什么注意的点,还有少许人把 ViewBinding和DataBinding 有点傻傻分不清楚。

文章配图1.png

文章配图2.png 所以有了这篇文章,这片文章主要讲一下在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

创建一个包含三个文本按钮的layout

1111.png

然后我们在使用的界面 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类

image.png

我们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。预览实图也是没问题的 。 是不是可以了呢? 来运行下。

image.png

理想很丰满,现实很骨感,运行后,直接给我大嘴巴上

        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类。嘿嘿。我们能不能直接用呢。 试试....

image.png
即然生成了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")
        }
    }
}

image.png

成功的输出了我们的打印。完美。

总结

ViewBinding 大大的减少了我们的工作量,也 避免了空指针和类型转化的错误。况且能支持include标签,不用在在类中定义各种View 然后在findViewByid了。直接定义好id直接拿来用。爽歪歪。赶快用在项目中吧。

你好,我是大忽悠,喜欢我的文章的话,记得帮我点个赞