Android 数据绑定和 MVVM - 对不同布局文件夹中的布局文件使用相同的名称

Android DataBinding & MVVM - Using same name for layout files in different layout folders

我一直在开发具有数据绑定和 MVVM 的应用程序。

我正在尝试为横向模式的应用程序使用替代布局。我有:

layout/fragment_content.xml
layout-land/fragment_content.xml

两种布局都具有相同的视图但外观不同,并且从相同的视图模型获取提要,如下所示:

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

<data class="MyBinding">

    <variable
        name="viewModel"
        type="com.myapp.package.viewModel.VMFirst"/>

    <variable
        name="controlModel"
        type="com.myapp.package.viewModel.VMSecond"/>
</data>

<DIFFERENT CONTENT HERE>

所有视图和 ID 都存在于两种布局中。

嗯,问题是,它没有编译,错误只是 "cannot find symbol method getViewModel" 和另一个变量的 getter。

到目前为止我尝试了什么:

  1. 使用 layout 和 layout-land 文件夹(失败,上面解释了错误)

  2. 使用布局别名Use Layout Aliases which I found here Issue 199344: Data binding does not work with layout aliases。在尝试这种方法时,我没有更改 xml 文件中的任何内容。这也失败了,错误是Could not write to com.myapp.package.databinding.MyBinding

是否不能在多个布局文件中使用数据绑定 data 标签?在使用数据绑定时,我应该使用什么来为不同的状态使用不同的布局?谢谢!

编辑:删除class="MyBinding"没有改变错误。

By default, a Binding class will be generated based on the name of the layout file, converting it to Pascal case and suffixing "Binding" to it. The above layout file was main_activity.xml so the generate class was MainActivityBinding. --Binding Data

并在编译时生成。

所以,select 不同的布局 java 代码。

layout/
R.layout.activity_main
R.layout.activity_main_tablet

values/
    <bool name="is_mobile">true</bool>
    <bool name="is_tablet">false</bool>
values-w820dp/
    <bool name="is_mobile">false</bool>
    <bool name="is_tablet">true</bool>



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

    if(getResources().getBoolean(R.bool.is_mobile)) {
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);            
    } else {
        ActivityMainTabletBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main_tablet);   
    }

}

我在我的应用程序中大量使用 MVVM,并且还在围绕它构建一个库。

我遵循每个 XML 中只有一个 ViewModel 的惯例。此外,viewmodel 变量的名称在所有 XML 中都是相同的。

因此,在您的情况下,您可以创建另一个包含 VMFirstVMSecond 的 ViewModel class。

public class ParentVM {
   VMFirst first;
   VMSecond second;
}

两个 XMLs(纵向和横向)将具有相同的名称,例如 activity_main.xml

<layout>
    <data>
      <variable 
          type="ParentViewModel"
          name="vm"/>
    </data>

那么在MainActivity代码中就不需要检查了。

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    ViewDataBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
    binding.setVariable(BR.vm, new ParentViewModel());
}

这有效。

单一ViewModel的优点

事实上,因为我在所有 xml 中都遵循相同的变量名,所以我能够在基础 class MvvmActivity 本身中包含绑定逻辑。所以,我所有的活动看起来像:

public class MainActivity extends MvvmActivity {

    @NonNull
    @Override
    protected ViewModel createViewModel() {
        return new MainViewModel();
    }

    @Override
    protected int getLayoutId() {
        return R.layout.activity_main;
    }
}

MvvmActivity 实现:MvvmActivity.java

保持常量数据绑定变量的另一个优点是您可以在 XML 本身中设置 RecyclerView 或 ViewPager 适配器。有关详细信息,请参阅 Setup RecyclerView from XML

如果有人搜索这个问题,2 年后我尝试做同样的事情,我发现它现在工作正常。

我在 layoutlayout_sw600dp 下创建了一个布局文件 activity_main。这是 layout 资源下的布局:

<layout 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">

    <variable
        name="small_variable"
        type="Integer"/>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/myRoot"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <View
            android:id="@+id/small_square"
            android:layout_width="60dp"
            android:layout_height="60dp"
            android:background="@android:color/holo_blue_bright"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

这个是layout_sw600dp文件夹下的布局:

<?xml version="1.0" encoding="utf-8"?>

<layout 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">

    <variable
        name="big_variable"
        type="Long"/>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/myRoot"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <View
            android:id="@+id/big_square"
            android:layout_width="60dp"
            android:layout_height="60dp"
            android:background="@android:color/holo_blue_bright"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

两者都有一个视图,但每个视图都有不同的 ID:small_squarebig_square

我 运行 phone 和平板电脑上的项目。这是我的发现:

  • DataBinding 在不同布局文件夹中的所有同名布局文件下创建一个包含 ALL 视图和变量的实现。
  • 存在于所有布局中的视图不可为空,所有其他视图均可为空。在上面 XML 中,myRoot 在使用来自 Kotlin 的绑定时 不是可空视图 ,而 big_squaresmall_square 可以为 null 视图。变量可以为空,无论它们是否存在于所有布局中(这是预期的行为)。
  • 您不能在每个文件中命名绑定 类 不同。它必须相同(上面示例中的 MainBinding,或者如果您未定义它,则默认为 LayoutResourceName + Binding)。
  • 关于绑定实现的视图和变量的名称是驼峰式的。所以我的 small_variable & small_square 在代码方面是 binding.smallVariablebinding.smallSquare
  • 使用 Kotlin,您可以只使用像 binding.bigSquare?.operation 这样的视图,这很棒,您不需要事先检查它是平板电脑还是 phone 或者视图是否为 null。
  • 提示一下,您可以分配 binding 个字段,即使它们所在的布局不会被使用。您仍然可以在代码上说 binding.smallVariable = 3 ,它会进行分配并保存值。我觉得还是小心点好。