问题:

最近使用模块化开发一个新项目,但是Butterknife真是闹心,即使在我成功弄了R2,项目也正常运行之后还是发现很多问题。

  1. 经常出现的R2爆红,必须重新构建之后才可以找到文件(强迫症表示不服);
  2. 又偶尔出现xml文件找不到的问题,之后莫名其妙的有可以找到了。我猜想可能和这个R2有关系,真心不好用。

使用视图绑定ViewBinding的优点:简洁、编译安全、编译速度快。

原理:

构建组件:视图绑定

Google在那个用来编译的gradle插件中增加了新功能,当某个module开启ViewBinding功能后,编译的时候就去扫描此模块下的layout文件,生成对应的binding类。那些你所熟悉的findViewById操作都是在这个自动生成的类中。

使用方法:

1.在要使用ViewBinding的 module 的gradle文件中开启ViewBinding

android {
    ...
    viewBinding {
        enabled = true
    }
    ...
}

2.若不想为某个布局文件生成binding类,则可以使用属性viewBindingIgnore添加到布局的根视图中即可:

<androidx.constraintlayout.widget.ConstraintLayout
      tools:viewBindingIgnore="true" >

</androidx.constraintlayout.widget.ConstraintLayout>

3.编写XML布局文件:activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".MainActivity"
    tools:viewBindingIgnore="true" >

    <TextView
        android:id="@+id/tv_content"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

对应在Module文件路径:
build\generated\data_binding_base_class_source_out\debug\out\包名\databinding下,生成类文件为ActivityMainBinding.java

4.在Activity中简单使用:

public class MainActivity extends AppCompatActivity {

    private ActivityMainBinding activityMainBinding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //设置布局文件
        activityMainBinding = ActivityMainBinding.inflate(LayoutInflater.from(this));
        setContentView(activityMainBinding.getRoot());

        //设置文本
        activityMainBinding.tvContent.setText("你好");
    }
}

5.针对布局中使用include

编写XML布局文件:layout_comment.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:id="@+id/tv_include"
        android:text="你好"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

在activity_main.xml布局中引用include布局:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 
    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"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tv_content"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

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

</androidx.constraintlayout.widget.ConstraintLayout>

若要使用到layout_comment.xml布局中的控件,必须为include标签声明id。

Activity中使用代码:

activityMainBinding.layoutInclude.tvInclude.setText("谢谢");

注:include根布局再添加id时,此时会报错:java.lang.NullPointerException: Missing required view with ID:***。

6.include布局使用merge进行布局优化问题

修改上文的layout_comment.xml布局,根布局使用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="wrap_content">

    <TextView
        android:id="@+id/tv_include"
        android:text="你好"
        android:gravity="end"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</merge>

Activity中正确使用代码:

LayoutCommentBinding commentBinding = LayoutCommentBinding.bind(activityMainBinding.getRoot());
commentBinding.tvInclude.setText("优化");

7.Fragment中简单使用

@Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        //使用ViewBinding
        FragmentBaseBinding fragmentBaseBinding = FragmentBaseBinding.inflate(inflater);
        return fragmentBaseBinding.getRoot();
}

8.自定义Dialog中使用

public class MyDialog extends Dialog {

    protected View mView;
    protected DialogBottomBinding mBinding;
    
    public MyDialog(@NonNull Context context, @StyleRes int themeResId) {
        super(context, themeResId);

        //原来的写法
        mView = View.inflate(getContext(), getLayoutId(), null);

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

9.自定义View中使用

编写view_my_layout.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>

编写自定义view:MyLinearLayout,并添加上面布局。

// 自定义view
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);

        // 正常添加布局
        ViewMyLayoutBinding binding = ViewMyLayoutBinding.inflate(LayoutInflater.from(getContext()), this, true);

        // 针对根标签为merge
        ViewMyLayoutMergeBinding binding = ViewMyLayoutMergeBinding.inflate(LayoutInflater.from(getContext()), this);
    }

}

注:使用merge标签和不使用merge标签所对应的Binding文件是不同的,所以需要区别对待。

10.Base常见封装

编写布局文件activity_base.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:orientation="vertical"
    tools:context=".BaseActivity">

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/colorPrimary" />

</LinearLayout>

BaseActivity代码:

public abstract class BaseActivity<T extends ViewBinding> extends AppCompatActivity {
    public ActivityBaseBinding baseBinding;
    public T viewBinding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        baseBinding = ActivityBaseBinding.inflate(getLayoutInflater());
        setContentView(baseBinding.getRoot());
        viewBinding = getViewBinding();
    }

    protected abstract T getViewBinding();

    public void setToolbarTitle(CharSequence title) {
        baseBinding.toolbar.setTitle(title);
    }

    public void setToolbarTitle(@StringRes int stringId) {
        baseBinding.toolbar.setTitle(stringId);
    }
}

MainActivity中使用:

public class MainActivity extends BaseActivity<ActivityMainBinding> {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setToolbarTitle("标题");
        viewBinding.tvContent.setText("使用ViewBinding");
    }

    @Override
    protected ActivityMainBinding getViewBinding() {
        return ActivityMainBinding.inflate(getLayoutInflater(), baseBinding.getRoot(), true);
    }
}