组件化之路 - ViewBinding一知半解

嗯哼,你没有看错,ViewBinding确实是架构化组件的重要组成部分之一,主要负责视图绑定方面 ~

兄弟篇

在Android中findViewById是最常见的模板代码之一,在我新手时期最喜欢写这样的代码,因为很有成就感…

But - 随着项目代码体量的不断变大,像findViewById这样的模板代码,注定要被优化掉,记得早期用的是ButterKnife黄油刀优化该模板代码,不过在Gradle插件升级到5.0版本之后,ButterKnife将无法再被使用!故此我们需要寻找的新的方式来解决这个问题 ~

掌握ViewBinding是基础,也是重点,但更多的时候我们需要把它集成到项目中,所以后期我总结了一篇ViewBinding基类封装 ~

基础介绍

随着Google每年的不断强大,在2019年I/O大会上公布了一款Android视图绑定工具ViewBinding。它的使用方式有点类似DataBinding,但相比DataBinding,ViewBinding是一个更轻量级、更纯粹的findViewById的替代方案

在市场上的很多开发者出现了语言分化,一部分继续使用Java开发,另一部分则使用Google推荐的Kotlin开发

针对不同语言,Kotlin开发者使用的是2017年推出的kotlin-android-extensions插件Java开发者使用的是Google2019年推出的 ViewBinding ~

Look here:为何现在才归纳ViewBinding?主要因为近期kotlin-android-extensions插件宣布不久就会废弃了,这意味着很难保证以后的兼容性,同时引用方式也发生了改变;所以现在很多kt开发者也在想着改变项目中视图组件为ViewBinding?

为何使用ViewBinding?它与findViewById 有什么区别?与数据绑定又有什么区别?

与 findViewById 的区别

  • Null 安全:由于视图绑定会创建对视图的直接引用,因此不存在因视图 ID 无效而引发 Null 指针异常的风险。此外,如果视图仅出现在布局的某些配置中,则绑定类中包含其引用的字段会使用 @Nullable 标记。
  • 类型安全:每个绑定类中的字段均具有与它们在 XML 文件中引用的视图相匹配的类型。这意味着不存在发生类转换异常的风险。
    这些差异意味着布局和代码之间的不兼容将会导致构建在编译时(而非运行时)失败。

与数据绑定的对比

视图绑定和数据绑定均会生成可用于直接引用视图的绑定类。但是,视图绑定旨在处理更简单的用例,与数据绑定相比,具有以下优势:

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

反过来,与数据绑定相比,视图绑定也具有以下限制:

考虑到这些因素,在某些情况下,最好在项目中同时使用视图绑定和数据绑定。您可以在需要高级功能的布局中使用数据绑定,而在不需要高级功能的布局中使用视图绑定。

So:现在Kotlin和Java都在积极使用ViewBinding,主要还是以下优点导致的 ~

  • Null 安全:由于视图绑定会创建对视图的直接引用,因此不存在因视图 ID 无效而引发 Null 指针异常的风险。此外,如果视图仅出现在布局的某些配置中,则绑定类中包含其引用的字段会使用 @Nullable 标记。
  • 类型安全:每个绑定类中的字段均具有与它们在 XML 文件中引用的视图相匹配的类型。这意味着不存在发生类转换异常的风险。
    这些差异意味着布局和代码之间的不兼容将会导致构建在编译时(而非运行时)失败。
  • ViewBinding生成的绑定类是一个Java类,并且添加了Kotlin的注解,可以很好的支持 Java 和 Kotlin 两种编程语言。

简单原理

ViewBinding主要是将layout内的xml进行组件实体化,也就意味着每一个xml都对应着一个ViewBinding类,那么在项目中我们可以直接通过 xxxBind类进行相关使用 ~

Activity使用示例

 xxxMainBinding mBinding = xxxBinding.inflate(getLayoutInflater());
 setContentView(mBinding.getRoot());

在使用前,先了解一些基本知识

  • xml存放位置:常规存于layout目录
    在这里插入图片描述
  • ViewBinding存储位置:我们可以通过app - build - generated - 查看data_binding目录下的Binding类
    在这里插入图片描述

