JetPack-(1)-ViewBinding

172 阅读4分钟

ViewBinding(视图绑定)

ViewBinding是android jetpack的一个特性,他的目的只有一个,就是避免编写findViewById,因为它会为每个XML布局文件生成一个绑定类。绑定类的实例包含在相应布局xml文件中具有ID的所有视图的直接引用。

ViewBinding的优点

  1. Null安全: 由于视图绑定会创建对视图的直接引用,因此不存在因视图ID无效而引发Null指针异常的风险。
  2. 类型安全: 减少控件类型转换带来的错误(例如text的id给Button对象带来的运行时错误)

与FindViewById的区别

findViewById编写过于繁琐,还需要进行类型转换,造成类型不安全。

与数据绑定的比较

准备工作

在Module级别的build.gradle文件中:

android {
    buildFeatures {
        viewBinding = true
    }
}

如果要忽略某个布局文件,需要添加 tools:viewBindingIgnore="true" 属性到布局中

<LinearLayout
              ...
              tools:viewBindingIgnore="true" >
  		  ...
</LinearLayout>

如何使用:

当开启ViewBinding后,系统会为该模块中每个XML布局文件生成一个绑定类(转换为驼峰命名并在末尾添加Binding),每个绑定类均包含根视图已经由id的所有视图的引用

例如:布局文件名为 activity_main.xml ,生成绑定类为 ActivityMainBinding

  1. 在Activity中使用

使用流程:

  • 开启视图绑定功能后,系统会为该模块中的XML布局生成一个绑定类,每个绑定类都包含根布局和具有ID布局的引用。
  • 调用绑定类的inflate() 方法获取绑定类对象。
  • 调用绑定类对象的getRoot() 方法获取根布局传递到 setContentView()。

XML布局:

<?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:gravity="center_horizontal"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tv_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/tv_age"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <ImageView
        android:id="@+id/iv_avatar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="确定" />
</LinearLayout>

Activity类:

public class MainActivity extends AppCompatActivity {

      private ActivityMainBinding binding;
      
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        binding.tvName.setText("hello world");
        binding.tvAge.setText(String.valueOf(18));
        binding.ivAvatar.setImageResource(R.mipmap.ic_launcher_round);
        binding.btn.setOnClickListener(view -> {
            Toast.makeText(mContext, "hello", Toast.LENGTH_SHORT).show();
        });
    }
}

  1. 在Adapter中使用

RecyclerView的Item布局(main_item.xml)

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    .....>

    <ImageView
        android:id="@+id/mainImage"
        ..... />

    <TextView
        ..... />
</LinearLayout>

Adapter如何使用:


public class MainAdapter extends RecyclerView.Adapter<MainAdapter.ViewHolder> {
    private List<Fruit> fruitList;

    public class ViewHolder extends RecyclerView.ViewHolder {
        public ImageView fruitImage;
        public TextView fruitName;

        public ViewHolder(MainItemBinding binding) {
            super(binding.getRoot());
            fruitImage = binding.img;
            fruitName = binding.textView;
        }
    }

    @NonNull
    @Override
    public MainAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        MainItemBinding binding = MainItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
        return new ViewHolder(binding);
    }

    @Override
    public void onBindViewHolder(@NonNull MainAdapter.ViewHolder holder, int position) {
        Fruit fruit = fruitList.get(position);
        holder.fruitImage.setImageResource(fruit.getFruitImage());
        holder.fruitName.setText(fruit.getFruitName());
    }

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

  1. 在Fragment中使用
  • 调用绑定类的 inflate() 方法,获取绑定类对象。
  • 在调用 getRoot() 方法获取根布局。
  • onCreateView() 方法返回根布局,使其成为屏幕上的活动视图。
  • 由于 Fragment 的存在时间比视图长。因此需要在 FragmentonDestroyView() 方法中清除对绑定类对象的所有引用。
public class BlankFragment extends Fragment {
    private FragmentBlankBinding binding;

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

    @Override
    public void onDestroy() {
        super.onDestroy();
        binding = null;
    }
}
  1. 在引入布局(include)中使用

被引入布局title_bar.xml

 <?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     .....>
     <Button
         android:text="include"
         android:id="@+id/test_button"
         ....../></LinearLayout>

主布局activity_main.xml

 <?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     .....>
     
     <!--在include标签中添加id属性-->
     <include
         android:id="@+id/title_bar"  
         //注意要写id不然ViewBinding是关联不到title_bar.xml中的控件的layout="@layout/title_bar"/>
     ...
 </LinearLayout>

在Activity中去引用:

public class MainActivity extends Activity {

    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
       //直接使用include标签的id,然后再根据include的id引用include布局里面的id
        binding.titleBar.testButton.setText("hello");
    }
}

5. 在include的merge标签中使用

  • merge:使用merge标签引入的布局在某些情况下可以减少一层布局的嵌套,而更少的布局嵌套通常就意味更高的效率。
  • merge和普通引入的区别: 首先我们在include的使用不可以再给他id了,因为merge标签并不是一个布局,所以我们无法像刚才那样再include的时候给他指定一个id。

merge标签布局title_bar.xml

 <merge xmlns:android="http://schemas.android.com/apk/res/android">
 
     <Button
         android:text="include"
         android:id="@+id/test_include"
         ....../>
 </merge>

引入布局activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     .....>
     
     <!--在include标签中添加id属性-->
     <include
         //android:id="@+id/title_bar"  //因为merge不能用id
         layout="@layout/title_bar"/>
     ...
 </LinearLayout>

Activity利用ViewBinding绑定:


public class MainActivity extends Activity {

    private ActivityMainBinding binding;
    private TitleBarBinding titleBarBinding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityMainBinding.inflate(getLayoutInflater());

        //调用TitleBarBinding.bind()让title_bar.xml和我们的activity_main.xml关联起来
        titleBarBinding = TitleBarBinding.bind(binding.getRoot());
        
        setContentView(binding.getRoot());

  
        titleBarBinding.testInclude.setText("button");
    }
}