Android使用ViewBinding

手机APP/开发
269
0
0
2024-03-08
标签   Android

前言

原始方式

在我们的开发过程中,需要获取XML布局文件中的ViewId,以便其赋值显示,我们习惯使用findViewById进行操作,可这样会导致很多的模版代码出现。

Butter Knife框架

直到Android大神 Jake Wharton开源了Butter Knife框架,通过Bind方式绑定获取ViewId。

基于Kotlin的扩展

近几年Android对Kotlin的支持,我们开始使用 Android Kotlin extensions。

在文件中导入布局文件直接引用viewId。无需做其他额外操作,最为方便。

生成代码的方式

谷歌在 Android Studio 3.6 Canary 11 及更高版本中加入了新的视图绑定方式ViewBinding。

注意:

要使用ViewBinding功能,AndroidStudio至少要升级到3.6。

ViewBinding和Kotlin扩展

ViewBindingKotlin 扩展 都是 Android 开发中常用的技术,用于简化视图查找和绑定的过程。

以下是它们之间的一些比较:

ViewBinding:

  • ViewBinding 是由 Android 官方推荐和支持的库,从 Android Studio 3.6 版本开始引入。
  • ViewBinding 使用了编译时生成的绑定类,在 xml 布局文件中的每个视图都会生成一个对应的绑定类对象,因此在编译时检测到视图名称的错误。
  • ViewBinding 可以生成类型安全的代码,避免了手动查找和强制转换视图对象的麻烦。
  • ViewBinding 不会增加 APK 大小,因为它只是编译时生成的代码。
  • 在多个模块中引用同一个视图时可能会出现命名冲突的问题,需要通过手动指定全限定名解决。

Kotlin扩展:

  • Kotlin扩展 是 Kotlin 语言的特性,其通过扩展函数的方式,允许开发者为现有的类添加新的函数或属性。
  • Kotlin扩展 使用起来相对简单,可以直接在布局文件中使用 Kotlin 扩展函数来查找和操作视图。
  • Kotlin扩展 对视图的数据获取和类型安全性没有提供直接的支持,需要手动处理可能的空指针异常和类型转换。
  • Kotlin扩展 会增加 APK 的大小,因为它是在运行时动态添加的函数。

总体而言

ViewBinding 在类型安全性和编译时错误检测方面比 Kotlin扩展 更好。 它是官方推荐的方式,并且可以避免一些潜在的运行时异常。 但是,如果你已经熟悉并且喜欢使用 Kotlin 扩展,并且对 APK 大小没有严格要求,那么你可以选择使用它。

ViewBinding

原理就是

Google在那个用来编译的gradle插件中增加了新功能,当某个module开启ViewBinding功能后,编译的时候就去扫描此模块下的layout文件,生成对应的binding类。

开启

KTS方式配置

build.gradle.kts中添加

android {
    viewBinding{
        enable = true
    }
}

注意

只要开启后,会自动遍历layout下的xml文件自动生成对应的类。

使用

页面中使用

private lateinit var binding: ActivityTexBinding

原来的

setContentView(R.layout.activity_tex)

替换为

binding = ActivityTexBinding.inflate(layoutInflater)
setContentView(binding.root)

使用

val text = "$$ c = \\pm\\sqrt{a^2 + b^2} $$"
binding.katexText.setText(text)

完整代码

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import cn.psvmc.texeditor.databinding.ActivityTexBinding

class TexActivity : AppCompatActivity() {
    private lateinit  var binding: ActivityTexBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityTexBinding.inflate(layoutInflater)
        setContentView(binding.root)
        val text = "$$ c = \\pm\\sqrt{a^2 + b^2} $$"
        binding.katexText.setText(text)
    }
}

注意