原理解析:我们通过一个简单的 xml 和 Binding类 来分析ViewBinding的初级原理~

activity_main

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tv_text"
        android:layout_width="wrap_content"
        android:layout_height="50dp"
        android:gravity="center"
        android:text="TextView"
         />

</LinearLayout>

对应的ActivityMainBinding
在这里插入图片描述

ActivityMainBinding

个人总结:内部依旧采用了findViewById的方式获取控件,不过在其之上封装了控件id为null的场景,同时进行了控件的类型匹配,减少了我们手动转换的过程 ~

// Generated by view binder compiler. Do not edit!
package com.example.viewbinding.databinding;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.viewbinding.ViewBinding;
import com.example.viewbinding.R;
import java.lang.NullPointerException;
import java.lang.Override;
import java.lang.String;

public final class ActivityMainBinding implements ViewBinding {
  @NonNull
  private final LinearLayout rootView;

  @NonNull
  public final TextView tvText;

  private ActivityMainBinding(@NonNull LinearLayout rootView, @NonNull TextView tvText) {
    this.rootView = rootView;
    this.tvText = tvText;
  }

  @Override
  @NonNull
  public LinearLayout getRoot() {
    return rootView;
  }

  //视图填充,重载方法1
  @NonNull
  public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater) {
    return inflate(inflater, null, false);
  }
 
  //视图填充,重载方法2
  @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);
  }

  //Look Here:内部封装了控件为NUll的场景,同时控件进行了类型匹配
  @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_text;
      TextView tvText = rootView.findViewById(id);
      if (tvText == null) {
        break missingId;
      }

      return new ActivityMainBinding((LinearLayout) rootView, tvText);
    }
    String missingId = rootView.getResources().getResourceName(id);
    throw new NullPointerException("Missing required view with ID: ".concat(missingId));
  }
}

使用方式

环境要求-注意:视图绑定在 Android Studio 3.6 Canary 11 及更高版本 中可用。

具体步骤

  1. build.gradle加入以下配置(支持按模块启用ViewBinding)
android {
        ...
        viewBinding {
            enabled = true
        }
    }
  1. layout中创建对应的xml

activity_main

<?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:orientation="vertical"
    android:gravity="center"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tv_text"
        android:layout_width="wrap_content"
        android:layout_height="50dp"
        android:gravity="center"
        android:text="TextView"
         />
</LinearLayout>

补充:如不希望生成某xml的视图绑定类,则可在该xml的根布局加入以下属性

  tools:viewBindingIgnore="true"
  1. 不同场景使用ViewBinding ,此处以Activity为示例
  • 未使用ViewBinding
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        setContentView(R.layout.activity_main);
        TextView mText = findViewById(R.id.tv_text);
        mText.setText("tvText");
    }
}
  • 已使用ViewBinding
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        ActivityMainBinding mBinding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(mBinding.getRoot());
        mBinding.tvText.setText("tvText");
    }
}

使用场景

为什么要单独说一下使用场景?主要是ViewBinding在Activity、Fragment、Adapter、自定义View等场景中的使用,有所区别 ~

Activity

如需设置绑定类的实例以供 Activity 使用,请在 Activity 的 onCreate() 方法中执行以下步骤:

  1. 调用生成的绑定类中包含的静态 inflate() 方法。此操作会创建该绑定类的实例以供 Activity 使用。
  2. 通过调用 getRoot()方法或使用 Kotlin 属性语法 获取对根视图的引用。
  3. 将根视图传递到 setContentView(),使其成为屏幕上的活动视图。
package com.example.viewbinding;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;

import com.example.viewbinding.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        ActivityMainBinding mBinding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(mBinding.getRoot());
        mBinding.tvText.setText("tvText");
    }
}

activity_main

<?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:orientation="vertical"
    android:gravity="center"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tv_text"
        android:layout_width="wrap_content"
        android:layout_height="50dp"
        android:gravity="center"
        android:text="TextView"
         />
</LinearLayout>
include

不论是在Activity,还是Fragment中,如果你使用了include标签,那么都首先要通过一级xml的id,之后在获取include内部xml的控件id ~