ActivityTexBinding是自动生成的类,它会自动遍历layout下的xml文件自动生成对应的类。 比如我的XML是activity_tex.xml,它自动生成的类就是ActivityTexBinding。 如果想在生成绑定类时忽略某个布局文件,将tools:viewBindingIgnore=”true”`属性添加到相应布局文件的根视图中。

基类

Activity

abstract class BaseActivity<T : ViewBinding> : AppCompatActivity() {
    private lateinit var _binding: T
    protected val binding get() = _binding;

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        _binding = getViewBinding()
        setContentView(_binding.root)
    }

    protected abstract fun getViewBinding(): T
}

使用

class MainActivity : BaseActivity<ActivityMainBinding>() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding.textView.text = "这是MainActivity"
    }

    override fun getViewBinding() = ActivityMainBinding.inflate(layoutInflater)
}

Fragment

abstract class BaseFragment<T : ViewBinding> : Fragment() {
    private lateinit var _binding: T
    protected val binding get() = _binding;
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = getViewBinding(inflater, container)
        return _binding.root
    }
    
    protected abstract fun getViewBinding(inflater: LayoutInflater, container: ViewGroup?): T
}

使用

class FirstFragment : BaseFragment<FragmentFirstBinding>() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding.textView.text = "这是FirstFragment"
    }

    override fun getViewBinding(
        inflater: LayoutInflater,
        container: ViewGroup?
    ) = FragmentFirstBinding.inflate(inflater, container, false)
}

其他

Fragment中使用

public class MyFragment extends Fragment {
  private FragmentMyBinding binding;

  public MyFragment() {

  }

  @Override
  public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);

  }

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

  @Override
  public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
      super.onViewCreated(view, savedInstanceState);
      binding.textView.setText("这是Fragment");
      binding.button.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View view) {
              Log.d("Fragment", "点击了按钮");
          }
      });
  }

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

Dialog中使用

class MyDialog extends Dialog {
    protected View mView;
    protected MyDialogBinding mBinding;

    public MyDialog(@NonNull Context context) {
        super(context,R.style.Dialog);
        mBinding = MyDialogBinding.inflate(getLayoutInflater());
        mView = mBinding.getRoot();
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(mView);
        Window window = this.getWindow();
        WindowManager.LayoutParams lp = window.getAttributes();
        Display d = window.getWindowManager().getDefaultDisplay();
        lp.width = (int) (d.getWidth() * 0.9F);
        window.setAttributes(lp);
    }
  

}

Adapter 中使用

public class MainAdapter extends RecyclerView.Adapter<MainAdapter.ViewHolder> {

    private List<String> mList;

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

    @NonNull
    @Override
    public MainAdapter.ViewHolder 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的写法
        LayoutCommentBinding commentBinding = LayoutCommentBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
        ViewHolder holder = new ViewHolder(commentBinding);
        return holder;
    }

    @Override
    public void onBindViewHolder(@NonNull MainAdapter.ViewHolder holder, int position) {
        holder.mTextView.setText(mList.get(position));
    }

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

    static class ViewHolder extends RecyclerView.ViewHolder {

        TextView mTextView;

        //之前的写法
        //public ViewHolder(@NonNull View itemView) {
        //    super(itemView);
        //    mTextView = itemView.findViewById(R.id.tv_include);
        //}

        //使用ViewBinding的写法
        ViewHolder(@NonNull LayoutCommentBinding commentBinding) {
            super(commentBinding.getRoot());
            mTextView = commentBinding.tvInclude;
        }

    }
}

自定义View中使用

如果我们的自定义View中使用了layout布局,比如layout_my_view.xml,如下

<?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>

会生成一个名为LayoutMyViewViewBinding.java文件,在自定义View通过如下方式绑定,

public class MyView extends View {
    public MyView (Context context) {
        this(context, null);
    }

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

    public MyView (Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        LayoutMyViewViewBinding viewBinding = LayoutMyViewViewBinding.inflate(LayoutInflater.from(getContext()), this, true);
    }
}

如果自定义View布局文件中使用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>

此时要写成下面这个样子,

LayoutMyViewViewBinding viewBinding = LayoutMyViewViewBinding.inflate(LayoutInflater.from(context), this);

include标签的使用

include 标签不带 merge 标签,需要给 include 标签添加 id, 直接使用 id 即可,用法如下所示。

<include
    android:id="@+id/include"
    layout="@layout/layout_include_item" />

代码

ActivityMainBinding binding = ActivityMainBinding.inflate(layoutInflater);
binding.include.includeTvTitle.setText("使用 include 布局中的控件, 不包含 merge");

include 标签带 merge 标签,需要通过bind()将merge布局绑定到主布局上,用法如下所示。

<include
    layout="@layout/layout_merge_item" />

代码

ActivityMainBinding binding = ActivityMainBinding.inflate(layoutInflater);
LayoutMergeItemBinding mergeItemBinding = LayoutMergeItemBinding.bind(binding.getRoot());
mergeItemBinding.mergeTvTitle.setText("使用 include 布局中的控件, 包含 merge");