include布局不包含merge标签

include_text(include视图)

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <TextView
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        android:gravity="center"
        android:layout_width="wrap_content"
        android:layout_height="50dp"
        android:text="Include TextView"
        android:id="@+id/include_tv_text"
        tools:ignore="MissingConstraints" />
</androidx.constraintlayout.widget.ConstraintLayout>

activity_main(引用include的xml)

<?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:orientation="vertical"
    android:gravity="center"
    tools:context=".MainActivity">

    <include
        android:id="@+id/main_include"
        layout="@layout/include_text"
        />
</LinearLayout>

MainActivity

package com.example.viewbinding;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;

import com.example.viewbinding.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        ActivityMainBinding mBinding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(mBinding.getRoot());
        
        //include部分
        mBinding.mainInclude.includeTvText.setText("Include Text");
        mBinding.mainInclude.includeTvText.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mBinding.tvText.setText("Include 点击事件");
                mBinding.mainInclude.includeTvText.setText("Include 点击事件");
            }
        });
    }
}

补充:在使用include时,不可在二级视图的根布局加id,否则ViewBinding会报错,示例如下 ~

include_text(include视图)

<?xml version="1.0" encoding="utf-8"?>
<!--根布局id - include_parent-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/include_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <TextView
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        android:gravity="center"
        android:layout_width="wrap_content"
        android:layout_height="50dp"
        android:text="Include TextView"
        android:id="@+id/include_tv_text"
        tools:ignore="MissingConstraints" />
</androidx.constraintlayout.widget.ConstraintLayout>
include布局包含merge标签

include_text(include视图)

仅将最外层布局更改为merge标签

<?xml version="1.0" encoding="utf-8"?>
<merge
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <TextView
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        android:gravity="center"
        android:layout_width="wrap_content"
        android:layout_height="50dp"
        android:text="Include TextView"
        android:id="@+id/include_tv_text"
        tools:ignore="MissingConstraints" />
</merge>

Look here:其他与上方相同,包含xml的引用和ViewBinding的使用

在运行项目后,会直接报错:Caused by: java.lang.NullPointerException: Missing required view with ID: com.example.viewbinding:id/main_include
在这里插入图片描述
因为每个xml都会生成一个ViewBinding类,通过查看IncludeTextBinding后,我们将子类的xml通过bind的方式绑定在根布局上 ~

  @NonNull
  public static IncludeTextBinding 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_tv_text;
      TextView includeTvText = rootView.findViewById(id);
      if (includeTvText == null) {
        break missingId;
      }

      return new IncludeTextBinding(rootView, includeTvText);
    }
    String missingId = rootView.getResources().getResourceName(id);
    throw new NullPointerException("Missing required view with ID: ".concat(missingId));
  }

具体操作如下,亲测可用

  1. 去除include标签 id
<?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:gravity="center"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <include layout="@layout/include_text" />
</LinearLayout>
  1. 通过IncludeTextBinding.bind(view)的方式获取ActivityMainBinding视图,思想上有点像include常规的id查询方式
package com.example.viewbinding;

import android.os.Bundle;

import com.example.viewbinding.databinding.ActivityMainBinding;
import com.example.viewbinding.databinding.IncludeTextBinding;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        ActivityMainBinding mBinding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(mBinding.getRoot());
        
        //之前的
		// mBinding.mainInclude.includeTvText.setText("Include Text");

        IncludeTextBinding includeBinding = IncludeTextBinding.bind(mBinding.getRoot());
        includeBinding.includeTvText.setText("Include 冲刺把,卡布达");
    }
}
Fragment

如需设置绑定类的实例以供 Fragment 使用,请在 Fragment 的 onCreateView() 方法中执行以下步骤:

  1. 调用生成的绑定类中包含的静态 inflate() 方法。此操作会创建该绑定类的实例以供 Fragment 使用。
  2. 通过调用 getRoot() 方法或使用 Kotlin 属性语法 获取对根视图的引用。
  3. onCreateView() 方法返回根视图,使其成为屏幕上的活动视图。

注意:注意:inflate() 方法会要求您传入布局膨胀器。如果布局已膨胀,您可以调用绑定类的静态 bind() 方法。如需了解详情,请查看视图绑定 GitHub 示例中的例子

package com.example.viewbinding;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.example.viewbinding.databinding.FragmentTestBinding;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

/**
 * @author MrLiu
 * @date 2021/11/1
 * desc ViewBinding在Fragment中的使用场景
 */
public class TestFragment extends Fragment {

    private FragmentTestBinding mBinding;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        mBinding = FragmentTestBinding.inflate(inflater);
        mBinding.tvFragment.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mBinding.tvFragment.setText("冲击吧,卡布达~");
            }
        });
        return mBinding.getRoot();
    }

	//官方建议:防止OOM(感觉在项目中可以通过Lifecycle写个BaseFragment)
    @Override
    public void onDestroy() {
        super.onDestroy();
        mBinding = null;
    }
}

fragment_test

<?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="match_parent">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="25dp"
        android:gravity="center"
        android:id="@+id/tv_fragment"
        android:text="Fragment Text"
        />
</LinearLayout>
Adapter

此处借鉴了一篇 blog 中的使用方式,需注意以下要点

  • ViewHolder的构造器参数改为使用的Binding对象
  • 实例化ViewHolder的时候传入相应的Binding对象
package com.example.viewbinding;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import com.example.viewbinding.databinding.ItemAdapterBinding;

import java.util.List;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;

/**
 * @author MrLiu
 * @date 2021/11/1
 * desc ViewBinding在Adapter中的使用场景
 */
class TestAdapter extends RecyclerView.Adapter<TestAdapter.TestHolder> {
    private List<String> mList;

    public TestAdapter(List<String> list) {
        mList = list;
    }

    //LookHere:不同的就在这里
    @NonNull
    @Override
    public TestHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        //常规写法
        //View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_comment, parent, false);
        //ViewHolder holder = new ViewHolder(view);

        //使用ViewBinding的写法
        ItemAdapterBinding testBinding = ItemAdapterBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
        TestHolder testHolder = new TestHolder(testBinding);
        return testHolder;
    }

    @Override
    public void onBindViewHolder(@NonNull TestHolder holder, int position) {
        holder.mItem.setText("");
    }

    @Override
    public int getItemCount() {
        return mList.size();
    }

    //LookHere:不同的就在这里
    public class TestHolder extends RecyclerView.ViewHolder {
        TextView mItem;

        //常规写法
//        public TestHolder(@NonNull View itemView) {
//            super(itemView);
//             mItem = itemView.findViewById(R.id.tv_item);
//        }

        //使用ViewBinding的写法
        TestHolder(@Nullable ItemAdapterBinding itemAdapterBinding) {
            super(itemAdapterBinding.getRoot());
            mItem = itemAdapterBinding.tvItem;
        }
    }
}

item_adapter

<?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="40dp">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:id="@+id/tv_item"
        android:text="Adapter Text"
        />
</LinearLayout>
自定义Dialog

关于自定义Dialog的写法与Activity、Fragment相近,不需特别关照

package com.example.viewbinding;

import android.app.Dialog;
import android.content.Context;
import android.view.View;

import com.example.viewbinding.databinding.DialogTestBinding;
import androidx.annotation.NonNull;

/**
 * @author MrLiu
 * @date 2021/11/2
 * desc ViewBinding在自定义Dialog中的使用场景
 */
public class TextDialog extends Dialog {
    private View mView;

    public TextDialog(@NonNull Context context) {
        super(context);
    }

    public TextDialog(@NonNull Context context, int themeResId) {
        super(context, themeResId);
        //常规写法
        //mView = View.inflate(getContext(), getLayoutId(), null);

        //使用ViewBinding的写法
        DialogTestBinding mBinding = DialogTestBinding.inflate(getLayoutInflater());
        mView = mBinding.getRoot();

        setContentView(mView);
    }
}

dialog_test

<?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="match_parent">

</LinearLayout>
自定义View

关于自定义View的相关部分主要借鉴了此篇 blog ,根据layout是否使用merge?划分为俩部分进行讲解 ~

布局中不包含Merge标签

view_my_layout(子布局)

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="这是自定义布局"
        android:textSize="50sp" />

</androidx.constraintlayout.widget.ConstraintLayout>

MyLinearLayout (自定义控件)

  • init1、2、3、4是使用inflate来导入layout布局的常规写法,全部可以正常显示自定义的布局。
  • init10、11、12是使用ViewBinding的写法10无法正常显示视图,11和12是两种不同的写法,道理一样。
public class MyLinearLayout extends LinearLayout {
    public MyLinearLayout(Context context) {
        this(context, null);
    }

    public MyLinearLayout(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

//        init1();
//        init2();
//        init3();
        init4();
    }

    private void init1() {
        inflate(getContext(), R.layout.view_my_layout, this);
    }

    private void init2() {
        View view = LayoutInflater.from(getContext()).inflate(R.layout.view_my_layout, this);
    }

    //和init2()方法相等
    private void init3() {
        View view = LayoutInflater.from(getContext()).inflate(R.layout.view_my_layout, this, true);
    }

    private void init4() {
        View view = LayoutInflater.from(getContext()).inflate(R.layout.view_my_layout, this, false);
        addView(view);
    }

    //Notice:视图异常,布局无法填充满
    private void init10() {
        ViewMyLayoutBinding binding = ViewMyLayoutBinding.inflate(LayoutInflater.from(getContext()));
        addView(binding.getRoot());
    }

    private void init11() {
        ViewMyLayoutBinding binding = ViewMyLayoutBinding.inflate(LayoutInflater.from(getContext()), this, true);
    }

    private void init12() {
        ViewMyLayoutBinding binding = ViewMyLayoutBinding.inflate(LayoutInflater.from(getContext()), this, false);
        addView(binding.getRoot());
    }

}
布局中包含Merge标签

view_my_layout_merge(子布局)

<?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="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="这是自定义merge"
        android:textSize="50sp" />
        
</merge>

MyLinearLayout(自定义控件)

Look here:仅init20()使用ViewBinding有效

    private void init20() {
        ViewMyLayoutMergeBinding binding = ViewMyLayoutMergeBinding.inflate(LayoutInflater.from(getContext()), this);
    }

    //Notice:没有效果,可以理解为还没有rootView
    private void init21() {
        ViewMyLayoutMergeBinding binding = ViewMyLayoutMergeBinding.bind(this);
    }
俩种场景下的ViewBinding

我们对比下使用merge标签和不使用merge标签所对应的Binding文件

  • 不使用merge标签的Binding代码如下

inflate(@NonNull LayoutInflater inflater) 调用了 inflate(@NonNull LayoutInflater inflater, @Nullable ViewGroup parent, boolean attachToParent) 方法,最终调用了bind(@NonNull View rootView)方法

  @NonNull
  public static ViewMyLayoutBinding inflate(@NonNull LayoutInflater inflater) {
    return inflate(inflater, null, false);
  }

  @NonNull
  public static ViewMyLayoutBinding inflate(@NonNull LayoutInflater inflater,
      @Nullable ViewGroup parent, boolean attachToParent) {
    View root = inflater.inflate(R.layout.view_my_layout, parent, false);
    if (attachToParent) {
      parent.addView(root);
    }
    return bind(root);
  }

  @NonNull
  public static ViewMyLayoutBinding bind(@NonNull View rootView) {
    if (rootView == null) {
      throw new NullPointerException("rootView");
    }
    return new ViewMyLayoutBinding((ConstraintLayout) rootView);
  }
  • 使用merge标签生成的代码大致如下,inflate()方法最终调用了bind()方法
  @NonNull
  public static ViewMyLayoutMergeBinding inflate(@NonNull LayoutInflater inflater,
      @NonNull ViewGroup parent) {
    if (parent == null) {
      throw new NullPointerException("parent");
    }
    inflater.inflate(R.layout.view_my_layout_merge, parent);
    return bind(parent);
  }

  @NonNull
  public static ViewMyLayoutMergeBinding bind(@NonNull View rootView) {
    if (rootView == null) {
      throw new NullPointerException("rootView");
    }
    return new ViewMyLayoutMergeBinding(rootView);
  }
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

远方那座山

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值